/* --------------------------------------------------------------------------- kjol — the shared Tailwind extension layer. --------------------------------------------------------------------------- Three layers stack to make a kjol app's stylesheet, in this order: base Tailwind (tw_theme.css + tw_preflight.css) the framework defaults kjol extensions (THIS FILE) the shared design system app brand (the app's own style.css) colours, fonts, one-offs tw.CompileApp / tw.CompileAppFiles prepend this file to the app's entry stylesheet, so the Tailwind engine sees it before the app's brand. That is why `@import "tailwindcss"` lives here rather than in the app's stylesheet: an @import has to come first, and the app's file no longer is. A kjol app therefore carries only brand — its palette, its fonts, its own rules — and names none of the tokens below; it inherits them. The vocabulary here is shared with the Go/WASM kit (webui.ThemeTokens) on purpose: two kits, one contract. A designer changes `surface` once and both layers of a site move together. --------------------------------------------------------------------------- */ @import "tailwindcss"; /* --------------------------------------------------------------------------- Dark mode: `dark:` as a CLASS, not a media query. --------------------------------------------------------------------------- Tailwind's built-in dark variant follows the operating system. A site with its own theme switch cannot use it: the OS says one thing, the switch says another, the media query wins, and the button appears to do nothing. This redefines the variant against a class on , which the Theme controller (uikit/Theme.tsx, webui.Theme) toggles. The OS is still respected — it is the DEFAULT, applied by the boot script before first paint — just no longer the last word. --------------------------------------------------------------------------- */ @custom-variant dark (&:where(.dark, .dark *)); /* Icon family for uikit/Icons.tsx, read at runtime via getComputedStyle: --fa-style: classic → far/fas, sharp → fasr/fass; --fa-default-solid: 0|1. Kept in :root (not @theme) so Tailwind doesn't tree-shake it as unused — it is never named by a utility class, so nothing would mark it as reachable. */ :root { --fa-style: classic; --fa-default-solid: 0; } @theme { /* Custom text sizes */ --text-ss: 0.8rem; /* Override: Tailwind default is 0.75rem */ /* Custom breakpoints (Tailwind defaults + xs + ultrawide) */ --breakpoint-xs: 375px; --breakpoint-sm: 640px; --breakpoint-md: 768px; --breakpoint-lg: 1024px; --breakpoint-xl: 1280px; --breakpoint-2xl: 1536px; --breakpoint-ultrawide: 3840px; /* Custom containers */ --container-8xl: 88rem; --container-9xl: 96rem; --container-10xl: 104rem; --container-ultrawide: 240rem; /* Custom radius */ --radius-default: var(--radius-sm); /* ---- THE THEME CONTRACT ------------------------------------------------ Every component in the kit says bg-surface / border-line / text-ink and NEVER names a colour. These are the tokens it is allowed to name. That is the whole trick. A theme is the twenty values below, not a `dark:` variant on four hundred class strings — so switching to dark is a rule that re-points these, and not one component knows it happened. The vocabulary is deliberately identical to the Go/WASM kit's (webui. ThemeTokens). Two kits, one contract: a designer changes `surface` once and both layers of the site move together. */ /* Surfaces, in ascending prominence. */ --color-surface: #ffffff; --color-surface-muted: #fafafa; --color-surface-raised: #f5f5f5; --color-surface-strong: #e5e5e5; /* Lines. */ --color-line: #e5e5e5; --color-line-strong: #d4d4d4; /* Ink, in descending prominence. */ --color-ink: #171717; --color-ink-soft: #525252; --color-ink-muted: #737373; --color-ink-faint: #a3a3a3; /* The solid neutral button. It INVERTS: a dark button on a light page, a light button on a dark one. That is why it is three tokens and not `bg-ink` — the text on top of it has to invert with it, and `text-white` would vanish the moment the fill went pale. */ --color-fill-neutral: #404040; --color-fill-neutral-hover: #262626; --color-on-fill-neutral: #ffffff; /* The accent, in four parts, because a single "brand colour" cannot do four jobs. Same names as the Go kit (webui.ThemeTokens) — one vocabulary, two kits. primary is a FILL: it carries white text, so it has to stay dark enough for white to sit on it. accent is TEXT and icons: it has to stay readable ON the surface. One value cannot be both, and in dark mode they diverge completely — the fill stays put while the text has to climb. primary-subtle / primary-border are the tinted panel: a callout, a selected row. Brand supplies the values; see the app's style.css. These defaults keep the kit legible before an app has themed anything, NOT so that it can skip theming. */ --color-primary: #404040; --color-primary-hover: #262626; --color-primary-subtle: #f5f5f5; --color-primary-border: #d4d4d4; --color-accent: #262626; /* ---- deliberately-dark chrome (the kit's `onDark` prop) ---------------- Not the same thing as dark MODE. Several components take onDark, for when they sit on a dark bar or panel in an otherwise light page — so these stay dark in BOTH themes and are not overridden below. They are named by the components (bg-dark, text-text-on-dark, …); defining them here is what makes those onDark paths render styled rather than with no colour at all. */ --color-dark: #101013; --color-dark-raised: #1f1f24; --color-text-on-dark: #f9fafb; --color-text-on-dark-muted: #9ca3af; --color-text-on-dark-faint: #6b7280; --color-border-on-dark: #2a2a30; /* State colours. These DO name a colour, because they mean one: an alert that is not red is not an alert. Their tinted BACKGROUNDS are the one place the kit still carries a `dark:` variant — a red-50 wash is invisible on a near black surface, and no re-pointed token can fix that. */ --color-info: var(--color-sky-500); --color-on-info: var(--color-white); --color-success: var(--color-green-500); --color-on-success: var(--color-white); --color-warning: var(--color-amber-500); --color-on-warning: var(--color-white); --color-danger: var(--color-red-500); --color-on-danger: var(--color-white); /* ---- the categorical CHART palette ------------------------------------- Eight fixed slots for uikit/Chart.tsx. A series is painted by its INDEX (slot 1, 2, 3 …) and never by a cycled/derived hue — a ninth series folds into "Other" rather than inventing a colour. Unlike the state colours these are a set, tuned as a set: the ordering maximises the minimum colour-blind ΔE between neighbouring slots, so adjacent series stay distinguishable under protanopia/deuteranopia (worst adjacent ΔE 24 in light, 10 in dark — see the data-viz palette validator). These CAN be named in a component because a chart is SVG and SVG fills read CSS variables (a canvas cannot, so it would need a literal hex). The dark column below is the same eight hues stepped for the near-black surface, not a second palette. Brand overrides by redefining these (or passing `palette` to a single chart). */ --color-chart-1: #2a78d6; /* blue */ --color-chart-2: #1baf7a; /* aqua */ --color-chart-3: #eda100; /* yellow */ --color-chart-4: #008300; /* green */ --color-chart-5: #4a3aa7; /* violet */ --color-chart-6: #e34948; /* red */ --color-chart-7: #e87ba4; /* magenta */ --color-chart-8: #eb6834; /* orange */ /* Sequential ramp for the choropleth (uikit/USHeatmap): one hue, light→dark, six steps low→high. Unlike the categorical slots it means MAGNITUDE, so it is a single blue stepped by lightness. Dark mode re-points it below: on a near-black surface a high value must read as BRIGHTER, not darker, so the ramp inverts its lightness direction while keeping the same hue. */ --color-choropleth-1: #dbe9fb; --color-choropleth-2: #b3d0f6; --color-choropleth-3: #85b3ee; --color-choropleth-4: #5591e4; --color-choropleth-5: #2f6fca; --color-choropleth-6: #124f8f; } /* --------------------------------------------------------------------------- The dark theme. --------------------------------------------------------------------------- Only the token VALUES change. Not one component knows this block exists — they ask for bg-surface and text-ink, and here is where those come to mean something else. This is a plain rule, not another @theme block: @theme GENERATES utilities, and these are overrides of utilities that already exist. The surfaces are not pure black. Black gives a dark UI a hard, glaring edge against white text and makes every border invisible; a very dark grey leaves room for the raised surfaces and the lines above it to actually be seen. --------------------------------------------------------------------------- */ .dark { --color-surface: #101013; --color-surface-muted: #17171b; --color-surface-raised: #1f1f24; --color-surface-strong: #2c2c33; --color-line: #2a2a30; --color-line-strong: #3d3d45; --color-ink: #f2f2f3; --color-ink-soft: #c6c6cc; --color-ink-muted: #9a9aa3; --color-ink-faint: #71717a; /* Inverted, as promised above: pale fill, dark text. */ --color-fill-neutral: #e5e5e5; --color-fill-neutral-hover: #ffffff; --color-on-fill-neutral: #171717; /* The fill stays where it is — white text still has to be readable on it — but the tinted panel and the accent TEXT both have to move, or they vanish into a near black surface. This is exactly why they are separate tokens. */ --color-primary-subtle: #1f1f24; --color-primary-border: #3d3d45; --color-accent: #e5e5e5; /* The chart palette, re-stepped for the dark surface. Same eight hues, lifted into the dark-mode lightness band so every slot clears 3:1 against #101013 — a light-mode aqua or yellow would sink into the surface. Validated as a set against the dark surface, exactly as the light column was against white. */ --color-chart-1: #3987e5; --color-chart-2: #199e70; --color-chart-3: #c98500; --color-chart-4: #008300; --color-chart-5: #9085e9; --color-chart-6: #e66767; --color-chart-7: #d55181; --color-chart-8: #d95926; /* Same blue hue, stepped for the dark surface and inverted in direction: step 1 (low) is the dimmest, step 6 (high) the brightest, so "more" reads as "brighter". */ --color-choropleth-1: #1b2a44; --color-choropleth-2: #21406c; --color-choropleth-3: #2c5f97; --color-choropleth-4: #3f80c8; --color-choropleth-5: #649de8; --color-choropleth-6: #93c2f7; } /* The page's own background — painted before anything mounts, and behind it afterwards. Without this a dark app sits in a white window. */ html { background-color: var(--color-surface); color: var(--color-ink); }