/* * 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). */ /* Fonts (Newsreader editorial serif + Inter + JetBrains Mono) are loaded by * the consuming app's entry CSS, not @import-ed here — the token contract bans * font @imports inside theme.css (they break PostCSS ordering). The Google * Fonts URL is documented in this theme's README. */ :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.52 0.18 145); /* deepened 0.65→0.52 so light foreground clears AA (was 2.93:1) */ --success-foreground: oklch(0.99 0.005 80); --warning: oklch(0.72 0.16 70); --warning-foreground: oklch(0.15 0.02 70); /* Info — a cyan-blue held clear of the editorial primary (blue 260) so * status reads distinct from brand; deep enough to carry a light label. */ --info: oklch(0.52 0.15 235); --info-foreground: oklch(0.99 0.005 80); --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); /* ── Charts ── * Five editorial accents drawn from Modena's own status hues (brand blue, * venetian red, sage green, magenta, amber). Hues sit ≥60° apart and * adjacent indices alternate lightness so neighbouring series separate. */ --chart-1: oklch(0.55 0.20 260); /* brand blue */ --chart-2: oklch(0.63 0.20 25); /* venetian red */ --chart-3: oklch(0.58 0.15 145); /* sage green */ --chart-4: oklch(0.66 0.19 320); /* magenta */ --chart-5: oklch(0.60 0.16 70); /* amber */ --radius: 0.5rem; --spacing: 0.25rem; --border-width-thin: 1px; --border-width-base: 1px; --border-width-thick: 2px; --border-width-heavy: 4px; --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); /* Tailwind utility families (mirror --font-heading/--font-sans). */ --heading-family: var(--font-heading); --body-family: var(--font-sans); /* 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) — editorial, opts into fluid clamp() at the * hero/display/headline end (permitted for editorial themes). */ --text-hero-size: clamp(3.25rem, 2.6rem + 2.6vw, 4.5rem); --text-hero-lh: 1.05; --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-subtitle-size: 1.25rem; /* fills the label→title hole (deck/standfirst) */ --text-subtitle-lh: 1.4; --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 — calm editorial. No physical bounce: the springs the contract * mandates are aliased to the standard easings so var() always resolves * while the theme keeps its quiet, un-bouncy character. */ --duration-fast: 120ms; --duration-base: 200ms; --duration-slow: 300ms; --duration-spring: var(--duration-slow); --ease-standard: cubic-bezier(0.25, 0.1, 0.25, 1); --ease-decelerate: cubic-bezier(0, 0, 0.25, 1); --ease-accelerate: cubic-bezier(0.4, 0, 1, 1); --ease-emphasized: var(--ease-standard); --ease-spring-gentle: var(--ease-standard); --ease-spring-snappy: var(--ease-standard); --ease-spring-bouncy: var(--ease-standard); /* 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); --elevation-5: 0 24px 48px -8px rgba(0, 0, 0, 0.10), 0 8px 16px -8px rgba(0, 0, 0, 0.04); /* Glass — inert stubs. Modena is a flat editorial theme, not a glass one; * these resolve var() to no-ops so glass-aware components stay flat. */ --glass-blur: 0px; --glass-saturate: 100%; --glass-brightness: 1; --glass-contrast: 1; -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.55 0.22 260); /* deepened 0.65→0.55 so the near-white foreground clears AA on the filled button (was 3.38:1) */ --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); /* ── Status (dark-calibrated) ── * Light values (red/green/amber at L≈0.55-0.72) were inheriting into * dark mode, failing both as colored text on the dark field and as * button fills. Brightened here; foregrounds flipped to dark ink so * the fills carry text as well. */ --destructive: oklch(0.68 0.2 25); /* text-on-bg 6.37:1 (was 3.70:1 inherited) */ --destructive-foreground: oklch(0.16 0.04 25); /* dark-ink fill 6.19:1 */ --success: oklch(0.72 0.15 145); /* text-on-bg 8.58:1 */ --success-foreground: oklch(0.16 0.03 145); /* dark-ink fill 8.24:1 */ --warning: oklch(0.80 0.14 70); /* text-on-bg 10.51:1 */ --warning-foreground: oklch(0.16 0.02 70); /* dark-ink fill 10.16:1 */ --info: oklch(0.72 0.14 235); /* brightened for the dark field; dark-ink fill */ --info-foreground: oklch(0.16 0.03 235); /* ── Charts (dark) ── same five hues, brightened for the dark field. */ --chart-1: oklch(0.68 0.19 260); --chart-2: oklch(0.72 0.18 25); --chart-3: oklch(0.70 0.15 145); --chart-4: oklch(0.74 0.17 320); --chart-5: oklch(0.78 0.14 70); /* ── 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); --sidebar-ring: oklch(0.65 0.22 260); --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.59 0.018 250); /* raised 0.40→0.59 for AA vs dark bg/card (was 2.19:1) */ --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 *
...
* 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)); }