diff --git a/.githooks/check-freeze.sh b/.githooks/check-freeze.sh index a5ddd16231..6c39d76895 100755 --- a/.githooks/check-freeze.sh +++ b/.githooks/check-freeze.sh @@ -39,6 +39,19 @@ while IFS= read -r qmd; do [ -n "$qmd" ] || continue freeze_rel="_freeze/${qmd%.qmd}/execute-results/html.json" + # Skip when the freeze file is intentionally removed in this change: the + # page no longer executes code (e.g. converted to the markdown engine), so + # its freeze entry is vestigial and correctly deleted, not stale. + if [ "$MODE" = "commit" ]; then + if git diff --cached --diff-filter=D --name-only 2>/dev/null | grep -Fxq "$freeze_rel"; then + continue + fi + else + if git diff "${REMOTE}..HEAD" --diff-filter=D --name-only 2>/dev/null | grep -Fxq "$freeze_rel"; then + continue + fi + fi + # Read freeze JSON from git objects, not working tree, to avoid false passes # when the freeze file is updated on disk but not yet staged. if [ "$MODE" = "commit" ]; then diff --git a/.github/workflows/preview.yml b/.github/workflows/preview.yml index 3c617d6488..20be8f3b66 100644 --- a/.github/workflows/preview.yml +++ b/.github/workflows/preview.yml @@ -210,9 +210,14 @@ jobs: 'docs/extensions/listings/custom-formats.yml': 'docs/extensions/index.html', 'docs/extensions/listings/revealjs-formats.yml': 'docs/extensions/index.html', 'docs/extensions/listings/revealjs.yml': 'docs/extensions/index.html', - // Gallery data files → gallery index - 'docs/gallery/carousel.yml': 'docs/gallery/index.html', - 'docs/gallery/gallery.yml': 'docs/gallery/index.html', + // Gallery data files → gallery pages + 'docs/gallery/data/featured.yml': 'docs/gallery/index.html', + 'docs/gallery/data/reports-analytics.yml': 'docs/gallery/reports-analytics.html', + 'docs/gallery/data/dashboards-monitoring.yml': 'docs/gallery/dashboards-monitoring.html', + 'docs/gallery/data/research-scholarship.yml': 'docs/gallery/research-scholarship.html', + 'docs/gallery/data/teaching-learning.yml': 'docs/gallery/teaching-learning.html', + 'docs/gallery/data/software-documentation.yml': 'docs/gallery/software-documentation.html', + 'docs/gallery/data/personal-community.yml': 'docs/gallery/personal-community.html', // Guide listing → guide index 'docs/guide/guide.yml': 'docs/guide/index.html', // Download data → download index diff --git a/_freeze/docs/gallery/index/execute-results/html.json b/_freeze/docs/gallery/index/execute-results/html.json deleted file mode 100644 index f9f6bfda06..0000000000 --- a/_freeze/docs/gallery/index/execute-results/html.json +++ /dev/null @@ -1,15 +0,0 @@ -{ - "hash": "838b86bcdf1e147ddbf3ace68d6405b8", - "result": { - "engine": "knitr", - "markdown": "---\npagetitle: \"Gallery\"\nformat:\n html:\n toc: false\nimage: articles/advanced-layout-pdf.png\nsearch: false\nexecute:\n echo: false\nlisting:\n - id: gallery\n template: gallery.ejs\n contents: gallery.yml\ndescription: |\n A gallery of example documents, websites, books, and presentations created with Quarto.\naliases:\n - /docs/dashboards/examples/index.html\n - /docs/dashboards/examples/\n---\n\n::: {#hero-banner .column-screen}\n::: {.grid .column-page}\n::: {.headline .g-col-lg-6 .g-col-12 .g-col-md-12}\n::: {.h1 role=\"heading\" aria-level=\"1\"}\nGallery\n:::\n\n\nQuarto can produce a wide variety of output formats. Here are some examples:\n\n- [Articles & Reports](#articles-reports)\n- [Presentations](#presentations)\n- [Dashboards](#dashboards)\n- [Websites](#websites)\n- [Books](#books)\n- [Interactive Docs](#interactive-docs)\n:::\n\n\n::: {.cell .g-col-lg-6 .g-col-12 .g-col-md-12}\n::: {.cell-output-display}\n\n```{=html}\n
\n
\n\n\n\n\n\n
\n
\n
\n\n\"A\n\n
\n

Articles and reports with Python and R

\n
\n
\n
\n\n\"A\n\n
\n

Create data-driven presentations

\n
\n
\n
\n\n\"A\n\n
\n

Engage readers with interactivity

