| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903 |
- @import "tailwindcss";
- @import "tw-animate-css";
- @custom-variant dark (&:is(.dark *));
- @theme inline {
- --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-destructive-foreground: var(--destructive-foreground);
- --color-border: var(--border);
- --color-card-edge: var(--card-edge);
- --color-input: var(--input);
- --color-ring: var(--ring);
- --color-chart-1: var(--chart-1);
- --color-chart-2: var(--chart-2);
- --color-chart-3: var(--chart-3);
- --color-chart-4: var(--chart-4);
- --color-chart-5: var(--chart-5);
- --color-sidebar: var(--sidebar);
- --color-sidebar-foreground: var(--sidebar-foreground);
- --color-sidebar-primary: var(--sidebar-primary);
- --color-sidebar-primary-foreground: var(--sidebar-primary-foreground);
- --color-sidebar-accent: var(--sidebar-accent);
- --color-sidebar-accent-foreground: var(--sidebar-accent-foreground);
- --color-sidebar-border: var(--sidebar-border);
- --color-sidebar-ring: var(--sidebar-ring);
- --radius-sm: calc(var(--radius) - 4px);
- --radius-md: calc(var(--radius) - 2px);
- --radius-lg: var(--radius);
- --radius-xl: calc(var(--radius) + 4px);
- --font-sans: var(--font-geist-sans);
- --font-mono: var(--font-geist-mono);
- --font-display: var(--font-display-active);
- }
- /* ── Light: warm cream + amber/gold primary ── */
- :root {
- --radius: 0.625rem;
- --background: oklch(0.98 0.008 85);
- --foreground: oklch(0.18 0.02 75);
- --card: oklch(0.995 0.004 85);
- --card-foreground: oklch(0.18 0.02 75);
- --popover: oklch(0.995 0.004 85);
- --popover-foreground: oklch(0.18 0.02 75);
- --primary: oklch(0.72 0.17 75);
- --primary-foreground: oklch(0.16 0.03 75);
- --secondary: oklch(0.94 0.02 85);
- --secondary-foreground: oklch(0.30 0.03 75);
- --muted: oklch(0.95 0.01 85);
- --muted-foreground: oklch(0.52 0.03 75);
- --accent: oklch(0.93 0.04 80);
- --accent-foreground: oklch(0.25 0.03 75);
- --destructive: oklch(0.55 0.22 27);
- --destructive-foreground: oklch(0.98 0 0);
- --border: oklch(0.91 0.02 85);
- --input: oklch(0.91 0.02 85);
- --ring: oklch(0.72 0.17 75);
- --chart-1: oklch(0.72 0.17 75);
- --chart-2: oklch(0.65 0.15 160);
- --chart-3: oklch(0.60 0.18 30);
- --chart-4: oklch(0.60 0.12 280);
- --chart-5: oklch(0.55 0.14 340);
- --sidebar: oklch(0.97 0.01 80);
- --sidebar-foreground: oklch(0.18 0.02 75);
- --sidebar-primary: oklch(0.72 0.17 75);
- --sidebar-primary-foreground: oklch(0.16 0.03 75);
- --sidebar-accent: oklch(0.93 0.04 80);
- --sidebar-accent-foreground: oklch(0.25 0.03 75);
- --sidebar-border: oklch(0.91 0.02 85);
- --sidebar-ring: oklch(0.72 0.17 75);
- }
- /* ── Dark: deep charcoal + vibrant amber/gold ── */
- .dark {
- --background: oklch(0.12 0.01 70);
- --foreground: oklch(0.93 0.01 85);
- --card: oklch(0.16 0.012 70);
- --card-foreground: oklch(0.93 0.01 85);
- --popover: oklch(0.16 0.012 70);
- --popover-foreground: oklch(0.93 0.01 85);
- --primary: oklch(0.78 0.16 80);
- --primary-foreground: oklch(0.14 0.02 70);
- --secondary: oklch(0.22 0.015 70);
- --secondary-foreground: oklch(0.90 0.01 85);
- --muted: oklch(0.20 0.01 70);
- --muted-foreground: oklch(0.58 0.02 80);
- --accent: oklch(0.24 0.03 75);
- --accent-foreground: oklch(0.90 0.01 85);
- --destructive: oklch(0.55 0.22 27);
- --destructive-foreground: oklch(0.98 0 0);
- --border: oklch(0.25 0.015 70);
- --input: oklch(0.25 0.015 70);
- --ring: oklch(0.78 0.16 80);
- --chart-1: oklch(0.78 0.16 80);
- --chart-2: oklch(0.65 0.15 160);
- --chart-3: oklch(0.60 0.18 30);
- --chart-4: oklch(0.60 0.12 280);
- --chart-5: oklch(0.55 0.14 340);
- --sidebar: oklch(0.14 0.012 70);
- --sidebar-foreground: oklch(0.93 0.01 85);
- --sidebar-primary: oklch(0.78 0.16 80);
- --sidebar-primary-foreground: oklch(0.14 0.02 70);
- --sidebar-accent: oklch(0.24 0.03 75);
- --sidebar-accent-foreground: oklch(0.90 0.01 85);
- --sidebar-border: oklch(0.25 0.015 70);
- --sidebar-ring: oklch(0.78 0.16 80);
- }
- /* ── Theme presets ───────────────────────────────────────────────────────
- Each preset is applied on top of its base mode class (`light`/`dark`), so
- `dark:` utilities and the `.dark`-scoped rules further down keep working.
- These blocks must stay after `:root` and `.dark`: they carry the same
- specificity, so source order is what makes them win. ── */
- /* ── Graphite (light): neutral grey + steel blue ── */
- .theme-graphite {
- --background: oklch(0.98 0.002 250);
- --foreground: oklch(0.20 0.01 250);
- --card: oklch(1 0 0);
- --card-foreground: oklch(0.20 0.01 250);
- --popover: oklch(1 0 0);
- --popover-foreground: oklch(0.20 0.01 250);
- --primary: oklch(0.52 0.13 250);
- --primary-foreground: oklch(0.99 0 0);
- --secondary: oklch(0.95 0.005 250);
- --secondary-foreground: oklch(0.30 0.012 250);
- --muted: oklch(0.96 0.004 250);
- --muted-foreground: oklch(0.50 0.015 250);
- --accent: oklch(0.93 0.02 250);
- --accent-foreground: oklch(0.26 0.02 250);
- --destructive: oklch(0.55 0.22 27);
- --destructive-foreground: oklch(0.99 0 0);
- --border: oklch(0.91 0.006 250);
- --input: oklch(0.91 0.006 250);
- --ring: oklch(0.52 0.13 250);
- --chart-1: oklch(0.52 0.13 250);
- --chart-2: oklch(0.62 0.12 190);
- --chart-3: oklch(0.72 0.15 75);
- --chart-4: oklch(0.55 0.14 300);
- --chart-5: oklch(0.58 0.16 20);
- --sidebar: oklch(0.965 0.004 250);
- --sidebar-foreground: oklch(0.20 0.01 250);
- --sidebar-primary: oklch(0.52 0.13 250);
- --sidebar-primary-foreground: oklch(0.99 0 0);
- --sidebar-accent: oklch(0.93 0.02 250);
- --sidebar-accent-foreground: oklch(0.26 0.02 250);
- --sidebar-border: oklch(0.91 0.006 250);
- --sidebar-ring: oklch(0.52 0.13 250);
- }
- /* ── Ocean (light): cool blue paper + marine blue ── */
- .theme-ocean {
- --background: oklch(0.98 0.012 220);
- --foreground: oklch(0.20 0.03 235);
- --card: oklch(0.995 0.006 220);
- --card-foreground: oklch(0.20 0.03 235);
- --popover: oklch(0.995 0.006 220);
- --popover-foreground: oklch(0.20 0.03 235);
- --primary: oklch(0.52 0.12 215);
- --primary-foreground: oklch(0.99 0.005 220);
- --secondary: oklch(0.93 0.025 215);
- --secondary-foreground: oklch(0.28 0.04 230);
- --muted: oklch(0.95 0.015 215);
- --muted-foreground: oklch(0.50 0.035 225);
- --accent: oklch(0.91 0.045 205);
- --accent-foreground: oklch(0.26 0.05 230);
- --destructive: oklch(0.55 0.22 27);
- --destructive-foreground: oklch(0.99 0 0);
- --border: oklch(0.90 0.025 215);
- --input: oklch(0.90 0.025 215);
- --ring: oklch(0.52 0.12 215);
- --chart-1: oklch(0.52 0.12 215);
- --chart-2: oklch(0.65 0.13 175);
- --chart-3: oklch(0.60 0.14 260);
- --chart-4: oklch(0.70 0.13 90);
- --chart-5: oklch(0.58 0.16 340);
- --sidebar: oklch(0.96 0.018 215);
- --sidebar-foreground: oklch(0.20 0.03 235);
- --sidebar-primary: oklch(0.52 0.12 215);
- --sidebar-primary-foreground: oklch(0.99 0.005 220);
- --sidebar-accent: oklch(0.91 0.045 205);
- --sidebar-accent-foreground: oklch(0.26 0.05 230);
- --sidebar-border: oklch(0.90 0.025 215);
- --sidebar-ring: oklch(0.52 0.12 215);
- }
- /* ── Forest (light): soft sage + deep green ── */
- .theme-forest {
- --background: oklch(0.98 0.008 140);
- --foreground: oklch(0.20 0.02 150);
- --card: oklch(0.995 0.004 140);
- --card-foreground: oklch(0.20 0.02 150);
- --popover: oklch(0.995 0.004 140);
- --popover-foreground: oklch(0.20 0.02 150);
- --primary: oklch(0.50 0.12 150);
- --primary-foreground: oklch(0.98 0.01 140);
- --secondary: oklch(0.93 0.02 145);
- --secondary-foreground: oklch(0.28 0.03 150);
- --muted: oklch(0.95 0.012 145);
- --muted-foreground: oklch(0.50 0.025 150);
- --accent: oklch(0.92 0.04 145);
- --accent-foreground: oklch(0.26 0.04 150);
- --destructive: oklch(0.55 0.22 27);
- --destructive-foreground: oklch(0.99 0 0);
- --border: oklch(0.90 0.02 145);
- --input: oklch(0.90 0.02 145);
- --ring: oklch(0.50 0.12 150);
- --chart-1: oklch(0.50 0.12 150);
- --chart-2: oklch(0.62 0.13 100);
- --chart-3: oklch(0.60 0.14 200);
- --chart-4: oklch(0.68 0.15 65);
- --chart-5: oklch(0.55 0.15 25);
- --sidebar: oklch(0.96 0.014 145);
- --sidebar-foreground: oklch(0.20 0.02 150);
- --sidebar-primary: oklch(0.50 0.12 150);
- --sidebar-primary-foreground: oklch(0.98 0.01 140);
- --sidebar-accent: oklch(0.92 0.04 145);
- --sidebar-accent-foreground: oklch(0.26 0.04 150);
- --sidebar-border: oklch(0.90 0.02 145);
- --sidebar-ring: oklch(0.50 0.12 150);
- }
- /* ── Midnight (dark): deep navy + sky blue ── */
- .theme-midnight {
- --background: oklch(0.16 0.03 260);
- --foreground: oklch(0.93 0.015 250);
- --card: oklch(0.20 0.035 260);
- --card-foreground: oklch(0.93 0.015 250);
- --popover: oklch(0.20 0.035 260);
- --popover-foreground: oklch(0.93 0.015 250);
- --primary: oklch(0.70 0.14 245);
- --primary-foreground: oklch(0.15 0.04 260);
- --secondary: oklch(0.26 0.035 260);
- --secondary-foreground: oklch(0.90 0.015 250);
- --muted: oklch(0.24 0.03 260);
- --muted-foreground: oklch(0.65 0.03 250);
- --accent: oklch(0.30 0.05 255);
- --accent-foreground: oklch(0.92 0.02 250);
- --destructive: oklch(0.55 0.22 27);
- --destructive-foreground: oklch(0.98 0 0);
- --border: oklch(0.30 0.04 260);
- --input: oklch(0.30 0.04 260);
- --ring: oklch(0.70 0.14 245);
- --chart-1: oklch(0.70 0.14 245);
- --chart-2: oklch(0.72 0.13 190);
- --chart-3: oklch(0.70 0.14 300);
- --chart-4: oklch(0.78 0.14 85);
- --chart-5: oklch(0.68 0.16 350);
- --sidebar: oklch(0.18 0.032 260);
- --sidebar-foreground: oklch(0.93 0.015 250);
- --sidebar-primary: oklch(0.70 0.14 245);
- --sidebar-primary-foreground: oklch(0.15 0.04 260);
- --sidebar-accent: oklch(0.30 0.05 255);
- --sidebar-accent-foreground: oklch(0.92 0.02 250);
- --sidebar-border: oklch(0.30 0.04 260);
- --sidebar-ring: oklch(0.70 0.14 245);
- }
- /* ── Carbon (dark): true neutral black + mint ── */
- .theme-carbon {
- --background: oklch(0.15 0 0);
- --foreground: oklch(0.94 0 0);
- --card: oklch(0.19 0 0);
- --card-foreground: oklch(0.94 0 0);
- --popover: oklch(0.19 0 0);
- --popover-foreground: oklch(0.94 0 0);
- --primary: oklch(0.72 0.15 165);
- --primary-foreground: oklch(0.16 0.02 165);
- --secondary: oklch(0.25 0 0);
- --secondary-foreground: oklch(0.92 0 0);
- --muted: oklch(0.22 0 0);
- --muted-foreground: oklch(0.66 0 0);
- --accent: oklch(0.28 0.02 165);
- --accent-foreground: oklch(0.94 0 0);
- --destructive: oklch(0.58 0.22 27);
- --destructive-foreground: oklch(0.98 0 0);
- --border: oklch(0.27 0 0);
- --input: oklch(0.27 0 0);
- --ring: oklch(0.72 0.15 165);
- --chart-1: oklch(0.72 0.15 165);
- --chart-2: oklch(0.70 0.13 220);
- --chart-3: oklch(0.75 0.15 90);
- --chart-4: oklch(0.65 0.15 300);
- --chart-5: oklch(0.62 0.18 20);
- --sidebar: oklch(0.17 0 0);
- --sidebar-foreground: oklch(0.94 0 0);
- --sidebar-primary: oklch(0.72 0.15 165);
- --sidebar-primary-foreground: oklch(0.16 0.02 165);
- --sidebar-accent: oklch(0.28 0.02 165);
- --sidebar-accent-foreground: oklch(0.94 0 0);
- --sidebar-border: oklch(0.27 0 0);
- --sidebar-ring: oklch(0.72 0.15 165);
- }
- /* Card edges. --border sits about one shade off the card fill, which reads as
- no edge at all once a shadow is over it. This mixes the theme's own
- foreground into the edge instead, so it stays visible in every theme —
- dark ones included — without hardcoding a colour per theme. Declared on the
- same element the theme classes land on (documentElement), so `var(--foreground)`
- resolves to whichever theme is active. */
- :root {
- --card-edge: color-mix(in oklab, var(--foreground) 20%, transparent);
- }
- @layer base {
- * {
- @apply border-border outline-ring/50;
- }
- html {
- /* Reserve scrollbar space so its appearance never shifts layout width
- (the dashboard grid measures its container on mount) */
- scrollbar-gutter: stable;
- }
- body {
- @apply bg-background text-foreground;
- }
- }
- /* Font sets (src/lib/font-sets.ts), chosen on the Appearance page.
- The theme's font utilities are inlined, so `font-sans` compiles straight to
- var(--font-geist-sans) and there is no --font-sans left to override. The
- swap is therefore made one level down: fonts.css declares Geist under
- --font-geist-sans-base, and --font-geist-sans, the name everything uses, is
- pointed at it here or at another face by a set. Every `font-sans` and
- `font-mono` in the app follows, and the picker can still draw the default
- set from the *-base names while another one is active.
- --font-display-active is the heading face: the text face by default, Barlow
- Condensed in the workshop set, where a heading is also set in capitals and a
- size up, because a condensed face reads a size smaller than it is. */
- body {
- --font-geist-sans: var(--font-geist-sans-base);
- --font-geist-mono: var(--font-geist-mono-base);
- --font-display-active: var(--font-geist-sans-base);
- }
- html.font-set-workshop body {
- --font-geist-sans: var(--font-plex-sans);
- --font-geist-mono: var(--font-plex-mono);
- --font-display-active: var(--font-barlow-condensed);
- }
- html.font-set-precise body {
- --font-geist-sans: var(--font-inter);
- --font-geist-mono: var(--font-jetbrains-mono);
- --font-display-active: var(--font-inter-tight);
- }
- html.font-set-legible body {
- --font-geist-sans: var(--font-atkinson);
- --font-geist-mono: var(--font-atkinson-mono);
- --font-display-active: var(--font-atkinson);
- }
- /* Inter's clearer forms, where the font file carries them: a slashed zero and
- a lowercase l with a tail, so a part number cannot be read as a word. A
- feature a build of the font lacks is simply ignored. */
- html.font-set-precise body {
- font-feature-settings: 'zero', 'cv05';
- }
- html.font-set-workshop .font-display {
- text-transform: uppercase;
- letter-spacing: 0.03em;
- font-size: 1.22em;
- line-height: 1.1;
- }
- /* The work order number: figures in the default set, the heading face in the
- workshop set, as the mock has it. */
- .order-number {
- font-family: var(--font-geist-mono);
- }
- html.font-set-workshop .order-number {
- font-family: var(--font-barlow-condensed);
- font-size: 1.3em;
- letter-spacing: 0.01em;
- }
- /* Dialogs lock body scroll through react-remove-scroll, which then pushes
- the body in by one scrollbar width to stop the page jumping when the bar
- goes. The gutter above already keeps that space, so the push is the jump:
- the whole page slid left every time the search or plate lookup opened.
- Outranks the library's injected !important rule by specificity. */
- html body[data-scroll-locked] {
- margin-right: 0 !important;
- padding-right: 0 !important;
- }
- /* Keyboard-focusable rows (see src/lib/interactive-row.ts). Inset outline so
- the ring survives overflow-hidden card and table corners. */
- [data-row-interactive] {
- cursor: pointer;
- }
- [data-row-interactive]:focus-visible {
- outline: 2px solid var(--ring);
- outline-offset: -2px;
- }
- /* Hide scrollbar utility */
- .scrollbar-none {
- scrollbar-width: none;
- }
- .scrollbar-none::-webkit-scrollbar {
- display: none;
- }
- /* Scroll-edge shadows: a soft shadow at the top or bottom edge only while content is
- hidden in that direction. Pure CSS: the covering gradients scroll with the content
- (attachment: local) and slide off the fixed shadows as soon as there is more to see. */
- .scroll-shadows {
- --scroll-shadow: color-mix(in oklab, var(--foreground) 9%, transparent);
- background:
- linear-gradient(var(--background) 40%, transparent) center top / 100% 32px local,
- linear-gradient(transparent, var(--background) 60%) center bottom / 100% 32px local,
- radial-gradient(farthest-side at 50% 0, var(--scroll-shadow), transparent) center top / 100% 12px scroll,
- radial-gradient(farthest-side at 50% 100%, var(--scroll-shadow), transparent) center bottom / 100% 12px scroll;
- background-repeat: no-repeat;
- background-color: var(--background);
- }
- .dark .scroll-shadows {
- --scroll-shadow: color-mix(in oklab, var(--foreground) 14%, transparent);
- }
- /* Custom scrollbar */
- ::-webkit-scrollbar {
- width: 6px;
- height: 6px;
- }
- ::-webkit-scrollbar-track {
- background: transparent;
- }
- /* Derived from the theme tokens so every preset gets a matching thumb */
- ::-webkit-scrollbar-thumb {
- background: color-mix(in oklab, var(--muted-foreground) 45%, transparent);
- border-radius: 3px;
- }
- .dark ::-webkit-scrollbar-thumb {
- background: color-mix(in oklab, var(--muted-foreground) 40%, transparent);
- }
- /* Glass effect utility */
- .glass {
- background: color-mix(in oklab, var(--card) 70%, transparent);
- backdrop-filter: blur(16px);
- border: 1px solid color-mix(in oklab, var(--border) 60%, transparent);
- }
- /* Gradient text */
- .gradient-text {
- background: linear-gradient(
- 135deg,
- var(--primary),
- color-mix(in oklab, var(--primary) 55%, var(--chart-3))
- );
- -webkit-background-clip: text;
- -webkit-text-fill-color: transparent;
- background-clip: text;
- }
- /* Subtle grid bg */
- .grid-bg {
- background-image:
- linear-gradient(color-mix(in oklab, var(--border) 45%, transparent) 1px, transparent 1px),
- linear-gradient(90deg, color-mix(in oklab, var(--border) 45%, transparent) 1px, transparent 1px);
- background-size: 40px 40px;
- }
- /* Tiptap editor styles */
- .tiptap-content {
- overflow-wrap: anywhere;
- &:focus {
- outline: none;
- }
- > * + * {
- margin-top: 0.5em;
- }
- h2 {
- font-size: 1.25rem;
- font-weight: 600;
- line-height: 1.4;
- }
- h3 {
- font-size: 1.1rem;
- font-weight: 600;
- line-height: 1.4;
- }
- ul {
- list-style: disc;
- padding-left: 1.25rem;
- }
- ol {
- list-style: decimal;
- padding-left: 1.25rem;
- }
- li {
- margin-top: 0.15em;
- }
- blockquote {
- border-left: 3px solid var(--border);
- padding-left: 0.75rem;
- color: var(--muted-foreground);
- font-style: italic;
- }
- a {
- color: var(--primary);
- text-decoration: underline;
- cursor: pointer;
- }
- p.is-editor-empty:first-child::before {
- content: attr(data-placeholder);
- float: left;
- color: var(--muted-foreground);
- pointer-events: none;
- height: 0;
- }
- }
- /* Notes display (rendered HTML) */
- .notes-content {
- overflow-wrap: anywhere;
- h2 {
- font-size: 1.25rem;
- font-weight: 600;
- line-height: 1.4;
- }
- h3 {
- font-size: 1.1rem;
- font-weight: 600;
- line-height: 1.4;
- }
- ul {
- list-style: disc;
- padding-left: 1.25rem;
- }
- ol {
- list-style: decimal;
- padding-left: 1.25rem;
- }
- li {
- margin-top: 0.15em;
- }
- blockquote {
- border-left: 3px solid var(--border);
- padding-left: 0.75rem;
- color: var(--muted-foreground);
- font-style: italic;
- }
- a {
- color: var(--primary);
- text-decoration: underline;
- }
- > * + * {
- margin-top: 0.5em;
- }
- }
- /* AI chat markdown */
- .ai-markdown {
- > * + * {
- margin-top: 0.5em;
- }
- h1, h2, h3 {
- font-weight: 600;
- margin-top: 0.75em;
- }
- h1 { font-size: 1.25rem; }
- h2 { font-size: 1.1rem; }
- h3 { font-size: 1rem; }
- p { line-height: 1.5; }
- strong { font-weight: 600; }
- ul {
- list-style: disc;
- padding-left: 1.25rem;
- }
- ol {
- list-style: decimal;
- padding-left: 1.25rem;
- }
- li { margin-top: 0.15em; }
- code {
- font-size: 0.85em;
- background: var(--muted);
- padding: 0.15em 0.35em;
- border-radius: 4px;
- }
- pre {
- background: var(--muted);
- padding: 0.75rem 1rem;
- border-radius: 6px;
- overflow-x: auto;
- }
- pre code {
- background: none;
- padding: 0;
- }
- blockquote {
- border-left: 3px solid var(--border);
- padding-left: 0.75rem;
- color: var(--muted-foreground);
- font-style: italic;
- }
- .table-wrapper {
- overflow-x: auto;
- }
- table {
- width: 100%;
- border-collapse: collapse;
- margin: 0.5em 0;
- font-size: 0.875rem;
- white-space: nowrap;
- }
- th, td {
- padding: 0.5rem 0.75rem;
- text-align: left;
- border-bottom: 1px solid var(--border);
- }
- th {
- font-weight: 600;
- background: var(--muted);
- }
- tr:last-child td {
- border-bottom: none;
- }
- }
- /* ─── Dashboard grid (react-grid-layout) ─────────────────────────────── */
- .dashboard-grid .react-grid-item {
- transition: transform 200ms ease, width 200ms ease, height 200ms ease;
- }
- .dashboard-grid .react-grid-item.react-grid-placeholder {
- background: color-mix(in oklab, var(--primary) 12%, transparent);
- border: 2px dashed color-mix(in oklab, var(--primary) 50%, transparent);
- border-radius: var(--radius, 0.75rem);
- opacity: 1;
- }
- .dashboard-grid .react-grid-item.react-draggable-dragging,
- .dashboard-grid .react-grid-item.resizing {
- z-index: 30;
- transition: none;
- box-shadow: 0 12px 32px -8px rgb(0 0 0 / 0.25);
- }
- /* Replace the faint default resize arrow with a clear corner grip in the
- theme color; only rendered in edit mode (when resizing is enabled) */
- .dashboard-grid .react-grid-item > .react-resizable-handle {
- z-index: 20;
- width: 28px;
- height: 28px;
- bottom: 0;
- right: 0;
- padding: 0;
- background-image: none;
- cursor: se-resize;
- }
- .dashboard-grid .react-grid-item > .react-resizable-handle::after {
- content: "";
- position: absolute;
- right: 7px;
- bottom: 7px;
- width: 13px;
- height: 13px;
- border-right: 3px solid var(--primary);
- border-bottom: 3px solid var(--primary);
- border-bottom-right-radius: 5px;
- opacity: 0.9;
- transition: transform 120ms ease, opacity 120ms ease;
- }
- .dashboard-grid .react-grid-item > .react-resizable-handle:hover::after,
- .dashboard-grid .react-grid-item.resizing > .react-resizable-handle::after {
- transform: scale(1.25);
- opacity: 1;
- }
- .dashboard-grid-editing .react-grid-item {
- user-select: none;
- }
- /* Cards fill their grid tile; the last section (CardContent) scrolls */
- .dashboard-card-wrap {
- height: 100%;
- }
- .dashboard-card-wrap > *:first-child {
- height: 100%;
- display: flex;
- flex-direction: column;
- overflow: hidden;
- }
- /* Legacy Card markup: whatever comes last is the content, so it scrolls.
- Guarded so an AppCard footer (which is also a last child) never becomes
- the scroll area. */
- .dashboard-card-wrap > *:first-child > *:last-child:not([data-slot="app-card-footer"]) {
- flex: 1;
- min-height: 0;
- overflow-y: auto;
- }
- /* AppCard names its content region, so target it directly. */
- .dashboard-card-wrap > *:first-child > [data-slot="app-card-content"] {
- flex: 1;
- min-height: 0;
- overflow-y: auto;
- }
- /* Masks a value without a password input. A password field anywhere in a card
- makes Chrome read the whole card as a sign-in form and offer the user's
- saved email into the text field above it, which is how provider credential
- forms end up autofilled with an address. Masking in CSS keeps the secret
- hidden and leaves the browser nothing to recognise. */
- .masked-value {
- -webkit-text-security: disc;
- text-security: disc;
- }
- /* Toasts in the app's own look, for every toast sonner draws: the glass
- surface and icon tile of the upgrade dialog, and the primary hairline the
- layout runs along the top of the window. One step more specific than
- sonner's own `[data-sonner-toast][data-styled='true']` rules, so these win
- without !important. `--toast-accent` is the one colour that changes with
- the kind of toast. Sonner uses both pseudo-elements itself (swipe and the
- hover gap), so the hairline is a background layer instead. */
- [data-sonner-toaster] [data-sonner-toast][data-styled='true'] {
- --toast-accent: var(--primary);
- background:
- linear-gradient(
- to right,
- var(--toast-accent),
- color-mix(in oklab, var(--toast-accent) 35%, transparent) 45%,
- transparent
- )
- top / 100% 1px no-repeat,
- radial-gradient(
- 9rem 5rem at 1.9rem 50%,
- color-mix(in oklab, var(--toast-accent) 12%, transparent),
- transparent 70%
- ),
- color-mix(in oklab, var(--popover) 90%, transparent);
- -webkit-backdrop-filter: blur(16px);
- backdrop-filter: blur(16px);
- border-color: color-mix(in oklab, var(--border) 70%, transparent);
- border-radius: calc(var(--radius) + 4px);
- box-shadow:
- inset 0 1px 0 rgb(255 255 255 / 0.04),
- 0 18px 40px -16px rgb(0 0 0 / 0.45);
- padding: 12px 14px;
- gap: 12px;
- }
- [data-sonner-toaster] [data-sonner-toast][data-type='error'] {
- --toast-accent: var(--destructive);
- }
- [data-sonner-toaster] [data-sonner-toast][data-type='warning'] {
- --toast-accent: oklch(0.75 0.15 70);
- }
- /* The icon sits in a tile, like the upgrade dialog's */
- [data-sonner-toaster] [data-sonner-toast][data-styled='true'] [data-icon] {
- width: 2rem;
- height: 2rem;
- margin: 0;
- justify-content: center;
- border-radius: 0.5rem;
- color: var(--toast-accent);
- background: linear-gradient(
- to bottom,
- color-mix(in oklab, var(--toast-accent) 16%, transparent),
- color-mix(in oklab, var(--toast-accent) 5%, transparent)
- );
- box-shadow:
- inset 0 0 0 1px color-mix(in oklab, var(--toast-accent) 28%, transparent),
- inset 0 1px 0 rgb(255 255 255 / 0.12);
- }
- [data-sonner-toaster] [data-sonner-toast][data-styled='true'] [data-icon] svg {
- margin: 0;
- }
- [data-sonner-toaster] [data-sonner-toast][data-styled='true'] [data-title] {
- font-weight: 600;
- letter-spacing: -0.01em;
- }
- [data-sonner-toaster] [data-sonner-toast][data-styled='true'] [data-description] {
- color: var(--muted-foreground);
- }
- [data-sonner-toaster] [data-sonner-toast][data-styled='true'] [data-button] {
- background: var(--primary);
- color: var(--primary-foreground);
- border-radius: calc(var(--radius) - 2px);
- font-weight: 500;
- }
- [data-sonner-toaster] [data-sonner-toast][data-styled='true'] [data-cancel] {
- background: transparent;
- color: var(--muted-foreground);
- box-shadow: inset 0 0 0 1px var(--border);
- }
- /* Search palette and settings deep links.
- The spotlight is what a deep link from the palette lands on: the card or
- the field grows a ring in the theme colour that fades over a couple of
- seconds, so the eye is taken to the thing searched for rather than left at
- the top of the page. The progress line sits under the palette's input
- while the workshop's records are being fetched; local matches arrive on
- the keystroke itself and never wait for it. */
- @keyframes settings-spotlight {
- 0% {
- box-shadow: 0 0 0 0 color-mix(in oklab, var(--primary) 0%, transparent);
- }
- 20% {
- box-shadow:
- 0 0 0 3px color-mix(in oklab, var(--primary) 55%, transparent),
- 0 0 28px 6px color-mix(in oklab, var(--primary) 22%, transparent);
- }
- 100% {
- box-shadow:
- 0 0 0 3px color-mix(in oklab, var(--primary) 0%, transparent),
- 0 0 0 0 color-mix(in oklab, var(--primary) 0%, transparent);
- }
- }
- .settings-spotlight {
- animation: settings-spotlight 2.4s cubic-bezier(0.2, 0.7, 0.2, 1) 1;
- border-radius: max(var(--radius), 6px);
- }
- @keyframes search-progress {
- 0% {
- transform: translateX(-100%);
- }
- 100% {
- transform: translateX(300%);
- }
- }
- .search-progress {
- position: absolute;
- inset-inline: 0;
- bottom: -1px;
- height: 2px;
- overflow: hidden;
- pointer-events: none;
- }
- .search-progress::after {
- content: '';
- position: absolute;
- inset-block: 0;
- left: 0;
- width: 33%;
- border-radius: 999px;
- background: linear-gradient(90deg, transparent, var(--primary), transparent);
- animation: search-progress 1.1s ease-in-out infinite;
- }
- /* Loading skeletons: a soft wave across the placeholder instead of a blink.
- Built from currentColor, so it follows the text colour in light, dark and
- every colour theme without a token of its own. In the components layer, so
- a page can still restyle one skeleton with utilities. With reduced motion
- the wave stands still. */
- :root {
- --skeleton-base: color-mix(in srgb, currentColor 9%, transparent);
- --skeleton-highlight: color-mix(in srgb, currentColor 20%, transparent);
- --skeleton-bg: linear-gradient(
- 90deg,
- var(--skeleton-base) 0%,
- var(--skeleton-highlight) 50%,
- var(--skeleton-base) 100%
- );
- }
- @keyframes skeleton-wave {
- 0% {
- background-position: 200% 0;
- }
- 100% {
- background-position: -200% 0;
- }
- }
- @media (prefers-reduced-motion: reduce) {
- @keyframes skeleton-wave {
- 0%,
- 100% {
- background-position: 50% 0;
- }
- }
- }
- @layer components {
- .skeleton {
- background: var(--skeleton-bg);
- background-size: 200% 100%;
- animation: skeleton-wave 1.6s linear infinite;
- }
- }
|