diff --git a/scripts/verify.mjs b/scripts/verify.mjs index 4b1e8b7..a9fd401 100644 --- a/scripts/verify.mjs +++ b/scripts/verify.mjs @@ -329,6 +329,14 @@ if ( ) ) throw new Error('missing rules responsive contract'); +if ( + ![ + 'body{min-width:320px;margin:0;color:var(--ink);background:var(--paper);font-family:var(--font-sans)}', + 'max-width:75ch;font-size:var(--step-24)', + 'max-width:75ch;font-size:var(--step-20)', + ].every((token) => builtCss.includes(token)) +) + throw new Error('rules page lost its global font or 4K prose scale'); if ( !['font:700 var(--step-18) var(--font-mono)', 'font:500 var(--step-48) var(--font-mono)'].every( (token) => builtCss.includes(token), diff --git a/src/components/blocks/SiteFooter.astro b/src/components/blocks/SiteFooter.astro index 4c258b2..65761fa 100644 --- a/src/components/blocks/SiteFooter.astro +++ b/src/components/blocks/SiteFooter.astro @@ -47,7 +47,7 @@ const { navLabel = 'Chapter navigation' } = Astro.props; color: var(--ink); border: 1px solid var(--ink); text-decoration: none; - font: var(--step-0) monospace; + font: var(--step-0) var(--font-mono); text-transform: uppercase; } diff --git a/src/components/blocks/TopBar.astro b/src/components/blocks/TopBar.astro index 70372c3..13e3c00 100644 --- a/src/components/blocks/TopBar.astro +++ b/src/components/blocks/TopBar.astro @@ -34,7 +34,7 @@ const { id } = Astro.props; gap: 20px; padding: 24px 0; border-bottom: 1px solid var(--line); - font: 700 var(--step-0) monospace; + font: 700 var(--step-0) var(--font-mono); letter-spacing: 0.08em; text-transform: uppercase; } diff --git a/src/components/islands/RulesInteractive.astro b/src/components/islands/RulesInteractive.astro index b3f09b3..a654011 100644 --- a/src/components/islands/RulesInteractive.astro +++ b/src/components/islands/RulesInteractive.astro @@ -498,15 +498,15 @@ fix(api): scope session query .rules-root :global(*) { box-sizing: border-box; } - .rules-root :global(html) { + :global(html) { scroll-behavior: smooth; } - .rules-root :global(body) { + :global(body) { min-width: 320px; margin: 0; color: var(--ink); background: var(--paper); - font-family: Manrope, Arial, sans-serif; + font-family: var(--font-sans); } .progress { @@ -723,6 +723,10 @@ fix(api): scope session query background: transparent; text-align: left; cursor: pointer; + transition: + color 180ms cubic-bezier(0.2, 0, 0, 1), + background-color 180ms cubic-bezier(0.2, 0, 0, 1), + transform 180ms cubic-bezier(0.2, 0, 0, 1); } .pipeline button span { color: var(--accent); @@ -746,6 +750,9 @@ fix(api): scope session query background: var(--blue); box-shadow: inset 0 -6px var(--gold); } + .pipeline button:hover { + transform: translateY(-2px); + } .pipeline button.active span, .pipeline button.active small { color: var(--gold); @@ -765,6 +772,11 @@ fix(api): scope session query color: var(--paper); background: var(--deep); } + .stage-detail > *, + .skill-detail > * { + /* purpose: make a replaced detail panel legible as a state change */ + animation: rules-detail-in 200ms cubic-bezier(0.2, 0, 0, 1) both; + } .stage-number { color: var(--white-14); font: @@ -849,6 +861,10 @@ fix(api): scope session query background: var(--paper); text-align: left; cursor: pointer; + transition: + color 180ms cubic-bezier(0.2, 0, 0, 1), + background-color 180ms cubic-bezier(0.2, 0, 0, 1), + transform 180ms cubic-bezier(0.2, 0, 0, 1); } .skill-list button strong { font: @@ -864,6 +880,20 @@ fix(api): scope session query background: var(--accent); box-shadow: inset 6px 0 var(--gold); } + .skill-list button:hover { + transform: translateX(3px); + } + + @keyframes rules-detail-in { + from { + opacity: 0.01; + transform: translateY(6px); + } + to { + opacity: 1; + transform: translateY(0); + } + } .skill-list button.active small { color: var(--accent-paper-active); } @@ -1056,6 +1086,19 @@ fix(api): scope session query .hero h1 { font-size: clamp(150px, 7vw, 220px); } + .hero-foot > p { + max-width: 75ch; + font-size: var(--step-24); + } + .intro > p { + max-width: 75ch; + font-size: var(--step-20); + } + .stage-detail p, + .skill-detail p { + max-width: 75ch; + font-size: var(--step-18); + } .pipeline button { min-height: 170px; } @@ -1196,5 +1239,13 @@ fix(api): scope session query .progress span { transition: none; } + .pipeline button, + .skill-list button { + transition: none; + } + .stage-detail > *, + .skill-detail > * { + animation: none; + } } diff --git a/src/components/islands/SkillPackageExplorer.astro b/src/components/islands/SkillPackageExplorer.astro index 4132bfb..6cd4778 100644 --- a/src/components/islands/SkillPackageExplorer.astro +++ b/src/components/islands/SkillPackageExplorer.astro @@ -148,9 +148,7 @@ const packageFiles: PackageFile[] = [ .package-preview > span { margin: 0 0 14px; color: var(--gold); - font: - 700 var(--step-0) / 1.35 ui-monospace, - monospace; + font: 700 var(--step-0) / 1.35 var(--font-mono); letter-spacing: 0.1em; } @@ -188,16 +186,12 @@ const packageFiles: PackageFile[] = [ .package-tree code { min-width: 0; overflow-wrap: anywhere; - font: - 700 13px / 1.4 ui-monospace, - monospace; + font: 700 var(--step-13) / 1.4 var(--font-mono); } .package-tree small { color: var(--muted); - font: - 11px / 1.25 Arial, - sans-serif; + font: var(--step-0) / 1.25 var(--font-sans); text-align: right; } @@ -229,9 +223,7 @@ const packageFiles: PackageFile[] = [ border: 1px solid var(--muted); background: var(--deep); color: var(--line); - font: - 12px / 1.55 ui-monospace, - monospace; + font: var(--step-12) / 1.55 var(--font-mono); } .package-preview.is-swapping { diff --git a/src/layouts/BaseLayout.astro b/src/layouts/BaseLayout.astro index aa6c077..e0de031 100644 --- a/src/layouts/BaseLayout.astro +++ b/src/layouts/BaseLayout.astro @@ -11,6 +11,7 @@ const { title, description, lang = 'en' } = Astro.props; // bundled CSS because the legacy stylesheets @import the same file, so there // is exactly one copy of the faces. See public/fonts/fonts.css. import tokensStylesheet from '../styles/tokens.css?url'; +import baseStylesheet from '../styles/base.css?url'; const fonts = `${import.meta.env.BASE_URL}fonts/fonts.css`.replace('//', '/'); --- @@ -24,6 +25,7 @@ const fonts = `${import.meta.env.BASE_URL}fonts/fonts.css`.replace('//', '/'); + diff --git a/src/pages/index.astro b/src/pages/index.astro index 2f52d3c..85ffb91 100644 --- a/src/pages/index.astro +++ b/src/pages/index.astro @@ -84,9 +84,7 @@ const cards = data.cards ?? []; padding: 12px 16px; background: var(--ink); color: var(--paper); - font: - 700 var(--step-12) ui-monospace, - monospace; + font: 700 var(--step-12) var(--font-mono); letter-spacing: 0.06em; text-decoration: none; text-transform: uppercase; @@ -118,9 +116,7 @@ const cards = data.cards ?? []; .landing-note span { color: var(--red); - font: - 700 var(--step-0) ui-monospace, - monospace; + font: 700 var(--step-0) var(--font-mono); letter-spacing: 0.1em; } diff --git a/src/pages/skills-review.astro b/src/pages/skills-review.astro index 84fc3dc..458f67e 100644 --- a/src/pages/skills-review.astro +++ b/src/pages/skills-review.astro @@ -155,3 +155,88 @@ const serializedCatalog = JSON.stringify(catalog).replace(/ + + diff --git a/src/styles/base.css b/src/styles/base.css new file mode 100644 index 0000000..580dbe0 --- /dev/null +++ b/src/styles/base.css @@ -0,0 +1,39 @@ +/* Typography belongs to the document, not browser defaults. Components may + * opt into the mono token for code and editorial labels. */ +body { + font-family: var(--font-sans); +} + +button, +input, +textarea, +select { + font: inherit; +} + +code, +kbd, +pre, +samp { + font-family: var(--font-mono); +} + +/* Text links read as editorial references. Page-level navigation can still + * deliberately remove the decoration. */ +a { + text-decoration-thickness: 1px; + text-underline-offset: 0.18em; + transition: + color 180ms cubic-bezier(0.2, 0, 0, 1), + text-decoration-color 180ms cubic-bezier(0.2, 0, 0, 1); +} + +a:hover { + text-decoration-color: currentcolor; +} + +@media (prefers-reduced-motion: reduce) { + a { + transition: none; + } +}