\n
\n
\n
\n\n\"Documentation\n\n
\n

Publish collections of articles as a website

\n
\n
\n
\n\n\"A\n\n
\n

Create multi-format books

\n
\n
\n
\n\n\n
\n```\n\n:::\n:::\n\n:::\n:::\n\n::: {#gallery .column-page}\n:::\n\n```{=html}\n\n```\n", - "supporting": [], - "filters": [ - "rmarkdown/pagebreak.lua" - ], - "includes": {}, - "engineDependencies": {}, - "preserve": {}, - "postProcess": true - } -} \ No newline at end of file diff --git a/_quarto.yml b/_quarto.yml index b52f28767d..dd66373bca 100644 --- a/_quarto.yml +++ b/_quarto.yml @@ -111,6 +111,19 @@ website: - text: "Tutorial: Authoring" href: docs/get-started/authoring/ + - id: gallery + contents: + - section: "Gallery" + href: docs/gallery/index.qmd + contents: + - docs/gallery/reports-analytics.qmd + - docs/gallery/dashboards-monitoring.qmd + - docs/gallery/research-scholarship.qmd + - docs/gallery/teaching-learning.qmd + - docs/gallery/software-documentation.qmd + - docs/gallery/personal-community.qmd + - docs/gallery/browse.qmd + - id: guide collapse-level: 1 contents: @@ -684,8 +697,8 @@ format: respect-user-color-scheme: true toc: true theme: - light: [cosmo, theme.scss] - dark: [cosmo, theme-dark.scss] + light: [cosmo, theme.scss, docs/gallery/assets/_gallery.scss] + dark: [cosmo, theme-dark.scss, docs/gallery/assets/_gallery.scss] code-copy: true code-overflow: wrap css: styles.css diff --git a/_tools/screenshots/README.md b/_tools/screenshots/README.md new file mode 100644 index 0000000000..2a575e43ca --- /dev/null +++ b/_tools/screenshots/README.md @@ -0,0 +1,44 @@ +# Screenshot and preview tooling + +Scripts to capture and update quarto-web's documentation images and gallery media. + +There are two families. +Doc screenshots come from `manifest.json` through `capture.js`. +Gallery assets come from `docs/gallery/data/*.yml` through the `scripts/capture-*.mjs` scripts. + +Run every command from `_tools/screenshots/`. + +## Which tool makes what + +| Asset | Command | Script | Output | +| ----------------------------------------- | ----------------------------- | ---------------------------------- | ----------------------------------------------- | +| Doc screenshots (PNG, light and dark) | `npm run capture` | `capture.js` (manifest) | `docs/**/images/*.png` | +| Gallery hover previews (webm and mp4) | `npm run capture:previews` | `scripts/capture-previews.mjs` | `docs/gallery/thumbnails/featured/*.{webm,mp4}` | +| Gallery PDF card thumbnails (PNG) | `npm run capture:pdf-thumbs` | `scripts/capture-pdf-thumbs.mjs` | `docs/gallery/thumbnails/**/*.png` | +| Gallery card thumbnails, image-less (PNG) | `npm run capture:card-thumbs` | `scripts/capture-card-thumbs.mjs` | `docs/gallery/thumbnails//.png` | +| axe DevTools console (PNG, manual, macOS) | see script header | `scripts/capture-axe-devtools.mjs` | `docs/output-formats/images/axe-console.png` | + +Only `npm run capture:previews` makes the mp4 and webm hover clips. + +Each `capture:*` gallery command takes `-- --name ` to match a subset. +Each also takes `-- --limit ` to stage the first `n` matches. + +## Prerequisites at a glance + +All commands need Node.js 18+ and this tool's dependencies (`npm install`, then `npx playwright install chromium`). + +`capture:previews` also needs ffmpeg on `PATH` and a full Playwright Chromium; set `PREVIEW_CHROME` to point at it because the headless shell cannot record video. + +`capture:pdf-thumbs` also needs poppler (`pdfinfo`, `pdftoppm`) and ImageMagick (`magick`). + +Full setup steps are in `SETUP.md`. +The gallery prerequisites are detailed in `scripts/PREVIEWS.md`. + +## Where to read next + +`SETUP.md` covers install steps and the still-screenshot workflow. +`scripts/PREVIEWS.md` covers gallery video previews plus PDF and card thumbnails in detail. +`docs/gallery/README.md` covers how to add the gallery entries these thumbnails serve. +`CLAUDE.md` covers the AI-assisted workflow and the `manifest.json` field reference. +`DECISIONS.md` covers why the tooling is built the way it is. +`npm run help` prints the full command list. diff --git a/_tools/screenshots/SETUP.md b/_tools/screenshots/SETUP.md index de12eb4bcd..20c8aced2f 100644 --- a/_tools/screenshots/SETUP.md +++ b/_tools/screenshots/SETUP.md @@ -61,6 +61,12 @@ playwright-cli -s=screenshot screenshot --filename=docs/websites/images/about-jo playwright-cli -s=screenshot close ``` +## Gallery featured video previews + +`scripts/capture-previews.mjs` (`npm run capture:previews`) records the animated preview +clips for featured gallery cards. It is separate from the still-screenshot manifest and +needs ffmpeg plus a full Playwright Chromium. See **`scripts/PREVIEWS.md`** for details. + ## Optional: PNG Compression ```bash diff --git a/_tools/screenshots/package-lock.json b/_tools/screenshots/package-lock.json index ce19c156c7..92534f5647 100644 --- a/_tools/screenshots/package-lock.json +++ b/_tools/screenshots/package-lock.json @@ -6,6 +6,7 @@ "": { "dependencies": { "ajv": "^8.18.0", + "js-yaml": "^5.2.3", "open": "^10.0.0", "playwright": "^1.50.0", "sharp": "^0.33.0" @@ -398,6 +399,12 @@ "url": "https://github.com/sponsors/epoberezkin" } }, + "node_modules/argparse": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", + "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", + "license": "Python-2.0" + }, "node_modules/bundle-name": { "version": "4.1.0", "resolved": "https://registry.npmjs.org/bundle-name/-/bundle-name-4.1.0.tgz", @@ -593,6 +600,28 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/js-yaml": { + "version": "5.2.3", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-5.2.3.tgz", + "integrity": "sha512-n+mUVyUX5bVv7G/G2zyIHOhdxfuU1dY2NOFzTQUWiMUbFss8b57NFlgCCaggU78wSw5KVS9cllzeLyzyR+n5nw==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/puzrin" + }, + { + "type": "github", + "url": "https://github.com/sponsors/nodeca" + } + ], + "license": "MIT", + "dependencies": { + "argparse": "^2.0.1" + }, + "bin": { + "js-yaml": "bin/js-yaml.mjs" + } + }, "node_modules/json-schema-traverse": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", diff --git a/_tools/screenshots/package.json b/_tools/screenshots/package.json index 770a0cbfdf..d14dbc1243 100644 --- a/_tools/screenshots/package.json +++ b/_tools/screenshots/package.json @@ -8,14 +8,21 @@ "serve": "node scripts/serve.js", "compress": "node scripts/compress.js", "capture": "node capture.js", + "capture:previews": "node scripts/capture-previews.mjs", + "capture:pdf-thumbs": "node scripts/capture-pdf-thumbs.mjs", + "capture:card-thumbs": "node scripts/capture-card-thumbs.mjs", "open": "node scripts/open.js", "validate": "node scripts/validate.js", "update-demo": "bash scripts/update-demo.sh" }, "dependencies": { "ajv": "^8.18.0", + "js-yaml": "^5.2.3", "open": "^10.0.0", "playwright": "^1.50.0", "sharp": "^0.33.0" + }, + "allowScripts": { + "sharp@0.33.5": true } } diff --git a/_tools/screenshots/scripts/PREVIEWS.md b/_tools/screenshots/scripts/PREVIEWS.md new file mode 100644 index 0000000000..10885f324a --- /dev/null +++ b/_tools/screenshots/scripts/PREVIEWS.md @@ -0,0 +1,88 @@ +# Gallery featured video previews + +`capture-previews.mjs` generates the short, muted, looping clips that featured +gallery cards play on hover or keyboard focus. The rendering, +interaction, and reduced-motion handling live in the gallery, not here: +`docs/gallery/assets/_featured.ejs.md` and `docs/gallery/assets/_gallery.scss`. + +This script is separate from the still-screenshot manifest (`capture.js`): it +records motion rather than a single frame, and is driven by gallery data rather +than `manifest.json`. + +## How it works + +- Driven by `docs/gallery/data/featured.yml`: every card with both a `live` URL + and a `preview` base path gets a clip. `preview:` is the output path without + extension, e.g. `thumbnails/featured/software-documentation-gribouille`. +- Optional `preview-url:` overrides the page that is recorded when the card's + `live` link is not the best source (e.g. a landing page vs a richer subpage). The + card's `live` link is unchanged; only the capture source differs. +- Output: `.webm` (VP9) and `.mp4` (H.264) under + `docs/gallery/thumbnails/featured/` (animated previews are a featured-only + asset), beside the still PNG (`image:`), which stays the `