restructure theme css

This commit is contained in:
2026-07-17 16:58:16 -04:00
parent 35f0ea6db3
commit 03b5bea72d
11 changed files with 117 additions and 166 deletions

View File

@@ -43,6 +43,13 @@ writes CSS, and the text is just as likely to be Go (the gowasm kit writes its m
Go and has no JS build at all). Keeping it in the bundler made every Go-only consumer Go and has no JS build at all). Keeping it in the bundler made every Go-only consumer
drag a JavaScript bundler along for a CSS file. drag a JavaScript bundler along for a CSS file.
`tw` also owns the **shared design system** as CSS, in three files: `tw_theme.css` and
`tw_preflight.css` are Tailwind's own defaults, and `tw/kjol_theme.css` is the kjol
extension layer — the semantic tokens (`surface`/`line`/`ink`/…), the class-based `dark`
variant and the chart palette that BOTH kits name. `tw.CompileApp` stacks them: base
Tailwind → kjol extensions → the app's brand `style.css`. So neither kit ships the tokens
in its own tree; both inherit them from the compiler, and an app carries only brand.
### go/ — module `kjol` ### go/ — module `kjol`
Packages, imported as `kjol/<name>`: `appenv basic chrono config csv dbutil finance httputil Packages, imported as `kjol/<name>`: `appenv basic chrono config csv dbutil finance httputil
@@ -125,8 +132,8 @@ tokens).
`@custom-variant dark (&:where(.dark, .dark *));` is required — the built-in `dark` variant is a `@custom-variant dark (&:where(.dark, .dark *));` is required — the built-in `dark` variant is a
`prefers-color-scheme` media query, which a site with its own switch cannot use (the OS says one `prefers-color-scheme` media query, which a site with its own switch cannot use (the OS says one
thing, the switch says another, and the media query wins). For gowasm the app defines the tokens thing, the switch says another, and the media query wins). For gowasm the app defines the tokens
(see `webui.ThemeTokens`); for the Solid kit `jsruntime/styles/theme.css` defines them and the (see `webui.ThemeTokens`); for the Solid kit `tw/kjol_theme.css` defines them and the Tailwind
bundler prepends it, so the app's `style.css` carries brand only. engine prepends it (`tw.CompileApp`), so the app's `style.css` carries brand only.
Controllers: `webui.Theme` (Go) and `jsruntime/uikit/Theme.tsx` (Solid) — both read the same Controllers: `webui.Theme` (Go) and `jsruntime/uikit/Theme.tsx` (Solid) — both read the same
`kjol-theme` localStorage key, so a preference survives crossing between two front-ends. `kjol-theme` localStorage key, so a preference survives crossing between two front-ends.
@@ -145,10 +152,6 @@ get a white flash until the bundle loads. It is the only hand-written JavaScript
registry; the generated file is app-owned, not committed here). kjol ships only the SUBSET its registry; the generated file is app-owned, not committed here). kjol ships only the SUBSET its
own kit + `kjol-website` reference. An app's own `frontend/icons` is searched FIRST, so an app with own kit + `kjol-website` reference. An app's own `frontend/icons` is searched FIRST, so an app with
a fuller kit keeps it — see `jsbundler.iconsDirs`. a fuller kit keeps it — see `jsbundler.iconsDirs`.
- `styles/theme.css` — the `@theme` scaffold, the semantic tokens, the `.dark` overrides, and the
`:root` fa vars. **It does the `@import "tailwindcss"`**, because the bundler PREPENDS it to the
app's `style.css` and an `@import` has to come first. An app adopting the shared tree therefore
drops that import from its own stylesheet and keeps only brand.
- `auth/ utils/ hooks/ ssr/ env.ts basic.ts finance.ts superfun.ts types.d.ts` — generic TS - `auth/ utils/ hooks/ ssr/ env.ts basic.ts finance.ts superfun.ts types.d.ts` — generic TS
scaffolding. Apps import as `@kjol/*`. (Concrete permission constants stay app-side.) scaffolding. Apps import as `@kjol/*`. (Concrete permission constants stay app-side.)

View File

