Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,10 @@

All notable changes to the Microsoft Agent 365 Customer Implementation Guide are documented here.

## 1.0.2 - 2026-09-09

- Added a `preview.webp` catalog screenshot and `preview` metadata so the catalog detail page shows a real preview of the guide. Guide content is unchanged.

## 1.0.1 - 2026-09-09

- Added `interactiveKind: guide` catalog metadata so the catalog labels this resource an "Interactive guide". Guide content is unchanged.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ author:
- "Pratik Bhusal"
- "Manas Biswas"
- "Alejandro Lopez"
version: 1.0.1
version: 1.0.2
published: 2026-09-09
updated: 2026-09-09
tags:
Expand All @@ -17,6 +17,7 @@ tags:
- security
format: interactive
interactiveKind: guide
preview: preview.webp
featured: false
status: active
whatItIs: >-
Expand Down
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
1 change: 1 addition & 0 deletions docs/CATALOG-METADATA.md
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,7 @@ prerequisites:
| `tags` | string list | Lowercase discovery terms. Prefer a few specific tags over many broad ones. |
| `format` | enum | One of `ps1`, `bundle`, `declarative`, `interactive`, `pptx`, `pbix`, or `md`. This replaces the former `artifact` label. |
| `interactiveKind` | enum | Only for `format: interactive`. Set `guide` for a walkthrough or implementation/decision guide, or `tool` for a calculator or utility. Defaults to `tool`, which the catalog labels "Interactive tool"; `guide` is labeled "Interactive guide". |
| `preview` | string | Optional relative path (inside the resource folder) to a screenshot the catalog detail page renders above the description, for example `preview.webp`. Use an optimized local image (WebP or PNG). No URLs, absolute paths, `..`, or backslashes. Omit it for resources without a screenshot. |
| `featured` | boolean | Use sparingly for resources selected for catalog promotion. Default is `false`. |
| `status` | enum | `active`, `preview`, or `archived`. Default is `active`. |
| `url` | HTTPS URL | Optional GitHub or destination URL. When omitted, the generator derives a GitHub URL from the resource path. |
Expand Down
104 changes: 104 additions & 0 deletions index.html
Original file line number Diff line number Diff line change
Expand Up @@ -757,6 +757,44 @@
.detail-main #detailDesc {
font-size: 1.15rem; line-height: 1.7; color: var(--text); margin: 0 0 2.5rem;
}
/* Resource preview screenshot */
.detail-preview { margin: 0 0 2.5rem; }
.detail-preview-frame {
display: block; width: 100%; margin: 0; padding: 0; border: 0;
background: var(--surface); border: 1px solid var(--border);
border-radius: 14px; overflow: hidden; position: relative;
box-shadow: 0 12px 32px -20px rgba(0, 0, 0, 0.55);
transition: transform 0.2s ease, border-color 0.2s ease, box-shadow 0.2s ease;
}
.detail-preview-frame img {
display: block; width: 100%; height: auto; aspect-ratio: 1600 / 960;
object-fit: cover; object-position: top center;
}
.detail-preview-frame[role="button"] { cursor: pointer; }
.detail-preview-frame[role="button"]:hover,
.detail-preview-frame[role="button"]:focus-visible {
border-color: var(--accent); transform: translateY(-2px);
box-shadow: 0 18px 40px -20px rgba(0, 0, 0, 0.6);
}
.detail-preview-frame[role="button"]:focus-visible { outline: 2px solid var(--accent); outline-offset: 3px; }
.detail-preview-badge {
position: absolute; bottom: 0.75rem; right: 0.75rem;
display: inline-flex; align-items: center; gap: 0.4rem;
padding: 0.4rem 0.7rem; border-radius: 999px;
font-size: 0.78rem; font-weight: 600; color: #fff;
background: rgba(17, 24, 39, 0.82); backdrop-filter: blur(4px);
opacity: 0; transform: translateY(4px); transition: opacity 0.2s ease, transform 0.2s ease;
pointer-events: none;
}
.detail-preview-frame[role="button"]:hover .detail-preview-badge,
.detail-preview-frame[role="button"]:focus-visible .detail-preview-badge { opacity: 1; transform: translateY(0); }
.detail-preview-caption {
margin: 0.6rem 0 0; font-size: 0.8rem; color: var(--text-muted);
}
@media (prefers-reduced-motion: reduce) {
.detail-preview-frame, .detail-preview-badge { transition: none; }
.detail-preview-frame[role="button"]:hover, .detail-preview-frame[role="button"]:focus-visible { transform: none; }
}
.detail-section-label,
.detail-main h3 {
font-family: var(--font-mono); font-size: 0.75rem; font-weight: 600;
Expand Down Expand Up @@ -1451,6 +1489,12 @@ <h1 class="detail-title" id="detailTitle"></h1>
<div class="detail-grid">
<div class="detail-main">
<p id="detailDesc"></p>
<figure class="detail-preview d-none" id="detailPreviewFigure">
<div class="detail-preview-frame" id="detailPreviewFrame">
<span class="detail-preview-badge" id="detailPreviewBadge" aria-hidden="true"></span>
</div>
<figcaption class="detail-preview-caption d-none" id="detailPreviewCaption"></figcaption>
</figure>
<h3>About this resource</h3>
<div id="detailSynthesis">
<section class="detail-content-section detail-what" id="detailWhatSection">
Expand Down Expand Up @@ -2511,6 +2555,66 @@ <h3>Browse all 54 scripts &rarr;</h3>
viewBtn.style.display = 'none';
viewBtn.onclick = null;
}

