From db2824d55c0bb79842164119db4674cc7549fc12 Mon Sep 17 00:00:00 2001 From: 0xLeif Date: Fri, 12 Jun 2026 16:00:08 -0600 Subject: [PATCH] Update: apply CorvidLabs design system to the docs Vendor the brand standard (tokens.css, theme.js, theme-toggle, crow marks, favicon) into site/public/brand and build the docs + examples + shared chrome on it: - Import brand tokens.css globally; alias the site's legacy variable layer onto the brand tokens so every component themes for free in light + dark. Square corners, no purple. - Schibsted Grotesk + Spline Sans Mono via Google Fonts. - Standard sun/moon theme toggle in the header (prefers-color-scheme + pre-paint, no flash); crow head mark inline (currentColor); favicon -> /brand/favicon.svg. - Switch Shiki to the css-variables theme and map --astro-code-token-* onto the brand --code-* syntax palette so spec/code blocks follow light/dark and stay on-brand. - prefers-reduced-motion guards, focus-visible, AA contrast both modes. Marketing landing + language gallery (moving to the hub) left untouched. Co-Authored-By: Claude Fable 5 --- site/astro.config.mjs | 7 +- site/public/brand/VERSION | 1 + site/public/brand/favicon.svg | 13 +++ site/public/brand/logo-mark-reversed.svg | 5 + site/public/brand/logo-mark.svg | 5 + site/public/brand/mascot.svg | 9 ++ site/public/brand/theme-toggle.html | 59 ++++++++++ site/public/brand/theme.js | 71 ++++++++++++ site/public/brand/tokens.css | 139 +++++++++++++++++++++++ site/src/components/Button.astro | 11 +- site/src/components/Callout.astro | 18 +-- site/src/components/Header.astro | 72 +++++++++--- site/src/components/Terminal.astro | 5 +- site/src/layouts/ArticleLayout.astro | 4 +- site/src/layouts/BaseLayout.astro | 19 +++- site/src/layouts/DocsLayout.astro | 4 +- site/src/pages/examples/index.astro | 4 +- site/src/styles/globals.css | 87 ++++++++++---- 18 files changed, 466 insertions(+), 67 deletions(-) create mode 100644 site/public/brand/VERSION create mode 100644 site/public/brand/favicon.svg create mode 100644 site/public/brand/logo-mark-reversed.svg create mode 100644 site/public/brand/logo-mark.svg create mode 100644 site/public/brand/mascot.svg create mode 100644 site/public/brand/theme-toggle.html create mode 100644 site/public/brand/theme.js create mode 100644 site/public/brand/tokens.css diff --git a/site/astro.config.mjs b/site/astro.config.mjs index 008669d..4b3543d 100644 --- a/site/astro.config.mjs +++ b/site/astro.config.mjs @@ -22,9 +22,10 @@ export default defineConfig({ markdown: { remarkPlugins: [rewriteMdLinks], shikiConfig: { - // github-dark-high-contrast passes WCAG AA for all token colors - // (#6A737D comment color in github-dark fails 3.05:1 on its #24292e bg) - theme: 'github-dark-high-contrast', + // css-variables theme so syntax colors come from the CorvidLabs brand + // --code-* tokens (mapped to --astro-code-token-* in globals.css). This + // follows light/dark automatically and keeps code on-brand (no purple/blue). + theme: 'css-variables', }, }, }) diff --git a/site/public/brand/VERSION b/site/public/brand/VERSION new file mode 100644 index 0000000..4360171 --- /dev/null +++ b/site/public/brand/VERSION @@ -0,0 +1 @@ +1.1.0 (317e2c4) diff --git a/site/public/brand/favicon.svg b/site/public/brand/favicon.svg new file mode 100644 index 0000000..a686bc8 --- /dev/null +++ b/site/public/brand/favicon.svg @@ -0,0 +1,13 @@ + + + + + + diff --git a/site/public/brand/logo-mark-reversed.svg b/site/public/brand/logo-mark-reversed.svg new file mode 100644 index 0000000..21f5568 --- /dev/null +++ b/site/public/brand/logo-mark-reversed.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/site/public/brand/logo-mark.svg b/site/public/brand/logo-mark.svg new file mode 100644 index 0000000..3a0f98a --- /dev/null +++ b/site/public/brand/logo-mark.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/site/public/brand/mascot.svg b/site/public/brand/mascot.svg new file mode 100644 index 0000000..2505206 --- /dev/null +++ b/site/public/brand/mascot.svg @@ -0,0 +1,9 @@ + + + + + + + + + diff --git a/site/public/brand/theme-toggle.html b/site/public/brand/theme-toggle.html new file mode 100644 index 0000000..8bc16fb --- /dev/null +++ b/site/public/brand/theme-toggle.html @@ -0,0 +1,59 @@ + + + + + + + + + + diff --git a/site/public/brand/theme.js b/site/public/brand/theme.js new file mode 100644 index 0000000..a82abaa --- /dev/null +++ b/site/public/brand/theme.js @@ -0,0 +1,71 @@ +/* ============================================================ + CorvidLabs theme controller — the standard light/dark behavior. + + Drop this on any page that imports tokens.css. It: + - defaults to the OS preference (prefers-color-scheme) + - lets ?theme=light|dark force a mode (handy for QA screenshots) + - persists an explicit choice in localStorage + - wires every [data-corvid-theme-toggle] button: click to flip, + with aria-pressed + a descriptive aria-label kept in sync. + + The sun/moon icon swap itself is pure CSS (see theme-toggle.html), + so the correct icon shows on first paint with no flash. This script + only manages state + accessibility. + + For zero flash of the wrong THEME, also inline the pre-paint snippet + from theme-toggle.html in ; this file can load with defer. + ============================================================ */ +(() => { + "use strict"; + + const root = document.documentElement; + const STORE_KEY = "corvid-theme"; + + const systemDark = () => window.matchMedia("(prefers-color-scheme: dark)").matches; + const isDark = () => + root.dataset.theme === "dark" || (!root.dataset.theme && systemDark()); + + // Apply a stored or URL-forced choice (the pre-paint snippet may + // have already done this; re-applying is idempotent). + const urlTheme = new URLSearchParams(location.search).get("theme"); + let saved = urlTheme; + if (!saved) { + // localStorage access can throw in sandboxed iframes / disabled-storage + // private modes — guard it so the toggle still initializes. + try { + saved = localStorage.getItem(STORE_KEY); + } catch (_) { + /* no stored preference available */ + } + } + if (saved === "dark" || saved === "light") { + root.dataset.theme = saved; + } + + const buttons = document.querySelectorAll("[data-corvid-theme-toggle]"); + + const reflect = () => { + const dark = isDark(); + buttons.forEach((btn) => { + btn.setAttribute("aria-pressed", String(dark)); + btn.setAttribute( + "aria-label", + dark ? "Switch to light theme" : "Switch to dark theme" + ); + }); + }; + + buttons.forEach((btn) => { + btn.addEventListener("click", () => { + root.dataset.theme = isDark() ? "light" : "dark"; + try { + localStorage.setItem(STORE_KEY, root.dataset.theme); + } catch (_) { + /* storage may be unavailable; the in-page choice still applies */ + } + reflect(); + }); + }); + + reflect(); +})(); diff --git a/site/public/brand/tokens.css b/site/public/brand/tokens.css new file mode 100644 index 0000000..c9a41b3 --- /dev/null +++ b/site/public/brand/tokens.css @@ -0,0 +1,139 @@ +/* ============================================================ + CorvidLabs Brand Tokens + The single source of truth. Import this file, don't re-derive it. + + Light is the default. Dark follows the OS via prefers-color-scheme, + and either can be forced with data-theme="light|dark" on + (the sun/moon toggle in theme.js writes it). Every token is defined + in both modes, so anything built on these tokens themes for free. + ============================================================ */ + +:root { + color-scheme: light; + + /* Core surfaces */ + --ink: #15181B; /* near-black, slightly cool — primary text/marks */ + --paper: #FAF9F6; /* warm off-white — page ground */ + --surface: #FFFFFF; /* cards, raised panels */ + --surface-strong: #DCDAD2; /* inputs, wells, lifted surfaces */ + + /* Accent — corvid feather sheen. Never purple. */ + --sheen: #0E6F66; /* the accent — 5.9:1 on paper (AA) */ + --sheen-strong: #0B5750; /* deepened — 8:1 on paper, for small accent text (AAA) */ + --sheen-bright: #45D0BC; /* glint — for use ON ink only */ + --steel: #1E6FA8; /* gradient end only, never text */ + + /* Ink alpha steps (text + hairlines over light surfaces) */ + --ink-70: rgba(21, 24, 27, 0.72); + --ink-60: rgba(21, 24, 27, 0.62); + --ink-45: rgba(21, 24, 27, 0.45); + --ink-12: rgba(21, 24, 27, 0.12); + --ink-06: rgba(21, 24, 27, 0.06); + --hairline: var(--ink-12); + --header-bg: rgba(250, 249, 246, 0.92); + + /* Semantic state colors (defined so sites stop improvising) */ + --danger: #84241E; /* errors, destructive, recording */ + --warning: #8A5A00; /* in-progress, caution */ + --success: #2F6B3A; /* shipped, healthy */ + --info: var(--sheen);/* informational — reuses the accent by design */ + + /* Code syntax highlighting (docs, terminals) — never purple */ + --code-keyword: #0B5750; + --code-string: #2F6B3A; + --code-number: #8A5A00; + --code-function: #1B5E8A; /* also types */ + --code-comment: rgba(21, 24, 27, 0.50); + + /* Categorical chart / data-viz palette — distinguishable, never purple */ + --chart-1: #0E6F66; /* teal */ + --chart-2: #1E6FA8; /* steel */ + --chart-3: #B07A1E; /* amber */ + --chart-4: #2F6B3A; /* green */ + --chart-5: #A0492E; /* clay */ + + /* The one gradient we allow: a feather-sheen hairline. Decorative only. */ + --iridescence: linear-gradient(90deg, #0E6F66 0%, #1799A3 55%, #1E6FA8 100%); + + /* Type */ + --font-display: "Schibsted Grotesk", "Helvetica Neue", sans-serif; + --font-mono: "Spline Sans Mono", "SF Mono", monospace; + --measure: 64ch; +} + +/* Dark surfaces. Declared once, applied via the forced override below + and the system-preference media query. */ +:root[data-theme="dark"] { + color-scheme: dark; + + --ink: #F4F3EF; + --paper: #131619; + --surface: #1B1F23; + --surface-strong: #272C31; + + --sheen: #45D0BC; + --sheen-strong: #45D0BC; + --sheen-bright: #45D0BC; + + --ink-70: rgba(244, 243, 239, 0.78); + --ink-60: rgba(244, 243, 239, 0.68); + --ink-45: rgba(244, 243, 239, 0.55); + --ink-12: rgba(244, 243, 239, 0.15); + --ink-06: rgba(244, 243, 239, 0.08); + --header-bg: rgba(19, 22, 25, 0.92); + + --danger: #F08A7D; + --warning: #E0B05A; + --success: #6FC98A; + + --code-keyword: #45D0BC; + --code-string: #6FC98A; + --code-number: #E0B05A; + --code-function: #6BB6E0; + --code-comment: rgba(244, 243, 239, 0.50); + + --chart-1: #45D0BC; + --chart-2: #6BB6E0; + --chart-3: #E0B05A; + --chart-4: #6FC98A; + --chart-5: #E0916F; +} + +@media (prefers-color-scheme: dark) { + /* :not([data-theme="light"]) lets a forced-light override win. */ + :root:not([data-theme="light"]) { + color-scheme: dark; + + --ink: #F4F3EF; + --paper: #131619; + --surface: #1B1F23; + --surface-strong: #272C31; + + --sheen: #45D0BC; + --sheen-strong: #45D0BC; + --sheen-bright: #45D0BC; + + --ink-70: rgba(244, 243, 239, 0.78); + --ink-60: rgba(244, 243, 239, 0.68); + --ink-45: rgba(244, 243, 239, 0.55); + --ink-12: rgba(244, 243, 239, 0.15); + --ink-06: rgba(244, 243, 239, 0.08); + --header-bg: rgba(19, 22, 25, 0.92); + + --danger: #F08A7D; + --warning: #E0B05A; + --success: #6FC98A; + + --code-keyword: #45D0BC; + --code-string: #6FC98A; + --code-number: #E0B05A; + --code-function: #6BB6E0; + --code-comment: rgba(244, 243, 239, 0.50); + + --chart-1: #45D0BC; + --chart-2: #6BB6E0; + --chart-3: #E0B05A; + --chart-4: #6FC98A; + --chart-5: #E0916F; + } +} diff --git a/site/src/components/Button.astro b/site/src/components/Button.astro index 7e670f2..d5be3ff 100644 --- a/site/src/components/Button.astro +++ b/site/src/components/Button.astro @@ -17,16 +17,17 @@ const Tag = href ? 'a' : 'button' diff --git a/site/src/components/Callout.astro b/site/src/components/Callout.astro index fbd4106..4c01f75 100644 --- a/site/src/components/Callout.astro +++ b/site/src/components/Callout.astro @@ -9,15 +9,15 @@ const { type = 'note' } = Astro.props diff --git a/site/src/components/Header.astro b/site/src/components/Header.astro index b1ffc70..724885f 100644 --- a/site/src/components/Header.astro +++ b/site/src/components/Header.astro @@ -23,7 +23,11 @@ const isActive = (href: string) => current === href.replace(/\/$/, '')