@@ -1,17 +1,17 @@
@import "tailwindcss";
/* --------------------------------------------------------------------------- /* ---------------------------------------------------------------------------
Dark mode: `dark:` as a CLASS, not a media query. kjol-website — brand stylesheet for the Kjol Go/WASM section (/wasm/*).
--------------------------------------------------------------------------- ---------------------------------------------------------------------------
Tailwind's built-in dark variant follows the operating system. A site with its own There is deliberately no `@import "tailwindcss"` and no `@custom-variant dark`
theme switch cannot use it: the OS says one thing, the switch says another, and the here. twcss compiles this file via tw.CompileAppFiles, which prepends kjol's
media query wins — so the switch appears to do nothing. shared extension layer (tw/kjol_theme.css) — the import, the class-based dark
variant, the semantic tokens (surface/line/ink/…), and the chart palette. This
file carries only what is genuinely this app's: the brand.
This redefines it against a class on <html>, which webui.Theme toggles. The OS is The values below match the /js side's frontend/css/style.css on purpose — same
still respected: it is the DEFAULT (see the boot script in server/main.go), just no Open Sans, same navy accent — so that crossing between /wasm and /js reads as two
longer the last word. parts of ONE site rather than two demos that happen to share a domain. The two
sections are built by completely different pipelines; they should not look like it.
--------------------------------------------------------------------------- */ --------------------------------------------------------------------------- */
@custom-variant dark (&:where(.dark, .dark *));
/* --------------------------------------------------------------------------- /* ---------------------------------------------------------------------------
Open Sans — self-hosted (files in wwwroot/fonts). One variable file per subset Open Sans — self-hosted (files in wwwroot/fonts). One variable file per subset
@@ -53,13 +53,9 @@
unicode-range: U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF, U+0304, U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F, U+A720-A7FF; unicode-range: U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF, U+0304, U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F, U+A720-A7FF;
} }
/* App-side design tokens the webui kit references (Tailwind v4 @theme). Brand /* The brand — the values the shared kit's tokens are re-pointed to. Everything the
values live with the app; the kit stays generic. kit already defines (surfaces, lines, ink, the chart palette, the state colours)
comes from tw/kjol_theme.css; this block sets only what is this site's own. */
The surface/line/ink tokens are the kit's THEME CONTRACT (see webui.ThemeTokens):
components say bg-surface / border-line / text-ink and never name a colour, so the
whole kit changes theme by changing these ten values rather than by carrying a dark:
variant on four hundred class strings. */
@theme { @theme {
--radius-default: 0.375rem; --radius-default: 0.375rem;
@@ -71,8 +67,7 @@
/* Navy and red — the flag, muted. kjøl is a Norwegian word and the palette says so. /* Navy and red — the flag, muted. kjøl is a Norwegian word and the palette says so.
Both are dark and low-key: the page is mostly prose, code and tables, and the brand's Both are dark and low-key: the page is mostly prose, code and tables, and the brand's
job is to mark the few things you can act on, not to compete with them for attention. job is to mark the few things you can act on, not to compete with them for attention. */
(The previous sky blue did the same job, but said nothing.) */
--color-primary: #1e3a63; /* muted navy — FILLS; they carry white text */ --color-primary: #1e3a63; /* muted navy — FILLS; they carry white text */
--color-primary-hover: #16294a; --color-primary-hover: #16294a;
--color-primary-subtle: #eef2f8; /* a navy wash — tinted panels, badges, callouts */ --color-primary-subtle: #eef2f8; /* a navy wash — tinted panels, badges, callouts */
@@ -82,44 +77,12 @@
separate token from primary, because the two have opposite constraints: a fill must be separate token from primary, because the two have opposite constraints: a fill must be
dark enough for white text on TOP of it, and accent text must be readable ON the dark enough for white text on TOP of it, and accent text must be readable ON the
surface. Here they are the same hue — navy — but not the same value: the accent is a surface. Here they are the same hue — navy — but not the same value: the accent is a
touch deeper so a navy link on white is unmistakably a link. (The red is gone; the touch deeper so a navy link on white is unmistakably a link. */
brand is navy throughout now. Only the flag keeps its red field.) */
--color-accent: #1c3a66; /* navy — accent TEXT */ --color-accent: #1c3a66; /* navy — accent TEXT */
/* Surfaces, lines, ink — the kit's theme contract. */ /* Heading ink — a touch stronger than body ink. Not part of the shared contract, so
--color-surface: #ffffff; the site names it here and re-points it in the .dark block below. */
--color-surface-muted: #fafafa;
--color-surface-raised: #f5f5f5;
--color-surface-strong: #e5e5e5;
--color-line: #e5e5e5;
--color-line-strong: #d4d4d4;
--color-ink: #171717;
--color-ink-soft: #525252;
--color-ink-muted: #737373;
--color-ink-faint: #a3a3a3;
--color-text-heading: #111827; --color-text-heading: #111827;
--color-text-on-dark: #f9fafb;
--color-text-on-dark-muted: #9ca3af;
/* The categorical CHART palette (webui/chart.go) and the sequential choropleth ramp
(webui/usheatmap.go) — the SAME tokens the Solid kit names in jsruntime/styles/
theme.css, so a chart looks identical on both front-ends. Marks name these as raw
CSS variables in the SVG they draw. Dark values in the .dark block below. */
--color-chart-1: #2a78d6;
--color-chart-2: #1baf7a;
--color-chart-3: #eda100;
--color-chart-4: #008300;
--color-chart-5: #4a3aa7;
--color-chart-6: #e34948;
--color-chart-7: #e87ba4;
--color-chart-8: #eb6834;
--color-choropleth-1: #dbe9fb;
--color-choropleth-2: #b3d0f6;
--color-choropleth-3: #85b3ee;
--color-choropleth-4: #5591e4;
--color-choropleth-5: #2f6fca;
--color-choropleth-6: #124f8f;
} }
/* --------------------------------------------------------------------------- /* ---------------------------------------------------------------------------
@@ -139,37 +102,15 @@
--grid-line: rgba(30, 58, 99, 0.06); /* the navy, at the edge of visible */ --grid-line: rgba(30, 58, 99, 0.06); /* the navy, at the edge of visible */
} }
/* --------------------------------------------------------------------------- /* The dark values for the brand.
The dark theme.
--------------------------------------------------------------------------- ---------------------------------------------------------------------------
Only the token VALUES change. Not one component knows this block exists — they ask The shared surfaces/lines/ink re-point themselves in tw/kjol_theme.css's own .dark
for bg-surface and text-ink, and here is where those come to mean something else. block; this one moves only the site's brand. Navy is too dark to read on a near-black
page, so both brand tokens climb to a lighter blue. The accent (TEXT) climbs furthest,
This is a plain rule, not another @theme block: @theme generates utilities, and these to a soft sky a link stays legible in; the fill climbs less, to a steel blue that still
are overrides of utilities that already exist. looks like a button and still carries white text. The tinted panel inverts outright,
because a pale wash on #101013 is not a tint, it is a white box. */
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 lines above it to actually be seen. --------------------------- */
.dark { .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;
/* Both brand tokens move in the dark, and both for the same reason now: navy is too dark
to read on a near-black page, so each climbs to a lighter blue.
The accent (TEXT) climbs furthest — a link has to be legible at body-text weight, so it
goes to a soft sky. The fill climbs less: it only has to look like a button and still
carry white text (~7:1), so it lifts to a steel blue and stops there, well below where
the accent lands. */
--color-accent: #9fc1ec; --color-accent: #9fc1ec;
--color-primary: #2b4f80; --color-primary: #2b4f80;
--color-primary-hover: #37619b; --color-primary-hover: #37619b;
@@ -180,30 +121,13 @@
/* The grid is drawn in ink, not in shadow, once the page is dark. */ /* The grid is drawn in ink, not in shadow, once the page is dark. */
--grid-line: rgba(226, 232, 240, 0.05); --grid-line: rgba(226, 232, 240, 0.05);
/* Chart palette re-stepped for the dark surface; choropleth inverts its lightness
direction so a high value reads as brighter. Mirrors the Solid scaffold's .dark. */
--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;
--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 the app mounts, and behind it afterwards. /* Only the font. The page's background and text colour come from the shared layer's
Without this, a dark app sits in a white window. */ `html` rule, which paints them from --color-surface / --color-ink — the tokens the
.dark block re-points. Setting them here would pin the page to white and leave a dark
app sitting in a white window. */
html { html {
background-color: var(--color-surface);
color: var(--color-ink);
font-family: var(--font-sans); font-family: var(--font-sans);
} }
@@ -258,7 +182,3 @@ html {
-webkit-mask-image: linear-gradient(to bottom, #000, #000 35%, transparent 100%); -webkit-mask-image: linear-gradient(to bottom, #000, #000 35%, transparent 100%);
mask-image: linear-gradient(to bottom, #000, #000 35%, transparent 100%); mask-image: linear-gradient(to bottom, #000, #000 35%, transparent 100%);
} }
/* (The hero glow that used to live here went with the hero. A coloured wash behind an
oversized headline is the most recognisable gesture in framework marketing, and this
page is not making that argument any more.) */

View File

@@ -1,10 +1,10 @@
/* --------------------------------------------------------------------------- /* ---------------------------------------------------------------------------
kjol-website — brand stylesheet for the Kjol JS Web section (/js/*). kjol-website — brand stylesheet for the Kjol JS Web section (/js/*).
--------------------------------------------------------------------------- ---------------------------------------------------------------------------
There is deliberately no `@import "tailwindcss"` here. The bundler PREPENDS There is deliberately no `@import "tailwindcss"` here. The bundler compiles this
kjol's shared scaffold (go/jsruntime/styles/theme.css) to this file, and that file via tw.CompileApp, which PREPENDS kjol's shared extension layer
scaffold does the import — an @import has to come first, and this file no (tw/kjol_theme.css); that layer does the import — an @import has to come first,
longer is. See jsbundler/css.go and the header of theme.css. and this file no longer is. See jsbundler/css.go and the header of kjol_theme.css.
What is left is only what is genuinely this app's: the brand. What is left is only what is genuinely this app's: the brand.

View File

@@ -44,8 +44,9 @@ func main() {
"app", "wasm", "css", // the Go/WASM app "app", "wasm", "css", // the Go/WASM app
"frontend", // the Solid app — a .tsx save rebuilds the JS bundle "frontend", // the Solid app — a .tsx save rebuilds the JS bundle
"../../webui", "../../wasmruntime", // the wasm engine (vdom + rsc now live UNDER wasmruntime, and the watcher walks subdirs) "../../webui", "../../wasmruntime", // the wasm engine (vdom + rsc now live UNDER wasmruntime, and the watcher walks subdirs)
"../../lexer", // the code-block highlighter "../../lexer", // the code-block highlighter
"../../jsruntime/uikit", "../../jsruntime/styles", // the Solid kit + the shared theme "../../jsruntime/uikit", // the Solid kit
"../../tw", // the Tailwind engine + the shared kjol extension layer (kjol_theme.css)
}, },
Build: build.All, Build: build.All,
BuildCSS: build.Tailwind, // a .css save skips codegen+wasm and hot-swaps the stylesheet BuildCSS: build.Tailwind, // a .css save skips codegen+wasm and hot-swaps the stylesheet

View File

@@ -1,8 +1,12 @@
// Command twcss compiles a Tailwind v4 stylesheet with kjol's native engine, // Command twcss compiles a kjol app's Tailwind stylesheet with kjol's native
// scanning explicit content globs for utility candidates. Unlike the app bundler // engine, scanning explicit content globs for utility candidates. Unlike the app
// (which is wired to the frontend tree) it takes the entry, output, and content // bundler (which is wired to the frontend tree) it takes the entry, output, and
// globs as flags/args, so it works for markup authored in any language — used by // content globs as flags/args, so it works for markup authored in any language —
// the kjol-website site, whose Go/WASM half writes its UI in Go. // used by the kjol-website site, whose Go/WASM half writes its UI in Go.
//
// It compiles via tw.CompileAppFiles, so the -entry stylesheet is layered onto
// kjol's shared extension layer: base Tailwind → kjol extensions → this stylesheet.
// The entry therefore carries only brand and names none of the shared tokens.
// //
// Usage (globs are relative to -base; pass "**" for a recursive walk): // Usage (globs are relative to -base; pass "**" for a recursive walk):
// //
@@ -32,7 +36,7 @@ func main() {
fmt.Fprintln(os.Stderr, "twcss:", err) fmt.Fprintln(os.Stderr, "twcss:", err)
os.Exit(1) os.Exit(1)
} }
css, err := tw.CompileFiles(string(src), *base, flag.Args()) css, err := tw.CompileAppFiles(string(src), *base, flag.Args())
if err != nil { if err != nil {
fmt.Fprintln(os.Stderr, "twcss:", err) fmt.Fprintln(os.Stderr, "twcss:", err)
os.Exit(1) os.Exit(1)

View File

@@ -94,14 +94,5 @@ func vendorDirs() []string {
return []string{filepath.Join(frontendDir, "vendor")} return []string{filepath.Join(frontendDir, "vendor")}
} }
// themeCSSPath is the shared Tailwind @theme scaffold prepended to the app's
// brand style.css, or "" in single-tree mode (the app's style.css is complete).
func themeCSSPath() string {
if webDir != "" {
return filepath.Join(webDir, "styles", "theme.css")
}
return ""
}
// faOutPath is where the generated FA registry is written (app-owned). // faOutPath is where the generated FA registry is written (app-owned).
func faOutPath() string { return filepath.Join(genTSDir, "faIcons.ts") } func faOutPath() string { return filepath.Join(genTSDir, "faIcons.ts") }

View File

@@ -78,17 +78,10 @@ func compileCSSBundle(label string, twSources []string, outName string) (bundleS
kitCands := tw.Scan(uikitDir(), []string{"**/*.ts", "**/*.tsx", "**/*.js", "**/*.jsx"}) kitCands := tw.Scan(uikitDir(), []string{"**/*.ts", "**/*.tsx", "**/*.js", "**/*.jsx"})
candidates = dedupStrings(append(candidates, kitCands...)) candidates = dedupStrings(append(candidates, kitCands...))
// Prepend the shared @theme scaffold (kjol jsruntime/styles/theme.css) ahead of the // CompileApp prepends kjol's shared extension layer (tokens, the class-based dark
// app's brand style.css so its tokens/vars are in scope. Absent in single-tree // variant, the chart palette) ahead of the app's brand style.css, so the app
// mode (the app's style.css is already complete). // stylesheet carries only brand: base Tailwind → kjol extensions → app brand.
input := string(src) compiled, count, err := tw.CompileApp(string(src), cssDir, candidates)
if tp := themeCSSPath(); tp != "" {
if theme, e := os.ReadFile(tp); e == nil {
input = string(theme) + "\n" + input
}
}
compiled, count, err := tw.Compile(input, cssDir, candidates)
if err != nil { if err != nil {
return bundleStats{}, fmt.Errorf("tailwind compile (%s): %w", label, err) return bundleStats{}, fmt.Errorf("tailwind compile (%s): %w", label, err)
} }

View File

@@ -1,6 +1,10 @@
// Package jsruntime is the JS/TS tree of kjol: the Solid component kit, the // Package jsruntime is the JS/TS tree of kjol: the Solid component kit, the
// vendored Solid runtime, the FontAwesome SVG source kit, the shared Tailwind // vendored Solid runtime, the FontAwesome SVG source kit, and the generic TS
// @theme scaffold, and the generic TS scaffolding (auth, hooks, ssr, utils). // scaffolding (auth, hooks, ssr, utils).
//
// The shared Tailwind design tokens are NOT here — they live in kjol/tw as the
// extension layer (tw/kjol_theme.css), which the Tailwind engine layers between
// its own defaults and an app's brand. See tw.CompileApp.
// //
// It holds no Go beyond this file. The package exists so the tree has a stable // It holds no Go beyond this file. The package exists so the tree has a stable
// home inside the Go module rather than a sibling directory the build has to go // home inside the Go module rather than a sibling directory the build has to go
@@ -26,9 +30,6 @@
// per-app registry (@appgen/faIcons); the registry is app-owned and is // per-app registry (@appgen/faIcons); the registry is app-owned and is
// not committed here. An app's own frontend/icons is searched first, so // not committed here. An app's own frontend/icons is searched first, so
// an app with a fuller kit keeps it (see jsbundler.iconsDirs). // an app with a fuller kit keeps it (see jsbundler.iconsDirs).
// styles/ theme.css — the @theme scaffold + :root fa vars, prepended to the
// app's brand style.css by the bundler. Brand color/font tokens stay
// app-side.
// //
// The rest (auth/ hooks/ ssr/ utils/ env.ts basic.ts finance.ts superfun.ts) is // The rest (auth/ hooks/ ssr/ utils/ env.ts basic.ts finance.ts superfun.ts) is
// generic TS scaffolding; apps import it as @kjol/*. Concrete permission // generic TS scaffolding; apps import it as @kjol/*. Concrete permission

View File

@@ -1,16 +1,22 @@
/* --------------------------------------------------------------------------- /* ---------------------------------------------------------------------------
kjol shared Tailwind v4 scaffold for the JS (Solid) kit. kjol the shared Tailwind extension layer.
--------------------------------------------------------------------------- ---------------------------------------------------------------------------
The bundler PREPENDS this file to the app's own css/style.css (see Three layers stack to make a kjol app's stylesheet, in this order:
jsbundler/css.go), so it is the first thing the Tailwind engine sees. That is
why the `@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.
An app adopting the shared tree therefore DROPS `@import "tailwindcss"` from base Tailwind (tw_theme.css + tw_preflight.css) the framework defaults
its own style.css and keeps only what is genuinely its own its brand palette, kjol extensions (THIS FILE) the shared design system
its fonts, its one-off rules. In single-tree mode (WebDir unset) this file is app brand (the app's own style.css) colours, fonts, one-offs
not prepended at all and the app's style.css stays complete, which is why
cdrateline and Hotlap still import Tailwind themselves. 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"; @import "tailwindcss";
@@ -23,8 +29,9 @@
the media query wins, and the button appears to do nothing. the media query wins, and the button appears to do nothing.
This redefines the variant against a class on <html>, which the Theme controller This redefines the variant against a class on <html>, which the Theme controller
(uikit/Theme.tsx) toggles. The OS is still respected it is the DEFAULT, applied (uikit/Theme.tsx, webui.Theme) toggles. The OS is still respected it is the
by the boot script before first paint just no longer the last word. DEFAULT, applied by the boot script before first paint just no longer the last
word.
--------------------------------------------------------------------------- */ --------------------------------------------------------------------------- */
@custom-variant dark (&:where(.dark, .dark *)); @custom-variant dark (&:where(.dark, .dark *));
@@ -118,9 +125,9 @@
they sit on a dark bar or panel in an otherwise light page so these stay 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. dark in BOTH themes and are not overridden below.
They were named by the components (bg-dark, text-text-on-dark, ) and They are named by the components (bg-dark, text-text-on-dark, ); defining
defined by nobody, so until now every one of them compiled to no colour at them here is what makes those onDark paths render styled rather than with no
all and the onDark paths silently rendered unstyled. */ colour at all. */
--color-dark: #101013; --color-dark: #101013;
--color-dark-raised: #1f1f24; --color-dark-raised: #1f1f24;
--color-text-on-dark: #f9fafb; --color-text-on-dark: #f9fafb;

View File

@@ -1269,6 +1269,14 @@ var defaultThemeCSS string
//go:embed tw_preflight.css //go:embed tw_preflight.css
var defaultPreflightCSS string var defaultPreflightCSS string
// kjolThemeCSS is the shared kjol extension layer — the semantic design tokens
// (surface/line/ink/...), the class-based dark variant, the chart palette and the
// :root icon vars. It sits between Tailwind's defaults and an app's brand, and is
// prepended to the app entry stylesheet by CompileApp / CompileAppFiles.
//
//go:embed kjol_theme.css
var kjolThemeCSS string
func parseThemeOptions(params string) ThemeOptions { func parseThemeOptions(params string) ThemeOptions {
o := themeNone o := themeNone
for _, f := range strings.Fields(params) { for _, f := range strings.Fields(params) {

View File

@@ -55,5 +55,28 @@ func CompileFiles(entryCSS, baseDir string, sourceGlobs []string) (string, error
return Minify(compiled) return Minify(compiled)
} }
// CompileApp is Compile with kjol's shared extension layer prepended to entryCSS —
// the layering every kjol front-end (Solid and gowasm) shares:
//
// base Tailwind → kjol extensions (kjol_theme.css) → the app's brand entryCSS
//
// The kjol layer does the `@import "tailwindcss"` and defines the semantic tokens,
// the class-based dark variant and the chart palette, so entryCSS carries only
// brand. Compile itself stays a plain Tailwind engine (no kjol tokens) for callers
// that want exactly that.
func CompileApp(entryCSS, baseDir string, candidates []string) (css string, utilities int, err error) {
return twCompile(kjolThemeCSS+"\n"+entryCSS, baseDir, candidates)
}
// CompileAppFiles is Scan + CompileApp + minify: CompileFiles with the kjol
// extension layer prepended.
func CompileAppFiles(entryCSS, baseDir string, sourceGlobs []string) (string, error) {
compiled, _, err := CompileApp(entryCSS, baseDir, scanSources(baseDir, sourceGlobs))
if err != nil {
return "", err
}
return Minify(compiled)
}
// Minify shrinks compiled CSS. // Minify shrinks compiled CSS.
func Minify(css string) (string, error) { return minifier.String("text/css", css) } func Minify(css string) (string, error) { return minifier.String("text/css", css) }