Redesign marketing homepage and add theme system (deploy preview)#126
Open
developit wants to merge 12 commits into
Open
Redesign marketing homepage and add theme system (deploy preview)#126developit wants to merge 12 commits into
developit wants to merge 12 commits into
Conversation
Adds a kinu theme that matches Claude Code web's design system, derived by scraping the canonical CSS variables and `.df-*` rules from the live app (CSSOM-walking + the published desktop CSS). What's included: - demo/src/themes/claude-code.css — token overrides + minimal per-component tweaks (segmented-control sizing, menu/list row density, surface shadow recipe), light + dark via @media + .dark - demo/src/claude-code-toggle.tsx — header button toggling data-theme="claude-code" on <html>, persisted in localStorage - demo/src/nav.tsx, demo/src/style.css — wire it in Highlights: - Maps Anthropic CDS v2 tokens (bg-100, text-100, brand-100, etc.) onto kinu's --k-* HSL tuples, both light and dark - Selector specificity bumped to :root[data-theme="claude-code"] so it beats the duplicated `:root` rules from kinu's per-component `@import "../../variables.css"` (which load AFTER this theme via JS module imports) - Monochromatic primary action color (no brand orange in default surfaces — Claude reserves clay for the logo); brand exposed as --k-brand for opt-in use - Tab indicator preserves kinu's anchor-positioned ::before pattern, re-skinned to match Claude's `.df-pill-indicator[data-segmented]` (32px track, 28px indicator, 8px radius, 0.5px hairline, df-z4 bg) - Menu/list rows use Claude's --df-row-* tokens (26px height, 13px font, 4px gap) — kinu's `[k="item"]` defaults of 38px/17px/8px were ~50% too tall - No styling on `[k="tooltip"]` wrapper (which is `position: relative` around the trigger) — earlier attempts chipped every tooltipped button with a visible background Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Establishes a minimal layered architecture for kinu themes and ships
"claw" as the first non-default theme — a Claude Code-style monochromatic
look with light + dark variants.
src/variables.css now wraps its `:root { --k-* }` defaults in a single
`@layer tokens` block. That's the entire library-side change.
API:
- kinu's default tokens: layered (lowest priority).
- Themes (and consumer overrides): unlayered. Always win, regardless
of selector specificity, because unlayered styles beat layered ones.
A theme is just a stylesheet:
[data-theme="x"] { --k-background: ...; --k-foreground: ...; }
[data-theme="x"].dark { --k-background: ...; }
No `:root[data-theme="x"]` specificity tricks, no `:where()` zeroing,
no source-order fragility from kinu's per-component
`@import "../../variables.css"` re-injecting `:root` blocks throughout
the bundle. Layers solve all three by construction.
Component CSS is intentionally NOT layered — keeps the change minimal
and means consumer overrides like `[k="button"] { padding: 0 }` still
work via source order, exactly as they do today.
Migrated from demo/src/themes/claude-code.css to src/themes/claw.css
so it can ship with the library. Token rules drop their `:root` prefix
since they're now competing only with kinu's layered defaults; per-
component rules keep the `[data-theme="claw"] [k="..."]` descendant
form (specificity 0,3,0) since they still need to beat kinu's
component CSS (0,2,0) at source-order tie level.
Internal helper variables renamed --cc-* → --claw-* to match.
Demo:
- claude-code-toggle.tsx → claw-toggle.tsx (component renamed)
- localStorage key migrated to `kinu-claw-theme`
- aria-labels and tooltip text updated
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
The top-bar split between a claw toggle and a Theme Customizer made the
relationship between the two unclear. Replace with a single Theme
dropdown:
◉ Default
◉ Claw
─────────
Customize…
The customizer used to emit `:root { --k-* }` rules, which collided with
any `[data-theme="x"]` theme at the same specificity. Now it reads the
active `data-theme` and scopes its generated CSS to the theme:
- no theme active → `:root { --k-* }` (same as before)
- claw active → `[data-theme="claw"] { --k-* }`
Both stay unlayered, so they win over kinu's layered defaults; the
customizer is injected late, so it wins source-order against the theme
itself for the tokens it overrides. Tokens the customizer doesn't touch
(--k-success, --k-shadow-color, --claw-* internals, etc.) keep flowing
through from the theme. Mental model: theme = baseline, customizer =
overlay on top.
A MutationObserver on `<html data-theme>` re-applies the customizer's
overlay whenever the active theme changes, so picking a different theme
keeps the user's customizations applied to the new baseline.
`demo/src/theme-picker.tsx` — DropdownMenu with one row per theme +
"Customize…" item that fires `commandfor="theme-customizer"`. Themes
are a `THEMES` array so adding more is one line. Active theme persists
via localStorage `kinu-theme`.
`demo/src/theme-customizer.tsx`:
- Stripped its built-in trigger button + tooltip; renders only
`<Dialog id="theme-customizer">`.
- `generateCSS(settings, theme)` takes the active theme and emits
`[data-theme="x"]` / `[data-theme="x"]:not(.light)` /
`[data-theme="x"].dark` selectors, defaulting to `:root` / `:root
:not(.light)` / `.dark` when no theme is active.
- On boot + on `data-theme` mutation, regenerates from saved settings
and re-injects.
- Dropped the redundant `kinu-custom-theme` storage key (the cached
CSS string); only the settings JSON is persisted now and the CSS is
derived on demand. Lets the overlay re-scope automatically.
- Exports `THEME_CUSTOMIZER_DIALOG_ID` so the picker can `commandfor`
it without a magic string drift.
- `demo/src/claw-toggle.tsx` — claw selection now lives in the picker.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Reading localStorage and applying `data-theme` from React happens after the bundle parses, so on first load the page paints once with kinu's defaults, then flashes to the picked theme. Move that work into a tiny synchronous `<script>` in <head> that runs before any CSS is applied. Two keys are read: - `kinu-theme` → set as `data-theme` attribute on <html> - `kinu-custom-theme` → injected as <style id="kinu-custom-style"> The customizer writes both keys back whenever it applies (and clears both on reset), so the bootstrap script always has fresh content. The React-side observers + initial state still run and stay in sync; this is just the pre-paint priming. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
claw's default density is desktop-grade (26px menu rows, 32px buttons,
28px tabs) — well below Apple HIG (44pt) and Material (48dp) tap-target
floors. On a phone the menu rows in particular are uncomfortable to
tap. Add a `@media (pointer: coarse), (max-width: 640px)` block at the
end of the theme that bumps interactive elements into the 36–44px
"Anthropic-grade" comfortable-touch band.
- menu/list rows ([k="item"]): 26px → 36px, 13px → 14px font
- button (default, md): 32px → 40px
- button [size="sm"]: 28px → 36px
- button [size="lg"]: 36px → 44px (HIG)
- button [size="icon"]: 28px → 36px
- input, select, textarea: 32px → 40px, 13px → 14px font
- tablist track / tab / indicator: 32 / 28 → 40 / 34
- switch: 32×18 → 40×22 with proportional knob
- menu/popover container padding: 6px → 8px (breathing room)
Detection rationale:
- `pointer: coarse` is the modality check (iPad with trackpad → fine,
iPad/phone in finger mode → coarse, phone → coarse, desktop → fine)
- `max-width: 640px` is belt-and-braces for browsers that misreport
the pointer query on phones
Also fixes a latent specificity bug uncovered while validating the
mobile rules: several `:where()`-wrapped overrides (cards, dialogs,
dropdown containers, kbd/code typography, checkbox/radio) were tying
with kinu's component CSS at (0,1,0) and losing source-order. Switched
to `:is()` so they land at (0,2,0). Visible effect: card shadow recipe,
popover padding, kbd font, and checkbox border now actually win on
desktop too.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Introduces a density system to kinu base, replacing claw's per-component @media (pointer: coarse) overrides with a layered token-swap. Eleven density tokens, defined inside `@layer tokens`: --k-row-h, --k-row-font, --k-row-px, --k-row-gap --k-control-h, --k-control-h-sm, --k-control-h-lg, --k-control-h-icon --k-control-font, --k-control-px, --k-icon Three named ramps: sm : compact / power-user dense (Linear-ish) md : comfortable desktop (default) lg : touch-friendly (Apple HIG / Material) Auto: `@media (pointer: coarse), (max-width: 640px)` swaps to lg when no `data-kinu-density` attribute is present. Setting the attribute to ANY value (including empty) opts out — `:root:not([data-kinu-density])` is the gate. This is a visual change for existing kinu consumers' DESKTOP UIs: button heights go 40→36px, item rows go 38→32px, fonts go 14→14 (unchanged). Consumers who want today's sizing add `data-kinu-density ="lg"` to <html>. Mobile appearance is identical (md upgrades to lg automatically via the media query, matching today's defaults). Switched hard-coded heights/paddings to `var(--k-control-h, fallback)` across button, input, textarea, select, item, and tabs. Fallback values match today's kinu defaults so anything that imports a single component CSS file without variables.css still renders. Massive simplification. Every per-component @media override (118 lines) deleted. Replaced by: @media (pointer: fine) and (min-width: 641px) { [data-theme="claw"]:not([data-kinu-density]) { /* sm density tokens */ } } claw runs sm density on desktop. On touch the @media doesn't match, so kinu base's auto-coarse rule (in tokens layer) takes over and applies lg — claw becomes touch-friendly for free. Consumers can pin to any density via `data-kinu-density`; the `:not([data-kinu-density])` gate respects that. Per-component visual rules retained: monochromatic hover wash, themed input border/focus, tab indicator hairline, badge/kbd sizing, etc. — anything that's a *visual* opinion rather than a sizing one. New DensityPicker dropdown alongside the ThemePicker, with options: ◉ Auto md on desktop, lg on touch Compact denser power-user UI Default comfortable desktop Spacious touch-friendly everywhere Selection persists via localStorage `kinu-density`. Inline bootstrap script in index.html now reads that key and applies the attribute synchronously before paint, alongside the existing theme + customizer priming. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Single palette button now opens one dropdown with two grouped sections
(Theme, Density) plus Customize at the bottom — fewer buttons in the
nav, density as a "natural neighbor" to theme rather than a separate
concern.
Layout:
THEME
✓ Default
Claw
─────────
DENSITY
✓ Auto comfortable on desktop, spacious on touch
Compact denser power-user UI
Comfortable kinu default desktop
Spacious touch-friendly everywhere
─────────
Customize…
Section headers are styled `<div>`s with uppercase 11px muted-tone
labels, matching Claude Code's "Pinned" / "Recents" sidebar pattern.
The headers aren't `[k="item"]`, so they don't get the active-state
check column — keeps the visual grouping clear.
Renamed density "Default" → "Comfortable" so the word "Default" only
appears once in the menu (under Theme).
The MutationObserver inside the picker now watches both `data-theme`
and `data-kinu-density` so the active states stay in sync if either
attribute changes externally (devtools, bootstrap script, etc.).
`density-picker.tsx` is deleted; its responsibility moved into
`theme-picker.tsx`. nav.tsx renders only `<ThemePicker />` now.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
DropdownMenuContent has `command="close"` on its <dialog>, so clicks on items bubble up and dismiss the dropdown immediately. That's the right default for action items, but for the picker's theme/density options we want to let the user flip through choices and see the visual effect without the menu closing each time. Add `e.stopPropagation()` on the picker items' onClick — same trick kinu's own `Item.Field` uses for label-wrapped items inside dropdowns. The state update + attribute write still happens (those run in the React handler before bubbling), but the event no longer reaches the dialog's command-close handler. "Customize…" intentionally does NOT stop propagation: clicking it should close the dropdown AND fire its `commandfor="theme-customizer"` to open the dialog. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Every kinu component that uses the standard "solid 2px focus ring"
recipe now reads `box-shadow: var(--k-focus-ring)` instead of inlining
`0 0 0 2px hsl(var(--k-ring))`. The token defaults to the same value,
so this is a visual no-op for kinu base. Themes can now restyle the
focus ring across every input-like component by overriding ONE token.
Components touched (all the solid-ring users):
input, textarea, select, switch, radio, otp, toggle, color-picker,
input-group
Components left as-is (intentionally bespoke focus styles):
button (4px halo + border colour change)
slider (4px halo on thumb specifically)
tab (outline + halo)
chip, tree (outline)
listbox (bottom underline)
claw uses the new token to swap the default opaque ring for a 3px
soft halo at 6% foreground alpha:
--k-focus-ring: 0 0 0 3px hsl(var(--k-foreground) / 0.06);
This reference-based recipe auto-adapts to dark mode (foreground
becomes white-ish), so a single declaration covers both modes.
Also extended claw's `:focus-visible` border-colour-darkening rule
from input/textarea to select as well (it was overlooked before;
select is a bordered control that should follow the same pattern).
While verifying this fix, discovered the dev server on :5173 was
actually serving the main repo (PID outside this worktree), which
is why earlier HMR-based verification kept showing stale CSS.
Killed and restarted from the worktree.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Themes can now restyle BOTH the focus ring AND the focused border
colour across every input-like component by overriding two tokens.
Previously claw needed a per-component `border-color` rule for the
darkening effect on input/textarea/select, while switch/radio/otp/
toggle/color-picker got just the halo — asymmetric.
kinu base:
--k-focus-border: hsl(var(--k-input)) /* no-op default */
--k-focus-ring: 0 0 0 2px hsl(var(--k-ring))
Eight components add `border-color: var(--k-focus-border)` to their
`:focus-visible` blocks (alongside the existing `box-shadow: var(--k-
focus-ring)`):
input, textarea, select, switch, radio, otp, toggle, color-picker
claw declares both tokens once:
--k-focus-ring: 0 0 0 3px hsl(var(--k-foreground) / 0.06)
--k-focus-border: hsl(var(--k-foreground) / 0.2)
…and deletes its per-component `[k="input"]:focus-visible { border-
color: ... }` block — every input-like control now gets the same
"darkened border + soft halo" treatment uniformly.
kinu base is a visual no-op: the default `--k-focus-border` matches
the resting border color (`--k-input`), so unstyled apps see no
border change on focus, exactly as before.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
The input-group wrapper paints the focus halo (via the :has() rule), and the focused child cancels its own ring + border to avoid a double indication. That cancellation rule was hardcoded to `hsl(var(--k-input))` — kinu's default border colour — bypassing the themed `--k-focus-border` token added in the previous commit. Net effect for claw users: an input INSIDE an input-group lost the darkened-border-on-focus treatment, while a standalone input got it. Fix is a one-line swap: `hsl(var(--k-input))` → `var(--k-focus-border)`. For kinu base this is a visual no-op (the focus-border token defaults to the same value); for claw the focused grouped input now shows its themed darken alongside the wrapper's halo, matching standalone input behavior. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
✅ Deploy Preview for kinu-sh ready!
To edit notification comments on pull requests, go to your Netlify project configuration. |
Contributor
|
Size Change: +11.5 kB (+30.56%) 🚨 Total Size: 49.2 kB 📦 View Changed
ℹ️ View Unchanged
|
Three visual/behavioral bugs surfaced on the claw theme deploy preview: - Avatars rendered as a clipped giant glyph. claw set `font-size: 11px` on `[k="avatar"]`, but kinu sizes initials via an em-multiplier on the ::after (img `0.01em` × ::after `100em`); an absolute size on the <img> feeds that 100× and blows initials up to ~1100px. Move the glyph size onto the ::after instead. - Inbox list became a subregion scroll container. claw's `line-height: 1.5` on `[k="item"]` inflated two-line rows past their fixed density height, overflowing the kinu Listbox (natively overflow-y: auto). Drop the override — sizing belongs to the density tokens, per the theme's own design notes. claw rows now match the default theme exactly. - Theme picker checkmark/active state was stale on first load. The homepage is prerendered with no theme, so the static HTML bakes in "Default"/"Auto" as checked; Preact's hydrate() doesn't patch attribute diffs, so initializing state straight to the live attribute left the checkmarks stale until a manual toggle. Use the standard two-pass SSR pattern: init empty (matching prerender), then sync to real attributes in an effect, which triggers the reconciling re-render. Also drop the legacy `kinu-custom-theme` cached-CSS injection from the index.html bootstrap — the customizer stopped writing that key (it now persists settings JSON and derives CSS on demand), so the block only re-injected stale CSS for users with old localStorage.
This was referenced Jun 16, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Same changes as #125, opened from a fresh branch to trigger a Netlify deploy preview.
This branch is the theme-system work rebased cleanly onto
main:@layer tokensarchitecture insrc/variables.css, first non-default theme (src/themes/claw.css), unified Theme dropdown (demo/src/theme-picker.tsx) with picker + customizer + density.--k-row-*,--k-control-*) with auto-coarse adaptation for touch.--k-focus-ring+--k-focus-borderstandardized across form controls.data-themeand main'sdata-color-schemelight/dark model.19 files changed, ~988 insertions. Library build, demo build, and library
tscall pass.https://claude.ai/code/session_01V7eeBQGLaJDdycKo8yPttw
Generated by Claude Code