diff --git a/copilot-agent-strategy/agent-365-customer-implementation-guide/CHANGELOG.md b/copilot-agent-strategy/agent-365-customer-implementation-guide/CHANGELOG.md index fe9057e..752d554 100644 --- a/copilot-agent-strategy/agent-365-customer-implementation-guide/CHANGELOG.md +++ b/copilot-agent-strategy/agent-365-customer-implementation-guide/CHANGELOG.md @@ -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. diff --git a/copilot-agent-strategy/agent-365-customer-implementation-guide/README.md b/copilot-agent-strategy/agent-365-customer-implementation-guide/README.md index 1b7b182..bc84a64 100644 --- a/copilot-agent-strategy/agent-365-customer-implementation-guide/README.md +++ b/copilot-agent-strategy/agent-365-customer-implementation-guide/README.md @@ -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: @@ -17,6 +17,7 @@ tags: - security format: interactive interactiveKind: guide +preview: preview.webp featured: false status: active whatItIs: >- diff --git a/copilot-agent-strategy/agent-365-customer-implementation-guide/preview.webp b/copilot-agent-strategy/agent-365-customer-implementation-guide/preview.webp new file mode 100644 index 0000000..d295d6b Binary files /dev/null and b/copilot-agent-strategy/agent-365-customer-implementation-guide/preview.webp differ diff --git a/docs/CATALOG-METADATA.md b/docs/CATALOG-METADATA.md index 4180f7b..059ecdf 100644 --- a/docs/CATALOG-METADATA.md +++ b/docs/CATALOG-METADATA.md @@ -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. | diff --git a/index.html b/index.html index 5c48781..ffd44c0 100644 --- a/index.html +++ b/index.html @@ -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; @@ -1451,6 +1489,12 @@

+
+
+ +
+
+

About this resource

@@ -2511,6 +2555,66 @@

Browse all 54 scripts →

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 = ''; + if (canOpen) { + previewFrame.setAttribute('role', 'button'); + previewFrame.setAttribute('tabindex', '0'); + previewFrame.setAttribute('aria-label', `Open ${resource.name} in the interactive viewer`); + previewBadge.innerHTML = `${eyeSvg}Open interactive view`; + 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')); diff --git a/tools/catalog-build/index.js b/tools/catalog-build/index.js index 65af170..52b2648 100644 --- a/tools/catalog-build/index.js +++ b/tools/catalog-build/index.js @@ -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'); @@ -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, @@ -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,