Files
learnharness/app/globals.css
T
amosblomqvist 0629e71a50 Settings-dialog round 1 staging repair: the provider-rewiring files
Round 1 (27d4f33) listed sidebar.tsx, keyboard-shortcuts.tsx,
app/settings/page.tsx and the globals.css keyframes in its message but
they were never staged. This commits exactly that content: the sidebar
Settings row as a button calling the dialog provider, the mod+, chord
opening the dialog, /settings redirecting to /chat/new, and the
settings-pop keyframes (reduced-motion aware). No new changes —
catch-up staging only. (next-env.d.ts churn left untracked-in-commit as
dev-server noise.)
2026-09-19 22:29:17 +03:00

447 lines
16 KiB
CSS
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
@import "tailwindcss";
@custom-variant dark (&:is(.dark *));
/*
* THE SKIN — wave 5. Design tokens, the single place to restyle the app.
*
* Mathematical soundness, per the owner's brief:
* - Tailwind's built-in 0.25rem (4px) spacing scale IS the rhythm — every
* padding/margin/gap in the app uses whole units of a 4px base.
* - The type scale below is a declared modular ladder (each --text-* token
* carries its own line-height); components never invent pixel sizes.
* - Radii ladder is 4 / 8 / 12 / 16px multiples of the 4px base.
* - Emphasis colors live in tokens (--lh-ink-strong / --lh-ink-italic) so a
* future settings UI reassigns them (configurable per the owner).
*/
/* Palette (owner's hexes): main #141414, raised #1C1C1C, edges #434343.
Mono chrome = JetBrains Mono; prose = Source Sans 3; titles = CMU Serif. */
:root {
color-scheme: dark;
--radius: 8px;
--background: #141414;
--foreground: #e6e6e6;
--card: #1c1c1c;
--card-foreground: #e6e6e6;
--popover: #1c1c1c;
--popover-foreground: #e6e6e6;
--primary: #e6e6e6;
--primary-foreground: #141414;
--secondary: #1c1c1c;
--secondary-foreground: #e6e6e6;
--muted: #1c1c1c;
--muted-foreground: #8d8d8d;
--accent: #2a2a2a;
--accent-foreground: #e6e6e6;
--destructive: oklch(0.704 0.191 22.216);
--border: #434343;
--input: #434343;
--ring: #8d8d8d;
/* Emphasis coloring in teaching prose — ready for the settings UI. */
--lh-ink-strong: #c96b4a;
--lh-ink-italic: #6fa9d6;
/* THE READING SIZE = the ladder's 16px rung (--text-lg). Can't be
var()-linked: @theme inline inlines literals into utilities without
emitting runtime vars — so this is the one documented duplication:
change one, change both. (owner, typography round 4: snapped from
the reskin's hand-tuned 16.5 to the ladder's 16.) */
--lh-prose-size: 16px;
--lh-prose-leading: 1.5;
/* Markdown heading weight (appearance settings; 900 = the Black cut). */
--lh-heading-weight: 900;
}
/* The app is always dark (<html class="dark">); mirror the same values so
the block stays coherent under Tailwind's variant. */
.dark {
--background: #141414;
--foreground: #e6e6e6;
--card: #1c1c1c;
--card-foreground: #e6e6e6;
--popover: #1c1c1c;
--popover-foreground: #e6e6e6;
--primary: #e6e6e6;
--primary-foreground: #141414;
--secondary: #1c1c1c;
--secondary-foreground: #e6e6e6;
--muted: #1c1c1c;
--muted-foreground: #8d8d8d;
--accent: #2a2a2a;
--accent-foreground: #e6e6e6;
--destructive: oklch(0.704 0.191 22.216);
--border: #434343;
--input: #434343;
--ring: #8d8d8d;
--lh-ink-strong: #c96b4a;
--lh-ink-italic: #6fa9d6;
/* THE READING SIZE = the ladder's 16px rung (--text-lg). Can't be
var()-linked: @theme inline inlines literals into utilities without
emitting runtime vars — so this is the one documented duplication:
change one, change both. (owner, typography round 4: snapped from
the reskin's hand-tuned 16.5 to the ladder's 16.) */
--lh-prose-size: 16px;
--lh-prose-leading: 1.5;
/* Markdown heading weight (appearance settings; 900 = the Black cut). */
--lh-heading-weight: 900;
}
/* CMU Serif bold (titles) — bundled local file, no CDN dependency. */
@font-face {
font-family: "lh-cmu";
src: url("/fonts/cmunbx.ttf") format("truetype");
font-weight: 700;
font-style: normal;
font-display: swap;
}
@theme inline {
/* Fonts: chrome = JetBrains Mono, prose = Source Sans 3, titles = CMU
Serif bold. The JetBrains/SourceSans variables attach in app/layout.tsx.
(owner, typography round) The Tailwind defaults are ALIASED to the app
fonts: `font-mono`/`font-sans` utilities would otherwise fall to the
SYSTEM stacks — a fourth font the app never chose — and streamdown's
code-block chrome + mermaid-error + demo-card all use them. */
--font-ui: var(--font-jetbrains), ui-monospace, SFMono-Regular, monospace;
--font-prose: var(--font-sans3), ui-sans-serif, system-ui, sans-serif;
--font-title: "lh-cmu", serif;
--font-mono: var(--font-jetbrains), ui-monospace, SFMono-Regular, monospace;
--font-sans: var(--font-sans3), ui-sans-serif, system-ui, sans-serif;
/* The modular type ladder — each size carries its own line-height.
Component sizes must pull from this ladder. (owner, typography
round 4: the 10px rung is merged into 11 — one pixel apart was a
distinction with no visible difference, and 10px sits at the mono
legibility floor; quiet labels get their quiet from color +
tracking, not a notch of size. Reading prose = the 16px rung via
--lh-prose-size.) */
--text-xs: 11px;
--text-xs--line-height: 1.4;
--text-sm: 12.5px;
--text-sm--line-height: 1.4;
--text-base: 14px;
--text-base--line-height: 1.45;
--text-lg: 16px;
--text-lg--line-height: 1.45;
--text-xl: 20px;
--text-xl--line-height: 1.4;
--text-2xl: 25px;
--text-2xl--line-height: 1.3;
--text-3xl: 32px;
--text-3xl--line-height: 1.25;
/* Radii ladder — 4/8/12/16 multiples of the 4px base. */
--radius-sm: 4px;
--radius-md: 8px;
--radius-lg: 12px;
--radius-xl: 16px;
--radius: 8px;
--color-background: var(--background);
--color-foreground: var(--foreground);
--color-card: var(--card);
--color-card-foreground: var(--card-foreground);
--color-popover: var(--popover);
--color-popover-foreground: var(--popover-foreground);
--color-primary: var(--primary);
--color-primary-foreground: var(--primary-foreground);
--color-secondary: var(--secondary);
--color-secondary-foreground: var(--secondary-foreground);
--color-muted: var(--muted);
--color-muted-foreground: var(--muted-foreground);
--color-accent: var(--accent);
--color-accent-foreground: var(--accent-foreground);
--color-destructive: var(--destructive);
--color-border: var(--border);
--color-input: var(--input);
--color-ring: var(--ring);
}
* {
border-color: var(--color-border);
}
body {
background: var(--color-background);
color: var(--color-foreground);
font-family: var(--font-ui);
}
/* Teaching prose (the markdown renderer's container gets .lh-prose):
Source Sans 3 with the size/leading tokens; emphasis color bound to
the settings-ready tokens. */
.lh-prose {
font-family: var(--font-prose);
font-size: var(--lh-prose-size);
line-height: var(--lh-prose-leading);
}
.lh-prose :where(strong, b) {
color: var(--lh-ink-strong);
font-weight: 700;
}
.lh-prose :where(em, i) {
color: var(--lh-ink-italic);
}
/* Markdown headings render at the font's BLACK weight (owner ask,
2026-09-19) — 900, not streamdown's font-semibold 600. The variable
Source Sans 3 axis spans 200–900 (next/font loads the full range), so
900 draws the real Black cut. Unlayered CSS beats the utility layer;
:where keeps specificity zero. Chrome titles (CMU Serif) don't live
under .lh-prose and stay untouched. */
.lh-prose :where(h1, h2, h3, h4, h5, h6) {
font-weight: var(--lh-heading-weight, 900);
}
/* lh-input (owner, typography round 2): lh-prose's 1.5 READING leading
makes typing carets tower over the glyphs — the caret fills the line
box (24px at 16px text, ~2× the glyph height). Inputs are not
paragraphs: a tighter 1.25 leading brings the caret to the font-metric
floor (~20px, as close to the glyphs as Chromium will draw it) while
wrapped lines in growing textareas still read fine. Rendered prose keeps
1.5 — the two share family and size, they differ only in leading. */
.lh-input {
font-family: var(--font-prose);
font-size: var(--lh-prose-size);
line-height: 1.25;
}
/* Thin dark native scrollbar (app-wide): dark thumb, transparent track. */
* {
scrollbar-width: thin;
scrollbar-color: var(--color-border) transparent;
}
::-webkit-scrollbar {
width: 8px;
height: 8px;
}
::-webkit-scrollbar-track {
background: transparent;
}
::-webkit-scrollbar-thumb {
background: var(--color-border);
border-radius: 8px;
}
::-webkit-scrollbar-thumb:hover {
background: var(--color-muted-foreground);
}
/* Dock variant swap — quick fade/rise on content change; the shell's HEIGHT
morph is separate (WAAPI in DockOverlay). Re-applied via class toggling, no
remount, so collapsed-question state survives. */
@keyframes dock-variant-in {
from {
opacity: 0;
transform: translateY(6px);
}
to {
opacity: 1;
transform: translateY(0);
}
}
.dock-variant {
animation: dock-variant-in 160ms cubic-bezier(0.2, 0.8, 0.2, 1);
}
/* ============================================================
* W3 — presentation fixes (appended 2026-08-22)
* ============================================================ */
/* Streamdown's internal UI chrome (code blocks, tables, mermaid containers,
copy/download buttons) is styled with Tailwind utilities living in its dist
* bundle. Tailwind v4 never scans node_modules, so without this line those
* classes are dead — code blocks and mermaid containers rendered unstyled.
* (Streamdown README, "Tailwind CSS setup".) */
@source "../node_modules/streamdown/dist/*.js";
/* Token the streamdown utilities reference (code-block/mermaid chrome uses
* bg-sidebar / border-sidebar): map it onto the app's card surface. */
@theme inline {
--color-sidebar: var(--card);
}
/* Mermaid diagrams: keep wide charts inside the content column. Mermaid
* inlines a px max-width on the svg, so !important is required to win. */
[data-streamdown="mermaid"] svg {
max-width: 100% !important;
height: auto !important;
}
/* Display math: long equations scroll instead of overflowing the column. */
.katex-display {
overflow-x: auto;
overflow-y: hidden;
}
/* ============================================================
* Chrome melt (owner wave, 2026-09-19) — tables and code blocks stop
* being boxes: blocks read as part of the message. These rules override
* streamdown's Tailwind utilities (utilities live in a cascade LAYER;
* unlayered author CSS wins regardless of order).
* ============================================================ */
/* Code blocks: no header bar — one quiet rounded tint so code reads as a
unit. The tint is DARKER than the page (muted #1c1c1c was LIGHTER than the
background #141414 and read as raised — owner note, chrome-melt tweaks
2026-09-19) plus a subtle hairline outline. (md-blocks renders its own
hover copy corner.) */
[data-streamdown="code-block"] {
border: 1px solid var(--border);
background: color-mix(in oklab, var(--background) 86%, black);
border-radius: 0.75rem;
padding: 0.375rem;
}
[data-streamdown="code-block-header"] {
display: none;
}
[data-streamdown="code-block-body"] {
border: 0;
background: transparent;
padding: 0.75rem 1rem;
}
/* Tables: hairline row rules + header underline, and a full outer border
— actual RECTANGLES outlined on all four sides (owner note, chrome-melt
tweaks 2026-09-19: the melt left the shape missing its left/right/top
edges). border-collapse merges the outer border with the row rules, so
no edge doubles. (md-blocks supplies the wrapper + the copy corner.) */
[data-streamdown="table"] {
border: 1px solid var(--border);
/* Tables run at the SESSION prose size (owner note, 2026-09-19: they
visibly lagged the 16px chat text at streamdown's 12.5px text-sm).
Unlayered CSS beats the cells' own utility-layer text-sm, and the
:where keeps specificity zero — the override wins on layering alone. */
font-size: var(--lh-prose-size);
}
[data-streamdown="table"] :where(th, td) {
font-size: inherit;
}
/* Column separators: a hairline left rule on every cell but the first in
its row (owner ask, 2026-09-19) — border-collapse merges it with the row
rules and the outer border, no doubled lines anywhere. */
[data-streamdown="table"] :where(th, td):not(:first-child) {
border-left: 1px solid var(--border);
}
[data-streamdown="table-header"] {
background: transparent;
border-bottom: 1px solid var(--border);
}
[data-streamdown="table-body"] [data-streamdown="table-row"] {
border-bottom: 1px solid var(--border);
}
/* The mermaid focus overlay's chart wrapper: the svg fills the wrapper
(which is sized to the chart's viewBox — pan-zoom.ts math centers on it),
overriding mermaid's inline max-width cap. Later + same-specificity
than the column rule above, so the !important tie breaks our way; the
overlay's svg never sits under [data-streamdown="mermaid"] anyway. */
[data-lh-overlay-chart] svg {
max-width: none !important;
width: 100% !important;
height: auto !important;
}
/* ============================================================
* Session status — the braille spinner (the one working indicator)
* ============================================================ */
/* (owner, minimal-color round) Phase color-coding is RETIRED: phases only
advance (every session ends in Teaching), so phase-as-signal was
decoration; the --phase-* tokens and every phase-dot state are gone.
What state deserved — "a turn is in flight" — is carried by MOTION
alone: the terminal braille spinner (components/braille-spinner.tsx),
monochrome, existing only while true. */
/* One keyframes, all ten frames: each frame <g> holds opacity 1 for its
10% slice of the 800ms cycle and 0 elsewhere; frames stagger via
positive animation-delay (frame k starts at k×80ms). Static contexts
(SSR, reduced motion) show frame 0 — see the base rules below. */
@keyframes braille-frame {
0%,
10% {
opacity: 1;
}
10.001%,
100% {
opacity: 0;
}
}
.braille-frame {
animation: braille-frame 800ms linear infinite;
opacity: 0;
}
/* Static fallback: frame 0 (⠋) is the glyph a non-animated context shows. */
.braille-frame[data-frame="0"] {
opacity: 1;
}
@media (prefers-reduced-motion: reduce) {
.braille-frame {
animation: none;
}
}
/* Settings dialog pop (settings-app wave, 2026-09-20): a short fade +
2% settle-scale — understated by design, the app's motion taste
(200ms sidebar, 150ms here); honors reduced-motion by appearing. */
@keyframes settings-pop {
from {
opacity: 0;
scale: 0.98;
}
to {
opacity: 1;
scale: 1;
}
}
.settings-dialog-pop {
animation: settings-pop 150ms ease-out;
}
@media (prefers-reduced-motion: reduce) {
.settings-dialog-pop {
animation: none;
}
}
/* ─────────────────────────────────────────────────────────────────────────
Electron merged chrome (owner, 2026-09-17). The system title bar is
hidden (titleBarStyle: "hiddenInset" in electron/main.cjs); the app's
own 60px header bands are the drag surface. Everything here is inert
in the plain browser — it keys off <html class="lh-electron">, which
SidebarProvider sets after mount when it detects the Electron shell.
- .lh-drag window-drag region — on a dedicated mask div in
home.tsx placed FIRST in the page DOM (the mask
resolves by DOM order, not z-index: no-drag subtracts
only regions added earlier in the tree)
- .lh-no-drag carve-out for interactive elements inside a drag band
- .lh-lights-gutter the sidebar LH band's traffic-light gutter: lights
end at x≈68, the wordmark starts at 80px (5rem)
- .lh-closed-toggle same gutter for the sidebar toggle when the sidebar
is closed — the lights are static, the toggle moves
───────────────────────────────────────────────────────────────────────── */
.lh-electron .lh-drag {
-webkit-app-region: drag;
}
.lh-electron .lh-no-drag {
-webkit-app-region: no-drag;
}
/* 92px = measured lights' right edge (~75pt) + the lights' own 16px
inset from the window edge — comfortable, symmetric air (owner). */
.lh-electron .lh-lights-gutter {
padding-left: 92px;
}
.lh-electron .lh-closed-toggle {
margin-left: 92px;
}