diff --git a/CHANGELOG.md b/CHANGELOG.md index e81032f8..9173a3af 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -53,6 +53,7 @@ and verification for each change is in the - **JSON:** Breaking: accessibility statements no longer claim conformance. `interpretation.accessibility_claim` gives result, scope and open manual review; `screen_reader.bfsg_compliance.verdict` is `no_violations_found`/`violations_found`/`incomplete` instead of `compliant`/`non_compliant`; both state `conformance` and `legal_applicability` as `not_determined`. - **Reports:** Risk rationales name controls without an accessible name and controls not operable by keyboard separately (`risk.keyboard_blocking_issues`), and neither they nor the verdict reasons state any longer that nameless controls cannot be operated or that tasks cannot be completed. - **Reports:** The expected impact of an action states the occurrences it addresses instead of "Fixes N occurrences; expected score impact: high", which no simulation backed. +- **Project site:** The start page now carries a release teaser, an FAQ and an overview of the common CLI flags (theme 0.11.1). - **Reports:** "Source quality" is shown as "Technical preparation for machine processing", an unweighted indicator. Its score combines on-page consistency and publisher/security signals; structural signals (word count, headings, structured data, metadata) are listed as observed values without a rating. `source_quality.substance.score` is deprecated and removed in 2.0. - **Reports:** Consistency compares what a page can contradict on its own — declared language vs. `og:locale`, `` vs. `og:title`, structured data vs. visible content — and weighs only the checks that applied. - **Reports:** The trust signal lists contact, imprint and privacy links that were not detected instead of reusing the UX trust score. diff --git a/site/src/pages/index.astro b/site/src/pages/index.astro index a8c00eed..0607b5c6 100644 --- a/site/src/pages/index.astro +++ b/site/src/pages/index.astro @@ -1,20 +1,33 @@ --- +import { readFile } from 'node:fs/promises'; import { Badge, Badges, BuildOutput, CodeBlock, Facts, + Faq, Features, Hero, Install, + Keymap, Quickstart, + ReleaseHighlights, Terminal, } from '@casoon/pages-theme/components'; import Base from '@casoon/pages-theme/layouts/Base.astro'; import { url } from '@casoon/pages-theme/url'; +import config from 'virtual:casoon-pages/config'; +import { parseChangelog } from '@casoon/pages-theme/lib/changelog'; import { castDemo, fixture } from '../showcase'; +// Skip "Unreleased": it has no version to show as "New in v…" yet. +const latestRelease = config.changelog + ? parseChangelog(await readFile(config.changelog, 'utf8')).find( + (r) => r.version.toLowerCase() !== 'unreleased' + ) + : undefined; + const quickstart = `# check which browser auditmysite will use auditmysite browser detect @@ -124,6 +137,37 @@ auditmysite --sitemap https://example.com/sitemap.xml --max-pages 20`; <Terminal ansi={fixture('plan.txt')} /> </BuildOutput> + <Keymap + heading="Common flags" + groups={[ + { + heading: 'What to audit', + items: [ + { label: 'Sitemap, as one batch report', keys: ['-s', '<sitemap-url>'] }, + { label: 'URL list from a file', keys: ['-u', '<file>'] }, + { label: 'Same-domain crawl from the URL', keys: ['--crawl'] }, + { label: 'Cap the number of pages', keys: ['-m', '<num>'] }, + ], + }, + { + heading: 'What to check', + items: [ + { label: 'Add performance, SEO, security, mobile', keys: ['--full'] }, + { label: 'WCAG level (AAA is incomplete)', keys: ['-l', 'A|AA|AAA'] }, + ], + }, + { + heading: 'What to write', + items: [ + { label: 'Report format', keys: ['-f', 'json|pdf|table'] }, + { label: 'Output file', keys: ['-o', '<file>'] }, + { label: 'PDF language', keys: ['--lang', 'de|en'] }, + { label: 'One JSON per page plus findings.jsonl', keys: ['--technician'] }, + ], + }, + ]} + /> + <Quickstart lead="Install the binary, make sure a Chrome is available, point it at a URL. The documentation covers batch modes, reports and CI." steps={[ @@ -190,6 +234,33 @@ auditmysite --sitemap https://example.com/sitemap.xml --max-pages 20`; </dd> </dl> </section> + + {latestRelease && <ReleaseHighlights release={latestRelease} />} + + <Faq + items={[ + { + q: 'Does auditmysite download or install a browser automatically?', + a: 'No. It requires Chrome, Chromium, or a managed browser to already be present and exits with an error if none is found. auditmysite browser install fetches Chrome for Testing, but only when you run it yourself.', + }, + { + q: 'Is the PDF report an accessibility statement I can publish?', + a: 'No. The EN 301 549 appendix and the JSON findings are a technical building block for a statement a person writes; the report explicitly does not claim conformance under BFSG or BITV 2.0.', + }, + { + q: 'Does 100% automated pass mean the page is fully WCAG 2.2 AA compliant?', + a: "No. Of the 55 WCAG 2.2 A/AA success criteria, only 30 are counted as automated, and a criterion counts there only if a rule can actually report a violation. The other 25 are listed as requiring manual review, never silently marked as passed.", + }, + { + q: 'Where do the WCAG rule ids and checks come from?', + a: 'From the barrierlab crates (a11y-rules, a11y-dom, accname, html-conform, and related crates), a shared Rust monorepo also used by astro-post-audit and LiveAudit, so the same rule id means the same thing across all three tools. Rule ids stay stable across auditmysite releases, with renames documented when a collision is fixed.', + }, + { + q: 'Can it audit more than one page at a time?', + a: 'Yes. Point it at a sitemap, a URL file, or a seed URL with --crawl, and it writes one aggregated batch report with averages, ranking, and recurring issues across pages. --technician switches that same batch run to one JSON file per page plus a flat findings.jsonl instead.', + }, + ]} + /> </Base> <style> @@ -233,6 +304,9 @@ auditmysite --sitemap https://example.com/sitemap.xml --max-pages 20`; font-size: 0.9rem; margin-top: 8px; } + .demo figcaption a { + text-decoration: underline; + } .limits .lead { color: var(--muted); margin: 0 0 20px; diff --git a/site/vendor/pages-theme/README.md b/site/vendor/pages-theme/README.md index 0aed3a4f..c6261ccd 100644 --- a/site/vendor/pages-theme/README.md +++ b/site/vendor/pages-theme/README.md @@ -123,13 +123,19 @@ Import from `@casoon/pages-theme/components` in `.astro` files; available direct | Component | Use | | --- | --- | | `Hero` | Title, lead, default slot (install, badges), `output` slot (real output); `layout`, `command` and `tone` pick the variant | +| `AnchorGrid` | Card grid under the hero, each linking to a section further down or out to a doc | | `Install` | Install command per ecosystem, CSS-only tabs, copy button; long commands wrap at spaces and after a `/` | | `Badge`, `Badges` | Static badges (no external badge services) | -| `Facts` | Verifiable key figures | -| `Features` | 3–5 core points | +| `Facts` | Verifiable key figures; `layout="grid"` (tiles) or `"list"` (spec sheet) | +| `Features` | Core points; `layout="numbered"` (3–5 items) or `"grid"` (an icon-tile catalog) | +| `Spotlight` | Repeatable two-column feature section — eyebrow, title, lead, bullets, links, beside a media slot | +| `Integrations` | Grid of what the project plugs into: icon tile, name, category | | `FileSet` | The files a run produced: `tabs`, `grid` or `rail` view | | `BuildOutput` | One real build output on the start page | | `Quickstart` | Steps, docs link, code slot | +| `Keymap` | Keyboard shortcuts or CLI flags, grouped cards of label + `<kbd>` keys | +| `ReleaseHighlights` | Teaser of one changelog release on the start page | +| `Faq` | Native `<details>` accordion, no JS | | `CodeBlock` | Build-time highlighting (Shiki, theme-aware), copy button | | `Terminal` | `ansi` prop (converted at build time) or pre-rendered HTML as slot | | `Callout` | `note`, `tip`, `caution`, `deprecated` | diff --git a/site/vendor/pages-theme/SOURCE b/site/vendor/pages-theme/SOURCE index d5719e70..5fcc9c16 100644 --- a/site/vendor/pages-theme/SOURCE +++ b/site/vendor/pages-theme/SOURCE @@ -1,6 +1,6 @@ -@casoon/pages-theme 0.8.3 -source: gh-pages-template@8287402 -synced: 2026-10-26 +@casoon/pages-theme 0.11.1 +source: gh-pages-template@717e8fe +synced: 2026-10-27 Vendored copy. Do not edit here: change the theme in gh-pages-template, then run scripts/sync-theme.sh <this repo>. diff --git a/site/vendor/pages-theme/components/AnchorGrid.astro b/site/vendor/pages-theme/components/AnchorGrid.astro new file mode 100644 index 00000000..87010602 --- /dev/null +++ b/site/vendor/pages-theme/components/AnchorGrid.astro @@ -0,0 +1,160 @@ +--- +/** + * Cards right under the hero, each jumping to a section further down the page (or out, to a + * doc). For projects with more than one entry point or audience — "what's in it for you, + * specifically" before the long-form pitch. The solid label pill reuses the Hero `step` pill; + * the tinted card background reuses the brand/accent-soft tokens Callout already carries. + */ +import config from 'virtual:casoon-pages/config'; +import { t } from '../lib/i18n'; + +interface Props { + items: { + label: string; + title: string; + body: string; + href: string; + tone?: 'accent' | 'brand'; + }[]; + /** Section label above the grid. Omitted: the grid carries no heading of its own. */ + heading?: string; +} + +const TONES: Array<'accent' | 'brand'> = ['accent', 'brand']; +const { items, heading } = Astro.props; +--- + +<nav class="anchor-grid" aria-label={heading ?? t(config.lang).overview}> + {heading && <p class="cp-label">{heading}</p>} + <ul> + { + items.map((item, i) => ( + <li> + <a class={item.tone ?? TONES[i % TONES.length]} href={item.href}> + <span class="label">{item.label}</span> + <h3>{item.title}</h3> + <p>{item.body}</p> + <span class="go" aria-hidden="true"> + → + </span> + </a> + </li> + )) + } + </ul> +</nav> + +<style> + .anchor-grid { + max-width: var(--max); + margin: 0 auto; + padding: 0 24px 80px; + } + ul { + display: grid; + grid-template-columns: repeat(auto-fit, minmax(min(260px, 100%), 1fr)); + gap: 18px; + margin: 0; + padding: 0; + list-style: none; + } + a { + position: relative; + display: flex; + flex-direction: column; + gap: 12px; + height: 100%; + padding: 26px 60px 52px 26px; + border: 1px solid var(--border); + border-radius: 18px; + color: var(--text); + transition: + border-color 0.18s ease, + transform 0.18s ease, + box-shadow 0.18s ease; + } + a.accent { + background: var(--accent-soft); + } + a.brand { + background: var(--brand-soft); + } + a:hover { + box-shadow: var(--shadow); + transform: translateY(-3px); + } + a.accent:hover { + border-color: var(--accent); + } + a.brand:hover { + border-color: var(--brand); + } + .label { + display: inline-flex; + align-self: flex-start; + padding: 5px 13px; + border-radius: 999px; + font-family: var(--font-mono); + font-size: 11.5px; + letter-spacing: 0.04em; + text-transform: uppercase; + white-space: nowrap; + /* The page ground, not white: on the light brand/accent of the dark theme white falls below + 4.5:1 — same reasoning as the Hero step pill. */ + color: var(--bg); + } + .accent .label { + background: var(--accent); + } + .brand .label { + background: var(--brand); + } + h3 { + margin: 0; + color: var(--heading); + font-size: 18px; + font-weight: 600; + letter-spacing: -0.01em; + line-height: 1.25; + } + p { + margin: 0; + font-size: 14px; + line-height: 1.55; + } + .go { + position: absolute; + right: 22px; + bottom: 22px; + display: flex; + align-items: center; + justify-content: center; + width: 30px; + height: 30px; + border: 1px solid var(--border); + border-radius: 999px; + background: var(--surface); + color: var(--muted); + font-size: 15px; + line-height: 1; + transition: + color 0.18s ease, + border-color 0.18s ease, + background 0.18s ease, + transform 0.18s ease; + } + a.accent:hover .go, + a.accent:focus-visible .go { + border-color: var(--accent); + background: var(--accent); + color: var(--bg); + transform: translateX(2px); + } + a.brand:hover .go, + a.brand:focus-visible .go { + border-color: var(--brand); + background: var(--brand); + color: var(--bg); + transform: translateX(2px); + } +</style> diff --git a/site/vendor/pages-theme/components/Facts.astro b/site/vendor/pages-theme/components/Facts.astro index 47bd9979..f4baee34 100644 --- a/site/vendor/pages-theme/components/Facts.astro +++ b/site/vendor/pages-theme/components/Facts.astro @@ -1,16 +1,22 @@ --- -/** Verifiable key figures. Real numbers only. */ +/** + * Verifiable key figures. Real numbers only. + * `layout="grid"` (default): a tile per fact, the value large. `layout="list"`: one card, a + * label/value row per fact — for a spec sheet (license, runtime, platforms) rather than a + * dashboard of numbers. + */ import config from 'virtual:casoon-pages/config'; import { t } from '../lib/i18n'; interface Props { items: { value: string; label: string }[]; + layout?: 'grid' | 'list'; } -const { items } = Astro.props; +const { items, layout = 'grid' } = Astro.props; --- -<section class="facts" aria-label={t(config.lang).keyFacts}> +<section class:list={['facts', `is-${layout}`]} aria-label={t(config.lang).keyFacts}> <dl> { items.map((f) => ( @@ -30,32 +36,66 @@ const { items } = Astro.props; padding: 0 24px 72px; } dl { + margin: 0; + } + .is-grid dl { display: grid; grid-template-columns: repeat(auto-fit, minmax(190px, 1fr)); gap: 1px; - margin: 0; overflow: hidden; border: 1px solid var(--border); border-radius: 16px; background: var(--border); } - div { + .is-grid div { display: flex; flex-direction: column-reverse; justify-content: flex-end; padding: 26px 22px; background: var(--surface); } - dd { + .is-grid dd { margin: 0 0 6px; color: var(--heading); font-family: var(--font-mono); font-size: 26px; letter-spacing: -0.02em; } - dt { + .is-grid dt { color: var(--muted); font-size: 13.5px; line-height: 1.5; } + + /* Spec sheet: one card, a label/value row per fact. */ + .is-list dl { + max-width: 440px; + margin-inline: auto; + padding: 4px 24px; + border: 1px solid var(--border); + border-radius: 16px; + background: var(--surface); + box-shadow: var(--shadow); + } + .is-list div { + display: flex; + align-items: baseline; + justify-content: space-between; + gap: 20px; + padding: 15px 0; + border-top: 1px solid var(--border); + } + .is-list div:first-child { + border-top: 0; + } + .is-list dt { + color: var(--muted); + font-size: 14.5px; + } + .is-list dd { + margin: 0; + color: var(--heading); + font-weight: 600; + text-align: right; + } </style> diff --git a/site/vendor/pages-theme/components/Faq.astro b/site/vendor/pages-theme/components/Faq.astro new file mode 100644 index 00000000..f4482c77 --- /dev/null +++ b/site/vendor/pages-theme/components/Faq.astro @@ -0,0 +1,87 @@ +--- +/** Native <details> accordion, no JS. Entries stay independent (several may be open at once). */ +import config from 'virtual:casoon-pages/config'; +import { t } from '../lib/i18n'; + +interface Props { + items: { q: string; a: string }[]; + heading?: string; +} + +const s = t(config.lang); +const { items, heading = s.faqHeading } = Astro.props; +--- + +<section class="faq"> + <h2>{heading}</h2> + <div class="list"> + { + items.map((item) => ( + <details> + <summary> + <span>{item.q}</span> + <span class="icon" aria-hidden="true"> + + + </span> + </summary> + <p>{item.a}</p> + </details> + )) + } + </div> +</section> + +<style> + .faq { + max-width: var(--max); + margin: 0 auto; + padding: 0 24px 80px; + } + h2 { + margin: 0 0 8px; + color: var(--heading); + font-size: 30px; + font-weight: 700; + letter-spacing: -0.02em; + } + .list { + border-top: 1px solid var(--border); + } + details { + border-bottom: 1px solid var(--border); + } + summary { + display: flex; + align-items: center; + justify-content: space-between; + gap: 20px; + padding: 20px 0; + color: var(--heading); + font-weight: 600; + cursor: pointer; + list-style: none; + } + summary::-webkit-details-marker { + display: none; + } + summary:hover { + color: var(--accent); + } + .icon { + flex-shrink: 0; + color: var(--muted); + font-size: 20px; + font-weight: 400; + line-height: 1; + transition: transform 0.15s ease; + } + details[open] .icon { + transform: rotate(45deg); + } + details p { + margin: 0 0 22px; + max-width: 68ch; + font-size: 15px; + line-height: 1.6; + } +</style> diff --git a/site/vendor/pages-theme/components/Features.astro b/site/vendor/pages-theme/components/Features.astro index 7af0585e..457e62d9 100644 --- a/site/vendor/pages-theme/components/Features.astro +++ b/site/vendor/pages-theme/components/Features.astro @@ -1,25 +1,38 @@ --- -/** 3–5 core points, each backed by something checkable. */ +/** + * Core points, each backed by something checkable. + * `layout="numbered"` (default, 3–5 items): a numbered list. `layout="grid"`: a denser catalog + * for more items — an icon tile per entry (`icon`, a short mono label) instead of the index. + */ import config from 'virtual:casoon-pages/config'; import { t } from '../lib/i18n'; interface Props { - items: { title: string; body: string }[]; + items: { title: string; body: string; icon?: string }[]; heading?: string; + layout?: 'numbered' | 'grid'; } -const { items, heading = t(config.lang).featuresHeading } = Astro.props; +const { items, heading = t(config.lang).featuresHeading, layout = 'numbered' } = Astro.props; --- -<section class="features"> +<section class:list={['features', `is-${layout}`]}> <h2 class="cp-label">{heading}</h2> - <ol> + <ul> { items.map((ft, i) => ( <li> - <span class="n" aria-hidden="true"> - {String(i + 1).padStart(2, '0')} - </span> + {layout === 'numbered' ? ( + <span class="n" aria-hidden="true"> + {String(i + 1).padStart(2, '0')} + </span> + ) : ( + ft.icon && ( + <span class="icon" aria-hidden="true"> + {ft.icon} + </span> + ) + )} <div> <h3>{ft.title}</h3> <p>{ft.body}</p> @@ -27,7 +40,7 @@ const { items, heading = t(config.lang).featuresHeading } = Astro.props; </li> )) } - </ol> + </ul> </section> <style> @@ -36,15 +49,17 @@ const { items, heading = t(config.lang).featuresHeading } = Astro.props; margin: 0 auto; padding: 0 24px 80px; } - ol { - display: grid; - grid-template-columns: repeat(auto-fit, minmax(min(300px, 100%), 1fr)); - gap: 28px 48px; + ul { margin: 0; padding: 0; list-style: none; } - li { + .is-numbered ul { + display: grid; + grid-template-columns: repeat(auto-fit, minmax(min(300px, 100%), 1fr)); + gap: 28px 48px; + } + .is-numbered li { display: flex; gap: 18px; padding-top: 20px; @@ -68,4 +83,32 @@ const { items, heading = t(config.lang).featuresHeading } = Astro.props; font-size: 14.5px; line-height: 1.6; } + + /* Denser catalog: an icon-tile card per entry, no numbering. */ + .is-grid ul { + display: grid; + grid-template-columns: repeat(auto-fit, minmax(min(240px, 100%), 1fr)); + gap: 16px; + } + .is-grid li { + display: flex; + flex-direction: column; + gap: 14px; + padding: 22px; + border-radius: 16px; + background: var(--bg-deep); + } + .is-grid .icon { + display: flex; + align-items: center; + justify-content: center; + width: 36px; + height: 36px; + border-radius: 10px; + background: var(--surface); + color: var(--heading); + font-family: var(--font-mono); + font-size: 13px; + font-weight: 600; + } </style> diff --git a/site/vendor/pages-theme/components/Footer.astro b/site/vendor/pages-theme/components/Footer.astro index 4ee1b331..0bb0ac5f 100644 --- a/site/vendor/pages-theme/components/Footer.astro +++ b/site/vendor/pages-theme/components/Footer.astro @@ -33,7 +33,8 @@ const columns = [ ...(config.catalog ? [{ label: s.catalogEntry(config.name), href: catalogUrl(config.lang, config.catalog) }] : []), - { label: s.allProjects, href: catalogUrl(config.lang) }, + // Only a public GitHub project is actually part of the open-source catalog. + ...(config.repo ? [{ label: s.allProjects, href: catalogUrl(config.lang) }] : []), ], }, ]; @@ -46,7 +47,8 @@ const columns = [ <img class="logo-light" src={logo.src} width="150" height="41" alt="CASOON" /> <img class="logo-dark" src={logoDark.src} width="150" height="41" alt="CASOON" /> </a> - <p>Ein Projekt von CASOON · Jörn Seidel</p> + <p>{config.description}</p> + <p class="by">{s.footerBy} · Jörn Seidel</p> </div> { columns.filter((col) => col.links.length > 0).map((col) => ( @@ -66,8 +68,8 @@ const columns = [ <div class="bottom"> <span>{config.license && `${s.license(config.license)} · `}© {new Date().getFullYear()} Jörn Seidel</span> <span class="legal"> - <a href={IMPRESSUM}>Impressum</a> - <a href={DATENSCHUTZ}>Datenschutz</a> + <a href={IMPRESSUM}>{s.imprint}</a> + <a href={DATENSCHUTZ}>{s.privacyPolicy}</a> </span> </div> </footer> @@ -114,6 +116,11 @@ const columns = [ } p { margin: 0 0 6px; + max-width: 32ch; + } + .by { + color: var(--muted); + font-size: 12.5px; } .title { margin-bottom: 12px; diff --git a/site/vendor/pages-theme/components/Integrations.astro b/site/vendor/pages-theme/components/Integrations.astro new file mode 100644 index 00000000..19bf0821 --- /dev/null +++ b/site/vendor/pages-theme/components/Integrations.astro @@ -0,0 +1,135 @@ +--- +/** + * A grid of things the project plugs into — registries, CI providers, editors. `icon` is a + * short mono label (an abbreviation, not a logo image: no external badge or icon service, same + * reasoning as `Badge`). `more`, given, appends a dashed placeholder card. + */ +import config from 'virtual:casoon-pages/config'; +import { t } from '../lib/i18n'; + +interface Props { + items: { name: string; category: string; icon: string; href?: string }[]; + heading?: string; + /** Label for a trailing "more coming" placeholder card. Omitted: no placeholder card. */ + more?: string; +} + +const { items, heading, more } = Astro.props; +const s = t(config.lang); +--- + +<section class="integrations" aria-label={heading ?? s.overview}> + {heading && <p class="cp-label">{heading}</p>} + <ul> + { + items.map((item) => ( + <li> + {item.href ? ( + <a href={item.href}> + <span class="icon" aria-hidden="true"> + {item.icon} + </span> + <p class="name">{item.name}</p> + <p class="category">{item.category}</p> + </a> + ) : ( + <div> + <span class="icon" aria-hidden="true"> + {item.icon} + </span> + <p class="name">{item.name}</p> + <p class="category">{item.category}</p> + </div> + )} + </li> + )) + } + { + more && ( + <li class="more"> + <span class="icon plus" aria-hidden="true"> + + + </span> + <p class="name">{more}</p> + </li> + ) + } + </ul> +</section> + +<style> + .integrations { + max-width: var(--max); + margin: 0 auto; + padding: 0 24px 80px; + } + ul { + display: grid; + grid-template-columns: repeat(auto-fit, minmax(140px, 1fr)); + gap: 14px; + margin: 0; + padding: 0; + list-style: none; + } + a, + div { + display: flex; + flex-direction: column; + align-items: center; + gap: 10px; + height: 100%; + padding: 24px 16px; + border: 1px solid var(--border); + border-radius: 16px; + background: var(--surface); + color: var(--text); + text-align: center; + } + a { + transition: + border-color 0.15s ease, + transform 0.15s ease; + } + a:hover { + border-color: var(--border-strong); + transform: translateY(-2px); + } + .icon { + display: flex; + align-items: center; + justify-content: center; + width: 44px; + height: 44px; + border-radius: 12px; + background: var(--bg-deep); + color: var(--heading); + font-family: var(--font-mono); + font-size: 13px; + font-weight: 600; + } + .name { + margin: 0; + color: var(--heading); + font-weight: 600; + font-size: 14.5px; + } + .category { + margin: 0; + color: var(--muted); + font-size: 13px; + } + .more { + border-style: dashed; + color: var(--muted); + } + .plus { + background: none; + border: 1px dashed var(--border-strong); + font-size: 18px; + font-weight: 400; + } + .more .name { + color: var(--muted); + font-weight: 500; + } +</style> diff --git a/site/vendor/pages-theme/components/Keymap.astro b/site/vendor/pages-theme/components/Keymap.astro new file mode 100644 index 00000000..bacb2ef9 --- /dev/null +++ b/site/vendor/pages-theme/components/Keymap.astro @@ -0,0 +1,102 @@ +--- +/** + * Keyboard shortcuts or CLI flags, grouped into cards: a label and one or more `<kbd>` keys per + * row. `keys` is a key sequence (chorded) — `['ctrl', 'k']` renders as two adjacent keycaps. + */ +interface Props { + groups: { + heading: string; + items: { label: string; keys: string[] }[]; + }[]; + /** Section heading. Omitted: the section carries no heading of its own. */ + heading?: string; +} + +const { groups, heading } = Astro.props; +--- + +<section class="keymap"> + {heading && <h2 class="cp-label">{heading}</h2>} + <div class="groups"> + { + groups.map((group) => ( + <div class="group"> + <p class="heading">{group.heading}</p> + <ul> + {group.items.map((item) => ( + <li> + <span class="label">{item.label}</span> + <span class="keys"> + {item.keys.map((key) => ( + <kbd>{key}</kbd> + ))} + </span> + </li> + ))} + </ul> + </div> + )) + } + </div> +</section> + +<style> + .keymap { + max-width: var(--max); + margin: 0 auto; + padding: 0 24px 80px; + } + .keymap > h2 { + margin: 0 0 20px; + } + .groups { + display: grid; + grid-template-columns: repeat(auto-fit, minmax(min(260px, 100%), 1fr)); + gap: 18px; + } + .group { + padding: 20px 22px; + border-radius: 16px; + background: var(--bg-deep); + } + .heading { + margin: 0 0 14px; + color: var(--muted); + font-size: 13.5px; + font-weight: 600; + } + ul { + margin: 0; + padding: 0; + list-style: none; + } + li { + display: flex; + align-items: center; + justify-content: space-between; + gap: 16px; + padding: 11px 0; + border-top: 1px solid var(--border); + font-size: 14px; + } + li:first-child { + border-top: 0; + } + .keys { + display: flex; + flex-shrink: 0; + gap: 4px; + } + kbd { + min-width: 22px; + padding: 3px 6px; + border: 1px solid var(--border-strong); + border-radius: 6px; + background: var(--surface); + box-shadow: 0 1px 0 var(--border-strong); + color: var(--heading); + font-family: var(--font-mono); + font-size: 12px; + text-align: center; + } +</style> diff --git a/site/vendor/pages-theme/components/ReleaseHighlights.astro b/site/vendor/pages-theme/components/ReleaseHighlights.astro new file mode 100644 index 00000000..cfada6e0 --- /dev/null +++ b/site/vendor/pages-theme/components/ReleaseHighlights.astro @@ -0,0 +1,110 @@ +--- +/** + * Teaser of one release's changes on the start page, from the same `Release` the changelog + * route renders — parse CHANGELOG.md once with `parseChangelog`. Pass the first release whose + * version isn't "Unreleased" (`releases.find((r) => r.version.toLowerCase() !== 'unreleased')`), + * not `releases[0]`: an `## [Unreleased]` section with entries already in it would otherwise + * show up here as "New in vUnreleased". Never maintained twice: the entry text is the + * changelog entry. + */ +import config from 'virtual:casoon-pages/config'; +import type { Release } from '../lib/changelog'; +import { t } from '../lib/i18n'; +import { url } from '../lib/url'; + +interface Props { + release: Release; + heading?: string; + /** Cap on how many of the release's entries to show. Default 6. */ + max?: number; +} + +const s = t(config.lang); +const { release, heading = s.newIn(release.version), max = 6 } = Astro.props; +const tone = (kind: string) => + ['breaking', 'removed', 'security'].includes(kind) + ? 'brand' + : kind === 'fixed' + ? 'accent' + : 'muted'; +const changes = release.changes.slice(0, max); +--- + +<section class="release-highlights"> + <div class="head"> + <h2 class="cp-label">{heading}</h2> + {config.changelog && <a href={url('changelog/')}>{s.changelog} →</a>} + </div> + <ul> + { + changes.map((change) => ( + <li> + <span class:list={['dot', tone(change.kind)]} aria-hidden="true" /> + <span set:html={change.html} /> + </li> + )) + } + </ul> +</section> + +<style> + .release-highlights { + max-width: var(--max); + margin: 0 auto; + padding: 0 24px 80px; + } + .head { + display: flex; + align-items: baseline; + justify-content: space-between; + gap: 16px; + margin-bottom: 20px; + } + .head h2 { + margin: 0; + } + .head a { + font-size: 14px; + } + ul { + display: grid; + grid-template-columns: repeat(auto-fit, minmax(min(300px, 100%), 1fr)); + gap: 20px 40px; + margin: 0; + padding: 0; + list-style: none; + } + li { + display: flex; + gap: 12px; + padding-top: 18px; + border-top: 1px solid var(--border); + font-size: 14.5px; + line-height: 1.55; + } + .dot { + flex-shrink: 0; + width: 7px; + height: 7px; + margin-top: 7px; + border-radius: 50%; + } + .dot.muted { + background: var(--muted); + } + .dot.accent { + background: var(--accent); + } + .dot.brand { + background: var(--brand); + } + li :global(strong) { + color: var(--heading); + } + li :global(code) { + padding: 1px 5px; + border-radius: 5px; + background: var(--code-bg); + font-size: 0.9em; + } +</style> diff --git a/site/vendor/pages-theme/components/Spotlight.astro b/site/vendor/pages-theme/components/Spotlight.astro new file mode 100644 index 00000000..93ef53fc --- /dev/null +++ b/site/vendor/pages-theme/components/Spotlight.astro @@ -0,0 +1,154 @@ +--- +/** + * A repeatable two-column feature section further down the page (unlike `Hero`, which is + * singular): eyebrow, title, lead, an optional bullet list and link row, beside a `media` slot + * (a `Terminal`, `CodeBlock`, screenshot — whatever shows the feature). `reverse` swaps sides, + * for alternating sections down a page. `lead` and `points` accept the changelog's inline + * Markdown subset (`` `code` ``, `**bold**`, `[text](url)`), reused rather than re-parsed. + */ +import { inlineMarkdown } from '../lib/changelog'; + +interface Props { + eyebrow?: string; + title: string; + lead: string; + points?: string[]; + links?: { label: string; href: string }[]; + reverse?: boolean; +} + +const { eyebrow, title, lead, points, links, reverse = false } = Astro.props; +--- + +<section class:list={['spotlight', reverse && 'reverse']}> + <div class="text"> + {eyebrow && <p class="eyebrow">{eyebrow}</p>} + <h2>{title}</h2> + <p class="lead" set:html={inlineMarkdown(lead)} /> + { + points && points.length > 0 && ( + <ul class="points"> + {points.map((point) => ( + <li set:html={inlineMarkdown(point)} /> + ))} + </ul> + ) + } + { + links && links.length > 0 && ( + <div class="links"> + {links.map((link) => ( + <a href={link.href}>{link.label} →</a> + ))} + </div> + ) + } + </div> + <div class="media"> + <slot /> + </div> +</section> + +<style> + .spotlight { + display: grid; + gap: 44px; + align-items: center; + max-width: var(--max); + margin: 0 auto; + padding: 0 24px 80px; + } + @media (min-width: 900px) { + .spotlight { + grid-template-columns: minmax(0, 1fr) minmax(0, 1.05fr); + gap: 56px; + } + .spotlight.reverse { + grid-template-columns: minmax(0, 1.05fr) minmax(0, 1fr); + } + .spotlight.reverse .text { + order: 1; + } + .spotlight.reverse .media { + order: 2; + } + } + .eyebrow { + display: flex; + align-items: center; + gap: 8px; + margin: 0 0 16px; + color: var(--muted); + font-family: var(--font-mono); + font-size: 12px; + letter-spacing: 0.09em; + text-transform: uppercase; + } + .eyebrow::before { + content: ''; + width: 6px; + height: 6px; + border-radius: 50%; + background: var(--brand); + } + h2 { + margin: 0 0 16px; + color: var(--heading); + font-size: clamp(26px, 3vw, 34px); + font-weight: 600; + letter-spacing: -0.02em; + line-height: 1.1; + } + .lead { + margin: 0; + font-size: 16.5px; + line-height: 1.65; + } + .lead :global(strong) { + color: var(--heading); + } + .lead :global(code) { + padding: 1px 5px; + border-radius: 5px; + background: var(--code-bg); + font-size: 0.9em; + } + .points { + margin: 22px 0 0; + padding: 0; + list-style: none; + } + .points li { + position: relative; + padding-left: 20px; + margin-bottom: 12px; + font-size: 14.5px; + line-height: 1.6; + } + .points li::before { + content: '—'; + position: absolute; + left: 0; + color: var(--muted); + } + .points :global(strong) { + color: var(--heading); + } + .links { + display: flex; + flex-wrap: wrap; + gap: 18px 26px; + margin-top: 24px; + } + .links a { + color: var(--heading); + font-weight: 600; + font-size: 14.5px; + } + .links a:hover { + color: var(--accent); + } + .media { + min-width: 0; + } +</style> diff --git a/site/vendor/pages-theme/components/index.ts b/site/vendor/pages-theme/components/index.ts index c310f912..1800f6e5 100644 --- a/site/vendor/pages-theme/components/index.ts +++ b/site/vendor/pages-theme/components/index.ts @@ -1,3 +1,4 @@ +export { default as AnchorGrid } from './AnchorGrid.astro'; export { default as ApiEntry } from './ApiEntry.astro'; export { default as Badge } from './Badge.astro'; export { default as Badges } from './Badges.astro'; @@ -8,11 +9,16 @@ export { default as CodeBlock } from './CodeBlock.astro'; export { default as Demo } from './Demo.astro'; export { default as ExamplePanel } from './ExamplePanel.astro'; export { default as Facts } from './Facts.astro'; +export { default as Faq } from './Faq.astro'; export { default as Features } from './Features.astro'; export { default as FileSet } from './FileSet.astro'; export { default as Hero } from './Hero.astro'; export { default as Install } from './Install.astro'; +export { default as Integrations } from './Integrations.astro'; +export { default as Keymap } from './Keymap.astro'; export { default as Quickstart } from './Quickstart.astro'; +export { default as ReleaseHighlights } from './ReleaseHighlights.astro'; +export { default as Spotlight } from './Spotlight.astro'; export { default as Swatch } from './Swatch.astro'; export { default as Tabs } from './Tabs.astro'; export { default as Terminal } from './Terminal.astro'; diff --git a/site/vendor/pages-theme/lib/i18n.ts b/site/vendor/pages-theme/lib/i18n.ts index 205bb260..7900ae85 100644 --- a/site/vendor/pages-theme/lib/i18n.ts +++ b/site/vendor/pages-theme/lib/i18n.ts @@ -18,6 +18,9 @@ const en = { website: (name: string) => `${name} website`, catalogEntry: (name: string) => `${name} on casoon.dev`, allProjects: 'All open-source projects by CASOON', + footerBy: 'A CASOON project', + imprint: 'Imprint', + privacyPolicy: 'Privacy Policy', license: (id: string) => `${id} License`, docsNav: 'Documentation', breadcrumb: 'Breadcrumb', @@ -34,6 +37,7 @@ const en = { buildOutputHeading: 'Generated at build time', allExamples: 'All examples', featuresHeading: 'What it does', + overview: 'Overview', quickstartTitle: 'Quickstart', quickstartLead: 'From install to a first result. The full walkthrough lives in the documentation.', @@ -57,6 +61,8 @@ const en = { showcaseDescription: (name: string) => `Real input and output of ${name}.`, showcaseLead: (name: string) => `Each example shows real output produced by ${name} itself, from files in the project repository. Input on the left, output on the right.`, + faqHeading: 'Frequently asked questions', + newIn: (version: string) => `New in v${version}`, }; export type Strings = typeof en; @@ -77,6 +83,9 @@ const de: Strings = { website: (name) => `Website von ${name}`, catalogEntry: (name) => `${name} auf casoon.dev`, allProjects: 'Alle Open-Source-Projekte von CASOON', + footerBy: 'Ein Projekt von CASOON', + imprint: 'Impressum', + privacyPolicy: 'Datenschutz', license: (id) => `${id}-Lizenz`, docsNav: 'Dokumentation', breadcrumb: 'Navigationspfad', @@ -93,6 +102,7 @@ const de: Strings = { buildOutputHeading: 'Beim Build erzeugt', allExamples: 'Alle Beispiele', featuresHeading: 'Was es kann', + overview: 'Überblick', quickstartTitle: 'Schnellstart', quickstartLead: 'Von der Installation zum ersten Ergebnis. Die ausführliche Anleitung steht in der Dokumentation.', @@ -117,6 +127,8 @@ const de: Strings = { showcaseDescription: (name) => `Echte Ein- und Ausgaben von ${name}.`, showcaseLead: (name) => `Jedes Beispiel zeigt echte Ausgabe, die ${name} selbst erzeugt hat, aus Dateien im Projekt-Repository. Links die Eingabe, rechts die Ausgabe.`, + faqHeading: 'Häufige Fragen', + newIn: (version) => `Neu in v${version}`, }; export const strings: Record<Lang, Strings> = { en, de }; diff --git a/site/vendor/pages-theme/package.json b/site/vendor/pages-theme/package.json index 5ddef13b..53686774 100644 --- a/site/vendor/pages-theme/package.json +++ b/site/vendor/pages-theme/package.json @@ -1,6 +1,6 @@ { "name": "@casoon/pages-theme", - "version": "0.8.3", + "version": "0.11.1", "private": true, "description": "Shared Astro theme for the GitHub Pages sites of CASOON open-source projects", "type": "module", @@ -15,7 +15,7 @@ "./url": "./lib/url.ts", "./showcase": "./lib/showcase.ts", "./layouts/*": "./layouts/*", - "./lib/*": "./lib/*", + "./lib/*": "./lib/*.ts", "./routes/*": "./routes/*", "./styles/*": "./styles/*" },