lib-theme-harbor: initial import (warm-consumer set, 2026-07)
Includes the 2026-07-07 UX sweep: neutral inline-code chips (accent reserved for actions/links/focus), dark-mode card/popover lightness lift for visible surface separation, 44px touch-target floor on coarse pointers, card-title pinned to a named ramp role. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
404
theme.css
Normal file
404
theme.css
Normal file
@@ -0,0 +1,404 @@
|
||||
/*
|
||||
* Harbor — cool neutral, warm spark.
|
||||
*
|
||||
* The most neutral of the warm-consumer set. The whole surface is a calm,
|
||||
* faintly-COOL light grey — morning harbor light on soft slate, a whisper of
|
||||
* blue-grey that reads as clean neutral, never cream, never SaaS blue. All the
|
||||
* warmth in the theme is delivered by ONE honey/amber accent (hue ~78), spent
|
||||
* surgically on the primary action, selection, and focus ring — grey stone
|
||||
* warmed by lamplight. Ink is a deep, very-slightly-cool charcoal; the amber
|
||||
* carries dark ink on top (the bright-swatch/dark-ink pairing) so it lands high
|
||||
* above the contrast floor while staying quiet everywhere else.
|
||||
*
|
||||
* Dark mode is the harbor at night: a deep cool-neutral charcoal (low chroma,
|
||||
* faint cool hue ~250, not pure black and never warm-brown) with the same amber
|
||||
* glowing warm against it. Crisp, quiet, trustworthy — a calm utility that is
|
||||
* warm only at the touchpoints. Built for me.sky-ai.com back-of-house.
|
||||
*
|
||||
* Type is crisp modern-neutral: Inter Tight for text, Geist for headings, Geist
|
||||
* Mono for code. Product register — fixed rem sizes, no serif. Radius is a
|
||||
* crisp 0.75rem; shadows are low and neutral; motion is product-fast.
|
||||
*
|
||||
* Token contract matches lib-theme-caffe-florian/theme.css (Crema v1).
|
||||
*/
|
||||
|
||||
/*
|
||||
* Fonts: consuming apps load Inter Tight + Geist + Geist Mono at the top of
|
||||
* their entry CSS (where @import statements are allowed), via the Google Fonts
|
||||
* URL documented in this theme's README. Embedding the @import here — or
|
||||
* smuggling the URL in a comment for a build script to lift — breaks PostCSS's
|
||||
* ordering rules, so neither is done in this file.
|
||||
*/
|
||||
|
||||
:root {
|
||||
--font-sans: "Inter Tight", -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif;
|
||||
--font-heading: "Geist", "Inter Tight", -apple-system, BlinkMacSystemFont, sans-serif;
|
||||
--font-mono: "Geist Mono", ui-monospace, "SF Mono", Menlo, Consolas, monospace;
|
||||
|
||||
/* Tailwind utility families. Distinct names so a consuming app's
|
||||
* `@theme inline` can indirect to these (--font-heading/--font-sans collide
|
||||
* with Tailwind's own font-* namespace). Mirror --font-heading/--font-sans. */
|
||||
--heading-family: "Geist", "Inter Tight", -apple-system, BlinkMacSystemFont, sans-serif;
|
||||
--body-family: "Inter Tight", -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif;
|
||||
|
||||
/* ── Colors ── soft faintly-cool light-grey surface (harbor light), clean
|
||||
* white cards, deep cool-neutral charcoal ink, one honey/amber accent. */
|
||||
--background: oklch(0.975 0.005 248);
|
||||
--foreground: oklch(0.22 0.008 255);
|
||||
|
||||
--card: oklch(1 0 0);
|
||||
--card-foreground: oklch(0.22 0.008 255);
|
||||
|
||||
--popover: oklch(0.995 0.003 248);
|
||||
--popover-foreground: oklch(0.22 0.008 255);
|
||||
|
||||
/* Primary — the sole warmth. Honey/amber deepened a touch toward orange
|
||||
* (74°, L0.77) so it's rich rather than blinding, with a near-black warm
|
||||
* ink (L0.17, low chroma) on top — crisp, not amber-brown-on-amber. ~9:1. */
|
||||
--primary: oklch(0.77 0.145 74);
|
||||
--primary-foreground: oklch(0.17 0.02 74);
|
||||
|
||||
--secondary: oklch(0.94 0.006 250);
|
||||
--secondary-foreground: oklch(0.3 0.008 255);
|
||||
|
||||
--muted: oklch(0.94 0.006 250);
|
||||
--muted-foreground: oklch(0.46 0.011 253);
|
||||
|
||||
/* Accent — the warm spark as a hover/selection tint: a soft amber wash with
|
||||
* deep amber ink, not a second competing fill. */
|
||||
--accent: oklch(0.93 0.03 80);
|
||||
--accent-foreground: oklch(0.38 0.09 72);
|
||||
|
||||
/* ── Status ── dark ink on the bright status hues (harbor's bright-swatch
|
||||
* pattern), except destructive/info which carry light labels on deeper
|
||||
* fills for alarm legibility. Warning is pulled toward amber-ORANGE
|
||||
* (hue 52) and set darker than the honey primary (hue 78) so the two never
|
||||
* read as the same swatch. */
|
||||
--destructive: oklch(0.55 0.21 27);
|
||||
--destructive-foreground: oklch(0.98 0.01 27);
|
||||
--success: oklch(0.6 0.14 150);
|
||||
--success-foreground: oklch(0.17 0.03 150);
|
||||
--warning: oklch(0.68 0.17 52);
|
||||
--warning-foreground: oklch(0.23 0.05 52);
|
||||
--info: oklch(0.54 0.15 248);
|
||||
--info-foreground: oklch(0.98 0.01 248);
|
||||
|
||||
--border: oklch(0.22 0.008 255 / 0.1);
|
||||
--input: oklch(0.22 0.008 255 / 0.12);
|
||||
--ring: oklch(0.77 0.145 74 / 0.55);
|
||||
|
||||
/* Chart palette — amber lead, then cool blue / teal / violet / rose.
|
||||
* Warm spark against a cool spectrum; hues spaced, adjacent lightness
|
||||
* varied. */
|
||||
--chart-1: oklch(0.76 0.15 78);
|
||||
--chart-2: oklch(0.58 0.14 248);
|
||||
--chart-3: oklch(0.68 0.1 190);
|
||||
--chart-4: oklch(0.55 0.15 300);
|
||||
--chart-5: oklch(0.68 0.15 25);
|
||||
|
||||
--sidebar: oklch(0.955 0.006 250);
|
||||
--sidebar-foreground: oklch(0.22 0.008 255);
|
||||
--sidebar-primary: oklch(0.8 0.14 78);
|
||||
--sidebar-primary-foreground: oklch(0.26 0.05 78);
|
||||
--sidebar-accent: oklch(0.93 0.03 80);
|
||||
--sidebar-accent-foreground: oklch(0.38 0.09 72);
|
||||
--sidebar-border: oklch(0.22 0.008 255 / 0.08);
|
||||
--sidebar-ring: oklch(0.8 0.14 78 / 0.55);
|
||||
|
||||
/* Syntax — cool base with a warm keyword, matching the harbor palette. */
|
||||
--syntax-keyword: oklch(0.55 0.15 300);
|
||||
--syntax-string: oklch(0.5 0.12 150);
|
||||
--syntax-number: oklch(0.55 0.14 52);
|
||||
--syntax-comment: oklch(0.55 0.01 253);
|
||||
--syntax-function: oklch(0.5 0.15 248);
|
||||
--syntax-type: oklch(0.52 0.1 190);
|
||||
|
||||
/* ── Chat surfaces ──
|
||||
* Contract for lib-agent-chat-ui / lib-agent-dock-ui. User bubble is a deep
|
||||
* cool-charcoal panel with light text (neutral, so amber stays scarce);
|
||||
* assistant bubble is a clean card with a hairline border. */
|
||||
--chat-user-bg: oklch(0.28 0.012 253);
|
||||
--chat-user-fg: oklch(0.97 0.005 250);
|
||||
--chat-assistant-bg: oklch(1 0 0);
|
||||
--chat-assistant-fg: oklch(0.22 0.008 255);
|
||||
--chat-assistant-border: oklch(0.22 0.008 255 / 0.1);
|
||||
|
||||
/* Crisp-modern radius. */
|
||||
--radius: 0.75rem;
|
||||
|
||||
/* ── Motion ── crisp, product-fast. Springs alias to the standard eases so
|
||||
* the theme stays quiet — personality lives in the speed, not in bounce. */
|
||||
--duration-fast: 160ms;
|
||||
--duration-base: 280ms;
|
||||
--duration-slow: 400ms;
|
||||
--duration-slower: 560ms;
|
||||
--duration-spring: 280ms;
|
||||
|
||||
--ease-standard: cubic-bezier(0.2, 0, 0, 1);
|
||||
--ease-decelerate: cubic-bezier(0, 0, 0, 1);
|
||||
--ease-accelerate: cubic-bezier(0.3, 0, 1, 1);
|
||||
--ease-emphasized: cubic-bezier(0.2, 0, 0, 1);
|
||||
|
||||
--ease-spring-gentle: var(--ease-standard);
|
||||
--ease-spring-snappy: var(--ease-decelerate);
|
||||
--ease-spring-bouncy: var(--ease-standard);
|
||||
|
||||
/* ── Typography ── crisp product register, fixed rem, full role ramp. */
|
||||
--text-hero-size: 3.5rem;
|
||||
--text-hero-lh: 1.05;
|
||||
--text-display-size: 3rem;
|
||||
--text-display-lh: 1.1;
|
||||
--text-headline-size: 2rem;
|
||||
--text-headline-lh: 1.2;
|
||||
--text-title-size: 1.5rem;
|
||||
--text-title-lh: 1.3;
|
||||
--text-subtitle-size: 1.25rem;
|
||||
--text-subtitle-lh: 1.5;
|
||||
--text-body-size: 1.0625rem;
|
||||
--text-body-lh: 1.6;
|
||||
--text-label-size: 0.9375rem;
|
||||
--text-label-lh: 1.25rem;
|
||||
--text-caption-size: 0.8125rem;
|
||||
--text-caption-lh: 1.125rem;
|
||||
|
||||
/* ── Elevation ── crisp, low, neutral cool shadows. */
|
||||
--elevation-0: none;
|
||||
--elevation-1: 0 1px 2px oklch(0.22 0.008 255 / 0.06);
|
||||
--elevation-2: 0 1px 3px oklch(0.22 0.008 255 / 0.08), 0 1px 2px oklch(0.22 0.008 255 / 0.05);
|
||||
--elevation-3: 0 4px 8px -2px oklch(0.22 0.008 255 / 0.08), 0 2px 4px -2px oklch(0.22 0.008 255 / 0.05);
|
||||
--elevation-4: 0 12px 24px -6px oklch(0.22 0.008 255 / 0.1), 0 4px 8px -4px oklch(0.22 0.008 255 / 0.05);
|
||||
--elevation-5: 0 20px 40px -8px oklch(0.22 0.008 255 / 0.16);
|
||||
|
||||
--spacing: 0.25rem;
|
||||
|
||||
--border-width-thin: 0.5px;
|
||||
--border-width-base: 1px;
|
||||
--border-width-thick: 2px;
|
||||
--border-width-heavy: 4px;
|
||||
|
||||
/* No glass — inert values so consuming apps referencing these get no-ops. */
|
||||
--glass-blur: 0px;
|
||||
--glass-saturate: 100%;
|
||||
--glass-brightness: 1;
|
||||
--glass-contrast: 1;
|
||||
}
|
||||
|
||||
/*
|
||||
* Dark mode — harbor at night. Deep cool-neutral charcoal (faint cool hue ~250,
|
||||
* low chroma, not pure black, never warm-brown); the same honey/amber glows
|
||||
* warm against it with dark ink on top. Crisp and calm.
|
||||
*/
|
||||
.dark {
|
||||
--background: oklch(0.2 0.008 252);
|
||||
--foreground: oklch(0.94 0.006 250);
|
||||
|
||||
--card: oklch(0.26 0.008 252);
|
||||
--card-foreground: oklch(0.94 0.006 250);
|
||||
|
||||
--popover: oklch(0.28 0.008 252);
|
||||
--popover-foreground: oklch(0.94 0.006 250);
|
||||
|
||||
--primary: oklch(0.80 0.145 74);
|
||||
--primary-foreground: oklch(0.17 0.02 74);
|
||||
|
||||
--secondary: oklch(0.28 0.008 252);
|
||||
--secondary-foreground: oklch(0.9 0.006 250);
|
||||
|
||||
--muted: oklch(0.28 0.008 252);
|
||||
--muted-foreground: oklch(0.72 0.011 250);
|
||||
|
||||
--accent: oklch(0.33 0.05 78);
|
||||
--accent-foreground: oklch(0.86 0.1 78);
|
||||
|
||||
--destructive: oklch(0.56 0.21 27);
|
||||
--destructive-foreground: oklch(0.98 0.01 27);
|
||||
--success: oklch(0.7 0.15 150);
|
||||
--success-foreground: oklch(0.17 0.03 150);
|
||||
--warning: oklch(0.74 0.16 52);
|
||||
--warning-foreground: oklch(0.23 0.05 52);
|
||||
--info: oklch(0.66 0.15 248);
|
||||
--info-foreground: oklch(0.16 0.03 248);
|
||||
|
||||
--border: oklch(0.95 0.006 250 / 0.12);
|
||||
--input: oklch(0.95 0.006 250 / 0.14);
|
||||
--ring: oklch(0.80 0.145 74 / 0.55);
|
||||
|
||||
--chart-1: oklch(0.8 0.15 78);
|
||||
--chart-2: oklch(0.68 0.14 248);
|
||||
--chart-3: oklch(0.74 0.1 190);
|
||||
--chart-4: oklch(0.66 0.15 300);
|
||||
--chart-5: oklch(0.72 0.15 25);
|
||||
|
||||
--sidebar: oklch(0.22 0.008 252);
|
||||
--sidebar-foreground: oklch(0.94 0.006 250);
|
||||
--sidebar-primary: oklch(0.81 0.14 78);
|
||||
--sidebar-primary-foreground: oklch(0.24 0.05 78);
|
||||
--sidebar-accent: oklch(0.33 0.05 78);
|
||||
--sidebar-accent-foreground: oklch(0.86 0.1 78);
|
||||
--sidebar-border: oklch(0.95 0.006 250 / 0.09);
|
||||
--sidebar-ring: oklch(0.81 0.14 78 / 0.55);
|
||||
|
||||
--syntax-keyword: oklch(0.72 0.13 300);
|
||||
--syntax-string: oklch(0.74 0.12 150);
|
||||
--syntax-number: oklch(0.78 0.14 52);
|
||||
--syntax-comment: oklch(0.6 0.01 250);
|
||||
--syntax-function: oklch(0.72 0.14 248);
|
||||
--syntax-type: oklch(0.72 0.1 190);
|
||||
|
||||
/* ── Chat surfaces (dark) ── */
|
||||
--chat-user-bg: oklch(0.34 0.012 252);
|
||||
--chat-user-fg: oklch(0.96 0.006 250);
|
||||
--chat-assistant-bg: oklch(0.26 0.008 252);
|
||||
--chat-assistant-fg: oklch(0.94 0.006 250);
|
||||
--chat-assistant-border: oklch(0.95 0.006 250 / 0.12);
|
||||
|
||||
--elevation-1: 0 1px 2px oklch(0 0 0 / 0.3);
|
||||
--elevation-2: 0 1px 3px oklch(0 0 0 / 0.4), 0 1px 2px oklch(0 0 0 / 0.25);
|
||||
--elevation-3: 0 4px 8px -2px oklch(0 0 0 / 0.4), 0 2px 4px -2px oklch(0 0 0 / 0.25);
|
||||
--elevation-4: 0 12px 24px -6px oklch(0 0 0 / 0.45), 0 4px 8px -4px oklch(0 0 0 / 0.25);
|
||||
--elevation-5: 0 20px 40px -8px oklch(0 0 0 / 0.55);
|
||||
}
|
||||
|
||||
/*
|
||||
* @layer base — Harbor's crisp, quiet product philosophy.
|
||||
*
|
||||
* No glass, no editorial flourish. Clean grotesque type, calm neutral surfaces
|
||||
* hairline-separated, and the amber spark reserved for focus, selection, and the
|
||||
* primary action. Everything else stays cool and out of the way.
|
||||
*/
|
||||
@layer base {
|
||||
body {
|
||||
min-height: 100vh;
|
||||
-webkit-font-smoothing: antialiased;
|
||||
-moz-osx-font-smoothing: grayscale;
|
||||
letter-spacing: -0.006em;
|
||||
font-family: var(--font-sans);
|
||||
font-size: var(--text-body-size);
|
||||
line-height: var(--text-body-lh);
|
||||
}
|
||||
|
||||
/* Headings — Geist, crisp and tight. */
|
||||
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-heading);
|
||||
font-weight: 600;
|
||||
letter-spacing: -0.02em;
|
||||
color: var(--foreground);
|
||||
}
|
||||
|
||||
code, kbd, samp, pre,
|
||||
[data-slot="code"],
|
||||
[data-slot="code-block"] {
|
||||
font-family: var(--font-mono);
|
||||
}
|
||||
|
||||
/* Inline code — cool-neutral chip. */
|
||||
:not(pre) > code {
|
||||
padding: 0.1em 0.35em;
|
||||
border-radius: calc(var(--radius) * 0.5);
|
||||
background: color-mix(in oklch, var(--foreground) 6%, transparent);
|
||||
color: var(--foreground);
|
||||
font-size: 0.9em;
|
||||
}
|
||||
|
||||
/* Selection — the amber spark. */
|
||||
::selection {
|
||||
background: color-mix(in oklch, var(--primary) 30%, transparent);
|
||||
color: var(--foreground);
|
||||
}
|
||||
|
||||
/* Links — amber, underline-on-hover. */
|
||||
a {
|
||||
color: color-mix(in oklch, var(--primary) 70%, var(--foreground));
|
||||
text-decoration-color: color-mix(in oklch, var(--primary) 40%, transparent);
|
||||
text-underline-offset: 0.2em;
|
||||
transition: text-decoration-color var(--duration-fast) var(--ease-standard);
|
||||
}
|
||||
a:hover {
|
||||
text-decoration-color: var(--primary);
|
||||
}
|
||||
|
||||
[data-slot="sidebar"] {
|
||||
background: var(--sidebar);
|
||||
border-right: var(--border-width-base) solid var(--sidebar-border);
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* Focus-visible — the amber ring, offset (not inset), for keyboard users.
|
||||
* Placed outside @layer base so it beats Tailwind's default ring utilities.
|
||||
*/
|
||||
[data-slot="button"]:focus-visible,
|
||||
[data-slot="input"]:focus-visible,
|
||||
[data-slot="textarea"]:focus-visible,
|
||||
[data-slot="select-trigger"]:focus-visible {
|
||||
outline: 2px solid var(--primary);
|
||||
outline-offset: 2px;
|
||||
box-shadow: none;
|
||||
}
|
||||
|
||||
/* Buttons — quiet weight; opacity absorb on press, product-fast. */
|
||||
[data-slot="button"] {
|
||||
font-weight: 500;
|
||||
letter-spacing: -0.006em;
|
||||
}
|
||||
|
||||
/* Card titles — pinned to the subtitle role so the scale is the theme's
|
||||
* decision, not the card primitive's default. */
|
||||
[data-slot="card-title"] {
|
||||
font-size: var(--text-subtitle-size);
|
||||
line-height: var(--text-subtitle-lh);
|
||||
}
|
||||
|
||||
/* Touch targets — desktop keeps the crisp density ladder; coarse pointers
|
||||
* get the 44px floor on every control. */
|
||||
@media (pointer: coarse) {
|
||||
[data-slot="button"] {
|
||||
min-height: 2.75rem;
|
||||
min-width: 2.75rem;
|
||||
}
|
||||
[data-slot="input"],
|
||||
[data-slot="select-trigger"] {
|
||||
min-height: 2.75rem;
|
||||
}
|
||||
}
|
||||
[data-slot="button"]:hover:not(:disabled) {
|
||||
opacity: 0.9;
|
||||
}
|
||||
[data-slot="button"]:active:not(:disabled) {
|
||||
opacity: 0.82;
|
||||
transform: scale(0.99);
|
||||
}
|
||||
|
||||
/* Inputs — clean card surface, hairline border, amber focus as a flush ring. */
|
||||
[data-slot="input"],
|
||||
[data-slot="textarea"],
|
||||
[data-slot="select-trigger"] {
|
||||
background: var(--card);
|
||||
border: var(--border-width-base) solid var(--border);
|
||||
border-radius: var(--radius);
|
||||
color: var(--foreground);
|
||||
transition:
|
||||
border-color var(--duration-fast) var(--ease-standard),
|
||||
box-shadow var(--duration-fast) var(--ease-standard);
|
||||
}
|
||||
[data-slot="input"]::placeholder,
|
||||
[data-slot="textarea"]::placeholder {
|
||||
color: var(--muted-foreground);
|
||||
opacity: 0.8;
|
||||
}
|
||||
[data-slot="input"]:hover:not(:disabled),
|
||||
[data-slot="textarea"]:hover:not(:disabled),
|
||||
[data-slot="select-trigger"]:hover:not(:disabled) {
|
||||
border-color: color-mix(in oklch, var(--foreground) 22%, transparent);
|
||||
}
|
||||
[data-slot="input"]:focus,
|
||||
[data-slot="textarea"]:focus,
|
||||
[data-slot="select-trigger"]:focus {
|
||||
border-color: var(--primary);
|
||||
box-shadow: 0 0 0 1px var(--primary);
|
||||
outline: none;
|
||||
}
|
||||
Reference in New Issue
Block a user