Files
lib-theme-modena/theme.css
jules 3a6bca9f2b Initial commit: Modena theme + --chat-* surface tokens
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-20 16:25:46 +10:00

448 lines
16 KiB
CSS

/*
* Modena — Editorial design system theme.
*
* Content-presentation tokens + base typography. Designed for blogs,
* articles, documentation, and long-form essays. Editorial minimalism:
* the UI disappears so the content takes center stage.
*
* Ports the relevant subset of the Modena reference into a Crema-style
* theme.css that:
* • Defines the standard Crema design tokens (--background, --primary,
* --foreground, etc.) so all shadcn/lib-* components keep working.
* • Adds Modena-specific tokens (--mod-prose-*, --mod-text-*, etc.)
* consumed by lib-modena-ui components and the article surface.
* • Applies base typography to h1-h4, [data-slot="article"], and
* [data-slot="prose"] so any consumer of @crema/content-ui
* automatically picks up the editorial look.
*
* Pairs with: @crema/content-ui (article primitives), lib-modena-ui
* (Modena-specific extra components).
*/
/* Newsreader for the editorial serif. Pulled at theme-import time so
* consumers don't have to remember to import the font separately. */
@import url("https://fonts.googleapis.com/css2?family=Newsreader:ital,opsz,wght@0,6..72,400;0,6..72,500;0,6..72,700;1,6..72,400&family=Inter:wght@400;500;600;700;800&family=JetBrains+Mono:wght@400;500&display=swap");
:root {
/* ── Crema-shaped tokens (apps and shadcn components consume these) ── */
--background: oklch(1 0 0);
--foreground: oklch(0.16 0.02 255);
--card: oklch(1 0 0);
--card-foreground: oklch(0.16 0.02 255);
--popover: oklch(1 0 0);
--popover-foreground: oklch(0.16 0.02 255);
--primary: oklch(0.55 0.22 260);
--primary-foreground: oklch(0.99 0.005 80);
--secondary: oklch(0.96 0.005 250);
--secondary-foreground: oklch(0.20 0.025 255);
--muted: oklch(0.97 0.003 250);
--muted-foreground: oklch(0.50 0.015 250);
--accent: oklch(0.95 0.01 260);
--accent-foreground: oklch(0.18 0.02 255);
--destructive: oklch(0.55 0.22 25);
--destructive-foreground: oklch(0.99 0.005 80);
--success: oklch(0.65 0.18 145);
--success-foreground: oklch(0.99 0.005 80);
--warning: oklch(0.72 0.16 70);
--warning-foreground: oklch(0.15 0.02 70);
--border: oklch(0.92 0.005 250);
--input: oklch(0.92 0.005 250);
--ring: oklch(0.55 0.22 260);
/* ── Chat surfaces ──
* Contract for lib-agent-chat-ui / lib-agent-dock-ui. User bubble is
* a filled, deep, high-contrast panel with light text; assistant
* bubble is a card-like surface with a hairline border. */
--chat-user-bg: oklch(0.48 0.2 260);
--chat-user-fg: oklch(0.99 0.005 80);
--chat-assistant-bg: oklch(0.98 0.003 250);
--chat-assistant-fg: oklch(0.16 0.02 255);
--chat-assistant-border: oklch(0.92 0.005 250);
--radius: 0.5rem;
--sidebar: oklch(0.985 0.003 250);
--sidebar-foreground: oklch(0.16 0.02 255);
--sidebar-accent: oklch(0.94 0.005 250);
--sidebar-accent-foreground: oklch(0.20 0.025 255);
--sidebar-border: oklch(0.92 0.005 250);
--sidebar-ring: oklch(0.55 0.22 260);
--sidebar-primary: oklch(0.55 0.22 260);
--sidebar-primary-foreground: oklch(0.99 0.005 80);
/* ── Modena-specific tokens ─────────────────────────────────────── */
/* Accent — duplicate of --primary so Modena components can read
* either. Override --mod-accent at the app/site level to brand. */
--mod-accent: var(--primary);
/* Gray scale (11 steps, matches Modena's reference). */
--mod-gray-50: oklch(0.985 0.002 250);
--mod-gray-100: oklch(0.965 0.003 250);
--mod-gray-200: oklch(0.92 0.004 250);
--mod-gray-300: oklch(0.86 0.006 250);
--mod-gray-400: oklch(0.70 0.012 250);
--mod-gray-500: oklch(0.55 0.018 250);
--mod-gray-600: oklch(0.45 0.020 250);
--mod-gray-700: oklch(0.36 0.022 250);
--mod-gray-800: oklch(0.24 0.024 250);
--mod-gray-900: oklch(0.17 0.025 250);
--mod-gray-950: oklch(0.10 0.025 250);
/* Text */
--mod-text: var(--foreground);
--mod-text-secondary: oklch(0.42 0.020 250);
--mod-text-muted: var(--muted-foreground);
--mod-text-faint: oklch(0.68 0.012 250);
/* Prose */
--mod-prose-color: oklch(0.22 0.022 250);
--mod-prose-size: clamp(1.1875rem, 1.0625rem + 0.4vw, 1.3125rem);
--mod-prose-line-height: 1.7;
--mod-prose-max-width: 720px;
--mod-prose-tracking: -0.011em;
/* Inline code */
--mod-code-color: oklch(0.55 0.22 350);
--mod-code-bg: var(--muted);
--mod-code-border: var(--border);
/* Code block (dark always) */
--mod-bg-code: oklch(0.16 0.020 250);
--mod-bg-code-fg: oklch(0.92 0.010 80);
--mod-bg-code-header: oklch(0.13 0.020 250);
/* Status colors */
--mod-info: oklch(0.55 0.22 260);
--mod-info-bg: color-mix(in oklch, oklch(0.55 0.22 260) 6%, transparent);
--mod-info-border: color-mix(in oklch, oklch(0.55 0.22 260) 20%, transparent);
--mod-success: oklch(0.65 0.18 145);
--mod-success-bg: color-mix(in oklch, oklch(0.65 0.18 145) 6%, transparent);
--mod-success-border: color-mix(in oklch, oklch(0.65 0.18 145) 20%, transparent);
--mod-warning: oklch(0.72 0.16 70);
--mod-warning-bg: color-mix(in oklch, oklch(0.72 0.16 70) 6%, transparent);
--mod-warning-border: color-mix(in oklch, oklch(0.72 0.16 70) 20%, transparent);
--mod-danger: oklch(0.55 0.22 25);
--mod-danger-bg: color-mix(in oklch, oklch(0.55 0.22 25) 6%, transparent);
--mod-danger-border: color-mix(in oklch, oklch(0.55 0.22 25) 20%, transparent);
/* Fonts */
--font-sans: "Inter", -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif;
--font-heading: "Inter", -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif;
--font-mono: "JetBrains Mono", ui-monospace, Menlo, Consolas, monospace;
--mod-font-body: "Newsreader", "Source Serif 4", Georgia, "Times New Roman", serif;
--font-ai-prose: var(--mod-font-body);
/* Letter-spacing scale */
--mod-tracking: -0.011em;
--mod-tracking-tight: -0.02em;
--mod-tracking-heading: -0.025em;
--mod-tracking-wide: 0.04em;
/* Spacing (Modena's 4px grid) */
--mod-space-1: 4px;
--mod-space-2: 8px;
--mod-space-3: 12px;
--mod-space-4: 16px;
--mod-space-5: 20px;
--mod-space-6: 24px;
--mod-space-8: 32px;
--mod-space-10: 40px;
--mod-space-12: 48px;
--mod-space-16: 64px;
/* Radius scale */
--mod-radius-sm: 4px;
--mod-radius: 6px;
--mod-radius-md: 8px;
--mod-radius-lg: 12px;
--mod-radius-xl: 16px;
--mod-radius-pill: 9999px;
/* Type scale (Crema-shaped) */
--text-display-size: clamp(2.5rem, 2rem + 2vw, 3.25rem);
--text-display-lh: 1.1;
--text-headline-size: clamp(2rem, 1.7rem + 1.2vw, 2.5rem);
--text-headline-lh: 1.15;
--text-title-size: 1.5rem;
--text-title-lh: 1.3;
--text-body-size: 1rem;
--text-body-lh: 1.6;
--text-label-size: 0.875rem;
--text-label-lh: 1.4;
--text-caption-size: 0.75rem;
--text-caption-lh: 1.4;
/* Motion */
--duration-fast: 120ms;
--duration-base: 200ms;
--duration-slow: 300ms;
--ease-standard: cubic-bezier(0.25, 0.1, 0.25, 1);
--ease-decelerate: cubic-bezier(0, 0, 0.25, 1);
/* Elevation */
--elevation-0: none;
--elevation-1: 0 1px 2px rgba(0, 0, 0, 0.04);
--elevation-2: 0 1px 3px rgba(0, 0, 0, 0.06), 0 1px 2px rgba(0, 0, 0, 0.03);
--elevation-3: 0 4px 8px -2px rgba(0, 0, 0, 0.06), 0 2px 4px -2px rgba(0, 0, 0, 0.03);
--elevation-4: 0 12px 24px -4px rgba(0, 0, 0, 0.08), 0 4px 8px -4px rgba(0, 0, 0, 0.03);
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
}
/* ── DARK MODE ──────────────────────────────────────────────────── */
.dark {
--background: oklch(0.13 0.018 250);
--foreground: oklch(0.95 0.010 250);
--card: oklch(0.17 0.020 250);
--card-foreground: oklch(0.95 0.010 250);
--popover: oklch(0.17 0.020 250);
--popover-foreground: oklch(0.95 0.010 250);
--primary: oklch(0.65 0.22 260);
--primary-foreground: oklch(0.99 0.005 80);
--secondary: oklch(0.22 0.020 250);
--secondary-foreground: oklch(0.92 0.010 250);
--muted: oklch(0.20 0.020 250);
--muted-foreground: oklch(0.65 0.020 250);
--accent: oklch(0.24 0.022 250);
--accent-foreground: oklch(0.92 0.010 250);
--border: oklch(0.27 0.020 250);
--input: oklch(0.27 0.020 250);
--ring: oklch(0.65 0.22 260);
/* ── Chat surfaces (dark) ── */
--chat-user-bg: oklch(0.55 0.2 260);
--chat-user-fg: oklch(0.99 0.005 80);
--chat-assistant-bg: oklch(0.20 0.020 250);
--chat-assistant-fg: oklch(0.95 0.010 250);
--chat-assistant-border: oklch(0.27 0.020 250);
--sidebar: oklch(0.11 0.018 250);
--sidebar-foreground: oklch(0.92 0.010 250);
--sidebar-accent: oklch(0.22 0.022 250);
--sidebar-accent-foreground: oklch(0.92 0.010 250);
--sidebar-border: oklch(0.25 0.020 250);
--sidebar-primary: oklch(0.65 0.22 260);
--sidebar-primary-foreground: oklch(0.99 0.005 80);
--mod-text: var(--foreground);
--mod-text-secondary: oklch(0.65 0.020 250);
--mod-text-muted: oklch(0.55 0.020 250);
--mod-text-faint: oklch(0.40 0.018 250);
--mod-prose-color: oklch(0.86 0.012 250);
--mod-code-color: oklch(0.78 0.22 350);
--mod-code-bg: oklch(0.22 0.020 250);
--mod-code-border: oklch(0.30 0.020 250);
}
/* ── BASE TYPOGRAPHY ─────────────────────────────────────────────
* Caffe Florian's theme applies serif font-heading to h1-h4 globally;
* Modena keeps headings in Inter sans. Apply explicitly so it sticks
* regardless of cascade order with other themes' base layers. */
html {
font-family: var(--font-sans);
}
h1, h2, h3, h4,
[data-slot="card-title"],
[data-slot="dialog-title"],
[data-slot="sheet-title"],
[data-slot="alert-dialog-title"] {
font-family: var(--font-sans);
font-weight: 700;
letter-spacing: var(--mod-tracking-heading);
}
/* ── ARTICLE SURFACE ─────────────────────────────────────────────
* Targets @crema/content-ui's data-slot attributes. Anyone using
* <Article><ArticleHeader/><Prose>...</Prose></Article>
* automatically picks up the editorial look. Outside [data-slot="article"]
* Prose stays neutral (so comment bodies don't get drop-cap/serif). */
[data-slot="article"] {
max-width: var(--mod-prose-max-width);
margin-inline: auto;
padding-inline: var(--mod-space-6);
color: var(--mod-text);
}
[data-slot="article"] [data-slot="article-header"] {
text-align: center;
padding-top: var(--mod-space-16);
margin-bottom: 3vmin;
border: none;
}
[data-slot="article"] [data-slot="article-header"] h1 {
font-family: var(--font-sans);
font-size: var(--text-display-size);
line-height: var(--text-display-lh);
letter-spacing: var(--mod-tracking-heading);
font-weight: 800;
margin-top: 0.5rem;
color: var(--mod-text);
}
[data-slot="article"] [data-slot="article-header"] p {
font-family: var(--mod-font-body);
font-optical-sizing: auto;
font-size: clamp(1.125rem, 1rem + 0.4vw, 1.375rem);
line-height: 1.5;
color: var(--mod-text-secondary);
margin: 0.75em auto 0;
max-width: 36rem;
}
[data-slot="article"] [data-slot="article-header"] > div:first-child {
font-size: 0.875rem;
font-weight: 600;
letter-spacing: var(--mod-tracking-wide);
text-transform: uppercase;
color: var(--mod-accent);
}
[data-slot="article"] [data-slot="article-meta"] {
justify-content: center;
padding-top: var(--mod-space-6);
margin-top: var(--mod-space-6);
font-size: 0.875rem;
color: var(--mod-text-muted);
}
[data-slot="article"] [data-slot="prose"] {
font-family: var(--mod-font-body);
font-optical-sizing: auto;
font-size: var(--mod-prose-size);
line-height: var(--mod-prose-line-height);
letter-spacing: var(--mod-prose-tracking);
color: var(--mod-prose-color);
}
[data-slot="article"] [data-slot="prose"] > * + * {
margin-top: 1.5em;
}
[data-slot="article"] [data-slot="prose"] :where(p) { margin: 0; }
[data-slot="article"] [data-slot="prose"] :where(h1, h2, h3, h4, h5, h6) {
font-family: var(--font-sans);
font-weight: 700;
line-height: 1.2;
letter-spacing: var(--mod-tracking-heading);
color: var(--mod-text);
}
[data-slot="article"] [data-slot="prose"] :where(h2) {
font-size: 1.875rem;
margin-top: 2.4em;
margin-bottom: 0.6em;
}
[data-slot="article"] [data-slot="prose"] :where(h3) {
font-size: 1.5rem;
margin-top: 2em;
margin-bottom: 0.5em;
}
[data-slot="article"] [data-slot="prose"] :where(h4) {
font-size: 1.25rem;
margin-top: 1.8em;
margin-bottom: 0.4em;
}
[data-slot="article"] [data-slot="prose"] :where(strong) {
font-weight: 700;
color: var(--mod-text);
}
[data-slot="article"] [data-slot="prose"] :where(ul, ol) {
padding-left: 1.5em;
margin: 0;
}
[data-slot="article"] [data-slot="prose"] :where(li + li) {
margin-top: 0.4em;
}
[data-slot="article"] [data-slot="prose"] :where(li::marker) {
color: var(--mod-text-faint);
}
[data-slot="article"] [data-slot="prose"] :where(blockquote) {
position: relative;
font-size: 1.25em;
font-weight: 500;
line-height: 1.5;
color: var(--mod-text);
padding: 0 0 0 1.5em;
margin: 2em 0;
border: none;
font-style: normal;
}
[data-slot="article"] [data-slot="prose"] :where(blockquote)::before {
content: "";
position: absolute;
left: 0;
top: 0;
bottom: 0;
width: 3px;
background: var(--mod-accent);
border-radius: 2px;
}
[data-slot="article"] [data-slot="prose"] :where(blockquote p) { margin: 0; }
[data-slot="article"] [data-slot="prose"] :where(blockquote p + p) { margin-top: 0.6em; }
[data-slot="article"] [data-slot="prose"] :where([data-slot="pullquote"]) {
font-family: var(--mod-font-body);
font-size: 1.625em;
font-style: italic;
font-weight: 400;
line-height: 1.5;
text-align: center;
padding: 0;
color: var(--mod-text);
margin: 2.5em 0;
}
[data-slot="article"] [data-slot="prose"] :where([data-slot="pullquote"])::before {
display: none;
}
[data-slot="article"] [data-slot="prose"] :where(hr) {
border: none;
border-top: 1px solid var(--border);
opacity: 0.6;
margin: 3em 0;
width: auto;
height: auto;
background: transparent;
}
[data-slot="article"] [data-slot="prose"] :where(:not(pre) > code) {
font-family: var(--font-mono);
font-size: 0.85em;
color: var(--mod-code-color);
background: var(--mod-code-bg);
border: 1px solid var(--mod-code-border);
border-radius: var(--mod-radius-sm);
padding: 0.1em 0.35em;
}
[data-slot="article"] [data-slot="prose"] :where(pre) {
font-family: var(--font-mono);
font-size: 0.875rem;
line-height: 1.65;
background: var(--mod-bg-code);
color: var(--mod-bg-code-fg);
border-radius: var(--mod-radius-md);
padding: var(--mod-space-5);
overflow-x: auto;
margin: 2em 0;
}
[data-slot="article"] [data-slot="prose"] :where(pre code) {
background: transparent;
color: inherit;
border: none;
padding: 0;
border-radius: 0;
font-size: inherit;
}
[data-slot="article"] [data-slot="prose"] :where(a) {
color: var(--mod-accent);
text-decoration: underline;
text-decoration-thickness: 1px;
text-underline-offset: 2px;
transition: color var(--duration-fast) var(--ease-standard);
}
[data-slot="article"] [data-slot="prose"] :where(a:hover) {
color: color-mix(in oklch, var(--mod-accent) 75%, var(--foreground));
}