// Resource preview screenshot (graceful when a resource has no preview)
const previewFigure = document.getElementById('detailPreviewFigure');
const previewFrame = document.getElementById('detailPreviewFrame');
const previewBadge = document.getElementById('detailPreviewBadge');
const previewCaption = document.getElementById('detailPreviewCaption');
let previewImg = document.getElementById('detailPreviewImg');
if (!previewImg) {
previewImg = document.createElement('img');
previewImg.id = 'detailPreviewImg';
previewImg.loading = 'lazy';
previewImg.decoding = 'async';
previewImg.width = 1600;
previewImg.height = 960;
previewFrame.insertBefore(previewImg, previewBadge);
}
if (resource.preview) {
previewImg.src = resource.preview;
previewImg.alt = `Preview of the ${resource.name} ${formatArtifact(resource.artifact, resource).toLowerCase()}`;
const canOpen = Boolean(embedUrl);
// Reuse the existing viewer overlay pattern to offer a larger, interactive view.
const eyeSvg = '<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M15 12a3 3 0 11-6 0 3 3 0 016 0z"/><path d="M2.458 12C3.732 7.943 7.523 5 12 5c4.478 0 8.268 2.943 9.542 7-1.274 4.057-5.064 7-9.542 7-4.477 0-8.268-2.943-9.542-7z"/></svg>';
if (canOpen) {
previewFrame.setAttribute('role', 'button');
previewFrame.setAttribute('tabindex', '0');
previewFrame.setAttribute('aria-label', `Open ${resource.name} in the interactive viewer`);
previewBadge.innerHTML = `${eyeSvg}<span>Open interactive view</span>`;
previewBadge.classList.remove('d-none');
previewCaption.textContent = 'Select the preview to open the interactive guide.';
previewCaption.classList.remove('d-none');
previewFrame.onclick = () => openViewer(resource.name, embedUrl);
previewFrame.onkeydown = (e) => {
if (e.key === 'Enter' || e.key === ' ') { e.preventDefault(); openViewer(resource.name, embedUrl); }
};
} else {
previewFrame.removeAttribute('role');
previewFrame.removeAttribute('tabindex');
previewFrame.removeAttribute('aria-label');
previewBadge.classList.add('d-none');
previewBadge.innerHTML = '';
previewCaption.classList.add('d-none');
previewCaption.textContent = '';
previewFrame.onclick = null;
previewFrame.onkeydown = null;
}
previewFigure.classList.remove('d-none');
} else {
previewFigure.classList.add('d-none');
previewImg.removeAttribute('src');
previewImg.alt = '';
previewFrame.removeAttribute('role');
previewFrame.removeAttribute('tabindex');
previewFrame.removeAttribute('aria-label');
previewBadge.classList.add('d-none');
previewBadge.innerHTML = '';
previewCaption.classList.add('d-none');
previewCaption.textContent = '';
previewFrame.onclick = null;
previewFrame.onkeydown = null;
}

// Mount giscus discussion
mountGiscus(resource.slug, document.documentElement.getAttribute('data-theme'));
Expand Down
12 changes: 12 additions & 0 deletions tools/catalog-build/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -140,6 +140,14 @@ function validate(data, file) {
if (data.interactiveKind !== undefined && data.format !== 'interactive') {
add('interactiveKind', 'is only allowed when format is interactive');
}
if (data.preview !== undefined) {
if (!isNonEmptyString(data.preview)) add('preview', 'must be a non-empty string');
else if (/^[a-z]+:\/\//i.test(data.preview) || data.preview.startsWith('/') || data.preview.includes('..') || data.preview.includes('\\')) {
add('preview', 'must be a relative path inside the resource folder (no URL, absolute path, "..", or backslash)');
} else if (!existsSync(join(dirname(file), data.preview))) {
add('preview', 'points to a file that does not exist');
}
}
if (data.whatItIs !== undefined && !isNonEmptyString(data.whatItIs)) add('whatItIs', 'must be a non-empty string');
if (data.whyUseIt !== undefined && !isStringList(data.whyUseIt)) add('whyUseIt', 'must be a non-empty list of strings');
if (data.howToUse !== undefined && !isNonEmptyString(data.howToUse)) add('howToUse', 'must be a non-empty Markdown string');
Expand Down Expand Up @@ -170,6 +178,9 @@ function slugify(value) {

function createResource(data, file, updated) {
const relativePath = toPosix(relative(repositoryRoot, file));
const preview = data.preview
? toPosix(relative(repositoryRoot, join(dirname(file), data.preview)))
: undefined;
const resource = {
name: data.title,
title: data.title,
Expand All @@ -185,6 +196,7 @@ function createResource(data, file, updated) {
featured: data.featured ?? false,
status: data.status ?? 'active',
interactiveKind: data.interactiveKind,
preview,
author: data.author,
version: data.version,
published: data.published,
Expand Down