Files
static-design-system/base.css
Brendan Chen b61cb986f1 feat: initial shared design system extracted from newerror
Foundation + chrome for the bchen.dev static sites: design tokens, Spectral /
Helvetica / IBM Plex Mono type, base + .prose typography, data-driven nav +
footer, and prefers-color-scheme + [data-theme] dark mode with a pre-paint
init. Colors are overridable CSS custom properties (defaults = newerror's
palette); each site sets only its own colors.css.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01C96hwEU9cM7mJpqATiv7R5
2026-06-30 21:20:21 -04:00

317 lines
9.2 KiB
CSS

/* =====================================================================
static-design-system — base.css
Shared visual foundation for the bchen.dev static sites.
Canonical design: "A New Error" (newerror.bchen.dev) — the "Ledger"
direction: Spectral serif body, Helvetica sans UI, IBM Plex Mono.
HOW COLOR WORKS
Every color is a CSS custom property. The values below are DEFAULTS
(newerror's palette). Each consuming site overrides ONLY these color
tokens in its own colors.css, which loads AFTER this file. Fonts,
spacing, typography, nav/footer chrome, and dark-mode behavior are
shared and should NOT be re-declared per site.
IMPORTANT: keep this file Liquid-safe. Its contents are passed through the
Jekyll include tag by each site's served wrapper (assets/ds.css), so do NOT
introduce Liquid delimiters (double-brace or brace-percent sequences).
Plain CSS braces are fine.
===================================================================== */
:root {
/* Type */
--serif: "Spectral", Georgia, "Times New Roman", serif;
--sans: "Helvetica Neue", Helvetica, Arial, sans-serif;
--mono: "IBM Plex Mono", ui-monospace, "SF Mono", Menlo, monospace;
/* Layout */
--measure: 760px; /* standard content column */
--read: 680px; /* narrower reading column (articles) */
--pad: 34px;
--gutter: 104px; /* date column width (ledger layouts) */
}
/* ---- Color tokens — DEFAULTS (newerror). Override per site in colors.css ---- */
:root, :root[data-theme="light"] {
--bg: #FAFBFC;
--ink: #1B2027;
--muted: #69717C;
--faint: #99A1AC;
--rule: #E6E9EF;
--rule-2: #b6b9be;
--accent: #1463C7;
--accent-hover: #0F4FA3;
--field: #F1F3F6;
}
:root[data-theme="dark"] {
--bg: #14171C;
--ink: #E2E6EC;
--muted: #9AA2AD;
--faint: #69717C;
--rule: #262B32;
--rule-2: #5e6670;
--accent: #6FA9F2;
--accent-hover: #97C1F8;
--field: #1D2128;
}
@media (prefers-color-scheme: dark) {
:root:not([data-theme="light"]) {
--bg: #14171C;
--ink: #E2E6EC;
--muted: #9AA2AD;
--faint: #69717C;
--rule: #262B32;
--rule-2: #5e6670;
--accent: #6FA9F2;
--accent-hover: #97C1F8;
--field: #1D2128;
}
}
/* =====================================================================
Base
===================================================================== */
* { box-sizing: border-box; }
html { -webkit-text-size-adjust: 100%; }
body {
margin: 0;
background: var(--bg);
color: var(--ink);
font-family: var(--serif);
font-size: 18px;
line-height: 1.6;
-webkit-font-smoothing: antialiased;
text-rendering: optimizeLegibility;
transition: background-color 0.25s ease, color 0.25s ease;
}
::selection { background: color-mix(in oklab, var(--accent) 22%, transparent); }
a { color: inherit; }
/* ---- shell / responsive width ---- */
.shell {
width: 100%;
max-width: var(--measure);
margin-inline: auto;
padding-inline: var(--pad);
}
.page { min-height: 100vh; display: flex; flex-direction: column; }
.page > main { flex: 1 0 auto; }
/* =====================================================================
Top navigation
===================================================================== */
.nav {
font-family: var(--sans);
display: flex;
align-items: baseline;
justify-content: space-between;
gap: 16px;
padding: 38px 0 18px;
border-bottom: 1px solid var(--rule);
}
.nav.minimal { border-bottom: none; padding-bottom: 0; }
.mast {
font-family: var(--serif);
font-size: 20px;
font-weight: 600;
letter-spacing: 0.005em;
color: var(--ink);
text-decoration: none;
white-space: nowrap;
}
.mast:hover { color: var(--accent); }
.nav-links {
display: flex;
align-items: baseline;
gap: 26px;
}
.nav-links a, .iconbtn {
font-family: var(--sans);
font-size: 13.5px;
letter-spacing: 0.01em;
color: var(--muted);
text-decoration: none;
cursor: pointer;
background: none;
border: none;
padding: 0;
}
.nav-links a:hover, .iconbtn:hover { color: var(--accent); }
.nav-links a.active { color: var(--ink); }
.nav-tools { display: flex; align-items: center; gap: 18px; }
.iconbtn {
display: inline-flex; align-items: center; justify-content: center;
width: 26px; height: 26px; border-radius: 6px;
color: var(--muted);
}
.iconbtn:hover { color: var(--accent); background: var(--field); }
.iconbtn svg { width: 15px; height: 15px; display: block; }
/* hamburger — hidden on desktop */
.hamburger { display: none; }
.mobile-menu { display: none; }
/* =====================================================================
Footer
===================================================================== */
.foot {
font-family: var(--sans);
border-top: 1px solid var(--rule);
margin-top: 20px;
padding: 22px 0 40px;
display: flex; align-items: center; justify-content: space-between;
gap: 16px;
font-size: 12.5px;
color: var(--faint);
flex: 0 0 auto;
}
.foot a { color: var(--muted); text-decoration: none; }
.foot a:hover { color: var(--accent); }
.foot .rss-link { display: inline-flex; align-items: center; gap: 7px; }
/* =====================================================================
Prose — reusable content typography (markdown pages, articles)
Wrap rendered content in <div class="prose"> to opt in.
===================================================================== */
.prose { font-size: 19.5px; line-height: 1.68; color: var(--ink); }
.prose > :first-child { margin-top: 0; }
.prose p { margin: 0 0 1.3em; text-wrap: pretty; }
.prose h1 {
font-family: var(--serif);
font-size: 32px; line-height: 1.16; font-weight: 600;
letter-spacing: -0.018em; color: var(--ink);
margin: 0 0 0.5em; text-wrap: pretty;
}
.prose h2 {
font-family: var(--serif);
font-size: 25px; font-weight: 600; line-height: 1.25;
letter-spacing: -0.01em;
margin: 2em 0 0.6em;
}
.prose h3 {
font-family: var(--serif);
font-size: 21px; font-weight: 600;
margin: 1.7em 0 0.5em;
}
.prose a {
color: var(--ink);
text-decoration: underline;
text-decoration-color: var(--rule-2);
text-underline-offset: 2px;
text-decoration-thickness: 1px;
}
.prose a:hover { color: var(--accent); text-decoration-color: var(--accent); }
.prose strong { font-weight: 600; }
.prose em { font-style: italic; }
.prose blockquote {
margin: 1.6em 0;
padding-left: 22px;
border-left: 2px solid var(--rule-2);
color: var(--muted);
font-style: italic;
}
.prose blockquote p:last-child { margin-bottom: 0; }
.prose ul, .prose ol { margin: 0 0 1.3em; padding-left: 1.3em; }
.prose li { margin: 0.3em 0; padding-left: 0.2em; }
.prose li::marker { color: var(--faint); }
.prose hr {
border: none; height: 0;
border-top: 1px solid var(--rule);
margin: 2.4em auto;
width: 40%;
}
.prose code {
font-family: var(--mono);
font-size: 0.82em;
background: var(--field);
padding: 0.12em 0.4em;
border-radius: 4px;
}
.prose pre {
font-family: var(--mono);
font-size: 14px; line-height: 1.55;
background: var(--field);
padding: 18px 20px;
border-radius: 8px;
overflow-x: auto;
margin: 1.5em 0;
}
.prose pre code { background: none; padding: 0; font-size: inherit; }
.prose figure { margin: 2em 0; }
.prose figure img { display: block; width: 100%; height: auto; border-radius: 3px; }
.prose figcaption {
font-family: var(--sans);
font-size: 12.5px; color: var(--faint);
margin-top: 10px; text-align: left;
}
.prose img { max-width: 100%; height: auto; }
/* =====================================================================
Mobile
===================================================================== */
@media (max-width: 640px) {
:root { --pad: 22px; --gutter: 74px; }
body { font-size: 17px; }
.nav-links { display: none; }
.hamburger { display: inline-flex; }
.mobile-menu {
display: none;
font-family: var(--sans);
}
.mobile-menu.open {
display: flex; flex-direction: column;
gap: 2px;
padding: 8px 0 14px;
border-bottom: 1px solid var(--rule);
}
.mobile-menu a, .mobile-menu button {
font-family: var(--sans);
text-align: left;
background: none; border: none; cursor: pointer;
color: var(--ink);
font-size: 16px;
padding: 12px 4px;
text-decoration: none;
border-radius: 6px;
}
.mobile-menu a:hover, .mobile-menu button:hover { color: var(--accent); }
.mobile-menu .row { display: flex; align-items: center; gap: 10px; }
.prose { font-size: 18.5px; }
.prose h1 { font-size: 30px; }
}
/* =====================================================================
Icon swaps (theme toggle + hamburger), driven by [data-theme] / .is-open
===================================================================== */
.theme-toggle .ico-sun { display: none; }
.theme-toggle .ico-moon { display: block; }
:root[data-theme="dark"] .theme-toggle .ico-moon { display: none; }
:root[data-theme="dark"] .theme-toggle .ico-sun { display: block; }
@media (prefers-color-scheme: dark) {
:root:not([data-theme="light"]) .theme-toggle .ico-moon { display: none; }
:root:not([data-theme="light"]) .theme-toggle .ico-sun { display: block; }
}
.hamburger .ico-close { display: none; }
.hamburger.is-open .ico-bars { display: none; }
.hamburger.is-open .ico-close { display: block; }
/* reduce motion */
@media (prefers-reduced-motion: reduce) {
* { transition: none !important; }
}