From 95c1b52f6801b333eb8b47473782db930c5c5c30 Mon Sep 17 00:00:00 2001 From: 0xLeif Date: Fri, 12 Jun 2026 16:05:43 -0600 Subject: [PATCH] Update: apply CorvidLabs design system to the docs MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Vendor the brand standard into site/public/brand (tokens.css, theme.js, crow head mark + favicon) and rebuild the docs and shared chrome on the brand tokens with working light + dark. - Import tokens.css globally + Schibsted Grotesk / Spline Sans Mono fonts - Bridge legacy CSS vars onto brand tokens so components theme for free - Light + dark: honor prefers-color-scheme + add the standard sun/moon header toggle (pre-paint snippet, theme.js, no-flash) - Crow head mark in the header (inline, currentColor) + brand favicon - Map Shiki onto the css-variables theme → brand --code-* tokens - Replace all off-brand colors (orange accent, purple tag) with brand semantic + chart tokens; square corners throughout Co-Authored-By: Claude Fable 5 --- site/astro.config.mjs | 8 +- 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/Badge.astro | 10 +- site/src/components/Button.astro | 17 +-- site/src/components/Callout.astro | 14 +-- site/src/components/CategoryTag.astro | 17 +-- site/src/components/Header.astro | 90 +++++++++++---- site/src/components/PluginCard.astro | 6 +- site/src/components/PostCard.astro | 5 +- site/src/components/Terminal.astro | 27 +++-- site/src/layouts/BaseLayout.astro | 22 +++- site/src/pages/blog/index.astro | 2 +- site/src/pages/examples/index.astro | 2 +- site/src/pages/index.astro | 11 +- site/src/pages/plugins/index.astro | 2 +- site/src/styles/globals.css | 82 +++++++++---- 23 files changed, 512 insertions(+), 105 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 dae3ff3..365f09b 100644 --- a/site/astro.config.mjs +++ b/site/astro.config.mjs @@ -13,9 +13,11 @@ export default defineConfig({ // cross-page references resolve at runtime instead of 404'ing. remarkPlugins: [remarkResolveDocLinks], 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', + // The 'css-variables' theme emits --astro-code-* custom properties + // instead of baked-in colors, so highlighting resolves to the brand + // --code-* tokens (mapped in src/styles/globals.css) and themes with + // light/dark for free. WCAG AA holds in both modes. + 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/Badge.astro b/site/src/components/Badge.astro index d2c76f2..61d9dca 100644 --- a/site/src/components/Badge.astro +++ b/site/src/components/Badge.astro @@ -10,13 +10,13 @@ const { dot = false } = Astro.props diff --git a/site/src/components/Callout.astro b/site/src/components/Callout.astro index 5535aff..4ccb93b 100644 --- a/site/src/components/Callout.astro +++ b/site/src/components/Callout.astro @@ -7,12 +7,12 @@ const { type = 'note' } = Astro.props diff --git a/site/src/components/CategoryTag.astro b/site/src/components/CategoryTag.astro index 23662dd..8e215db 100644 --- a/site/src/components/CategoryTag.astro +++ b/site/src/components/CategoryTag.astro @@ -15,12 +15,15 @@ const { category } = Astro.props diff --git a/site/src/components/Header.astro b/site/src/components/Header.astro index 53061e9..ed53652 100644 --- a/site/src/components/Header.astro +++ b/site/src/components/Header.astro @@ -15,7 +15,11 @@ const isActive = (href: string) => current === href.replace(/\/$/, '')