Skip to content

Redesign marketing homepage and add theme system (deploy preview)#126

Open
developit wants to merge 12 commits into
mainfrom
claude/confident-hypatia-don5k6-preview
Open

Redesign marketing homepage and add theme system (deploy preview)#126
developit wants to merge 12 commits into
mainfrom
claude/confident-hypatia-don5k6-preview

Conversation

@developit

Copy link
Copy Markdown
Owner

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:

  • Theme system: @layer tokens architecture in src/variables.css, first non-default theme (src/themes/claw.css), unified Theme dropdown (demo/src/theme-picker.tsx) with picker + customizer + density.
  • Density: token-driven sizing (--k-row-*, --k-control-*) with auto-coarse adaptation for touch.
  • Focus tokens: --k-focus-ring + --k-focus-border standardized across form controls.
  • Customizer: theme-aware overlay that composes with the active data-theme and main's data-color-scheme light/dark model.

19 files changed, ~988 insertions. Library build, demo build, and library tsc all pass.

https://claude.ai/code/session_01V7eeBQGLaJDdycKo8yPttw


Generated by Claude Code

developit and others added 11 commits June 9, 2026 11:22
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>
@netlify

netlify Bot commented Jun 10, 2026

Copy link
Copy Markdown

Deploy Preview for kinu-sh ready!

Name Link
🔨 Latest commit 17de6da
🔍 Latest deploy log https://app.netlify.com/projects/kinu-sh/deploys/6a314eff9c23b5000965c25a
😎 Deploy Preview https://deploy-preview-126--kinu-sh.netlify.app
📱 Preview on mobile
Toggle QR Code...

QR Code

Use your smartphone camera to open QR code link.

To edit notification comments on pull requests, go to your Netlify project configuration.

@github-actions

github-actions Bot commented Jun 10, 2026

Copy link
Copy Markdown
Contributor

Size Change: +11.5 kB (+30.56%) 🚨

Total Size: 49.2 kB

📦 View Changed
Filename Size Change
benchmarks/size/.tmp/few-components/bundle.css 3.57 kB +536 B (+17.67%) ⚠️
benchmarks/size/.tmp/nearly-all-components/bundle.css 16.2 kB +5.32 kB (+48.88%) 🚨
benchmarks/size/.tmp/one-component/bundle.css 1.8 kB +286 B (+18.85%) ⚠️
dist/index.css 16.3 kB +5.37 kB (+49.15%) 🚨
ℹ️ View Unchanged
Filename Size
benchmarks/size/.tmp/few-components/bundle.js 637 B
benchmarks/size/.tmp/nearly-all-components/bundle.js 4 kB
benchmarks/size/.tmp/one-component/bundle.js 341 B
dist/index.js 6.34 kB

compressed-size-action

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.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants