@askewly/design 0.4.1 → 0.4.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/data/DESIGN.md CHANGED
@@ -7,6 +7,7 @@ tokens:
7
7
  color:
8
8
  primitive:
9
9
  white: { value: {"colorSpace":"oklch","components":[1,0,0]}, type: color }
10
+ black: { value: {"colorSpace":"oklch","components":[0,0,0]}, type: color }
10
11
  gray:
11
12
  "1": { value: {"colorSpace":"oklch","components":[0.985,0,0]}, type: color }
12
13
  "2": { value: {"colorSpace":"oklch","components":[0.97,0.005,270]}, type: color }
@@ -61,6 +62,7 @@ tokens:
61
62
  base: { value: "{color.primitive.gray.1}", type: color }
62
63
  raised: { value: "{color.primitive.white}", type: color }
63
64
  overlay: { value: "{color.primitive.white}", type: color }
65
+ scrim: { value: "{color.primitive.black}", type: color }
64
66
  muted: { value: "{color.primitive.gray.3}", type: color }
65
67
  secondary: { value: "{color.primitive.gray.4}", type: color }
66
68
  tint: { value: "{color.primitive.askewly.mint}", type: color }
package/data/askewly.css CHANGED
@@ -34,6 +34,7 @@
34
34
  --color-border: var(--border);
35
35
  --color-input: var(--input);
36
36
  --color-ring: var(--ring);
37
+ --color-scrim: var(--scrim);
37
38
  --color-primary-hover: var(--primary-hover);
38
39
  --color-emphasis-surface: var(--emphasis-surface);
39
40
  --color-emphasis-foreground: var(--emphasis-foreground);
package/data/tokens.css CHANGED
@@ -7,6 +7,7 @@
7
7
  --card-foreground: oklch(0.16 0.015 270);
8
8
  --popover: oklch(1 0 0);
9
9
  --popover-foreground: oklch(0.16 0.015 270);
10
+ --scrim: oklch(0 0 0);
10
11
  --primary: #6F2DBD;
11
12
  --primary-foreground: oklch(0.985 0 0);
12
13
  --secondary: oklch(0.94 0.02 270);
package/data/tokens.json CHANGED
@@ -1 +1 @@
1
- {"$description":"Askewly Design token SSOT (DTCG-compatible, stable 2025.10 subset). Tier rule: component -> semantic -> primitive, one direction only. Dark mode lives in $extensions['com.askewly.modes'].dark on semantic tokens. Generated artifacts: DESIGN.md frontmatter, examples/ui-vocabulary-site/src/tokens.css. Do not edit generated files by hand.","color":{"primitive":{"$description":"Raw values only. gray is the site's violet-tinted neutral (hue 270) formalized as a 12-step ramp; usage convention: 1-2 app background, 3-5 interactive component states, 6-8 borders, 9-10 solid/low-contrast foreground, 11-12 text.","white":{"$type":"color","$value":{"colorSpace":"oklch","components":[1,0,0]}},"gray":{"1":{"$value":{"colorSpace":"oklch","components":[0.985,0,0]}},"2":{"$value":{"colorSpace":"oklch","components":[0.97,0.005,270]}},"3":{"$value":{"colorSpace":"oklch","components":[0.95,0.015,270]}},"4":{"$value":{"colorSpace":"oklch","components":[0.94,0.02,270]}},"5":{"$value":{"colorSpace":"oklch","components":[0.91,0.015,270]}},"6":{"$value":{"colorSpace":"oklch","components":[0.88,0.015,270]}},"7":{"$value":{"colorSpace":"oklch","components":[0.72,0.02,270]}},"8":{"$value":{"colorSpace":"oklch","components":[0.6,0.025,270]}},"9":{"$value":{"colorSpace":"oklch","components":[0.46,0.03,270]}},"10":{"$value":{"colorSpace":"oklch","components":[0.35,0.03,270]}},"11":{"$value":{"colorSpace":"oklch","components":[0.25,0.03,270]}},"12":{"$value":{"colorSpace":"oklch","components":[0.16,0.015,270]}},"$type":"color"},"askewly":{"$type":"color","$description":"Brand palette extracted from the landing planning reference image (2026-07-05). Canonical purple: violet.","violet":{"$value":{"colorSpace":"srgb","components":[0.435294,0.176471,0.741176],"hex":"#6F2DBD"}},"orchid":{"$value":{"colorSpace":"srgb","components":[0.65098,0.388235,0.8],"hex":"#A663CC"}},"lavender":{"$value":{"colorSpace":"srgb","components":[0.698039,0.596078,0.862745],"hex":"#B298DC"}},"sky":{"$value":{"colorSpace":"srgb","components":[0.721569,0.815686,0.921569],"hex":"#B8D0EB"}},"mint":{"$value":{"colorSpace":"srgb","components":[0.72549,0.980392,0.972549],"hex":"#B9FAF8"}},"violet-deep":{"$description":"Hover shade of the canonical brand violet (M2 2026-08-01, promoted from hardcoded #5f22a8).","$value":{"colorSpace":"srgb","components":[0.372549,0.133333,0.658824],"hex":"#5F22A8"}}},"red":{"9":{"$value":{"colorSpace":"oklch","components":[0.58,0.22,27]}},"$type":"color"},"indigo":{"50":{"$value":{"colorSpace":"oklch","components":[0.962,0.018,272.314]}},"300":{"$value":{"colorSpace":"oklch","components":[0.785,0.115,274.713]}},"400":{"$value":{"colorSpace":"oklch","components":[0.673,0.182,276.935]}},"500":{"$value":{"colorSpace":"oklch","components":[0.585,0.233,277.117]}},"600":{"$value":{"colorSpace":"oklch","components":[0.511,0.262,276.966]}},"700":{"$value":{"colorSpace":"oklch","components":[0.457,0.24,277.023]}},"950":{"$value":{"colorSpace":"oklch","components":[0.257,0.09,281.288]}},"$type":"color","$description":"Emphasis/selection accent ramp (M2 2026-08-01). Values = Tailwind v4 defaults the site already rendered, promoted verbatim for light-mode visual losslessness."},"skyx":{"100":{"$value":{"colorSpace":"oklch","components":[0.951,0.026,236.824]}},"300":{"$value":{"colorSpace":"oklch","components":[0.828,0.111,230.318]}},"400":{"$value":{"colorSpace":"oklch","components":[0.746,0.16,232.661]}},"500":{"$value":{"colorSpace":"oklch","components":[0.685,0.169,237.323]}},"600":{"$value":{"colorSpace":"oklch","components":[0.588,0.158,241.966]}},"700":{"$value":{"colorSpace":"oklch","components":[0.5,0.134,242.749]}},"950":{"$value":{"colorSpace":"oklch","components":[0.293,0.066,243.157]}},"$type":"color","$description":"Informational accent ramp (M2 2026-08-01, Tailwind v4 sky — named skyx to avoid clashing with brand askewly.sky)."},"emerald":{"50":{"$value":{"colorSpace":"oklch","components":[0.979,0.021,166.113]}},"300":{"$value":{"colorSpace":"oklch","components":[0.845,0.143,164.978]}},"700":{"$value":{"colorSpace":"oklch","components":[0.508,0.118,165.612]}},"950":{"$value":{"colorSpace":"oklch","components":[0.262,0.051,172.552]}},"$type":"color","$description":"Success accent ramp (M2 2026-08-01, Tailwind v4 defaults)."},"rose":{"100":{"$value":{"colorSpace":"oklch","components":[0.941,0.03,12.58]}},"400":{"$value":{"colorSpace":"oklch","components":[0.712,0.194,13.428]}},"500":{"$value":{"colorSpace":"oklch","components":[0.645,0.246,16.439]}},"600":{"$value":{"colorSpace":"oklch","components":[0.586,0.253,17.585]}},"950":{"$value":{"colorSpace":"oklch","components":[0.271,0.105,12.094]}},"$type":"color","$description":"Danger accent ramp (M2 2026-08-01, Tailwind v4 defaults). Distinct from red.9 (--destructive): the site's danger demos render rose, and the shades intentionally differ."}},"semantic":{"$description":"Role tokens. Components and site CSS consume this layer only. Dark values in $extensions['com.askewly.modes'].dark.","surface":{"$type":"color","base":{"$value":"{color.primitive.gray.1}","$extensions":{"com.askewly.modes":{"dark":"{color.primitive.gray.12}"}}},"raised":{"$description":"Cards, panels sitting on base.","$value":"{color.primitive.white}","$extensions":{"com.askewly.modes":{"dark":"{color.primitive.gray.11}"}}},"overlay":{"$description":"Popovers, dropdowns, floating layers.","$value":"{color.primitive.white}","$extensions":{"com.askewly.modes":{"dark":"{color.primitive.gray.11}"}}},"muted":{"$value":"{color.primitive.gray.3}","$extensions":{"com.askewly.modes":{"dark":"{color.primitive.gray.10}"}}},"secondary":{"$description":"Secondary button / subtle filled surface.","$value":"{color.primitive.gray.4}","$extensions":{"com.askewly.modes":{"dark":"{color.primitive.gray.10}"}}},"tint":{"$description":"Informational wash, agent-ready signal surface.","$value":"{color.primitive.askewly.mint}","$extensions":{"com.askewly.modes":{"dark":"{color.primitive.gray.10}"}}}},"text":{"$type":"color","default":{"$value":"{color.primitive.gray.12}","$extensions":{"com.askewly.modes":{"dark":"{color.primitive.gray.1}"}}},"muted":{"$value":"{color.primitive.gray.9}","$extensions":{"com.askewly.modes":{"dark":"{color.primitive.gray.7}"}}},"secondary":{"$description":"Foreground on surface.secondary.","$value":"{color.primitive.gray.11}","$extensions":{"com.askewly.modes":{"dark":"{color.primitive.gray.3}"}}},"on-accent":{"$description":"Text/icon on action.primary solid fills.","$value":"{color.primitive.gray.1}","$extensions":{"com.askewly.modes":{"dark":"{color.primitive.gray.12}"}}},"on-destructive":{"$description":"Text/icon on action.destructive solid fills. Theme-invariant white (M2: promotes the settled 'text-white is correct' marker verdict into a token).","$value":"{color.primitive.white}","$extensions":{"com.askewly.modes":{"dark":"{color.primitive.white}"}}}},"action":{"$type":"color","primary":{"$value":"{color.primitive.askewly.violet}","$extensions":{"com.askewly.modes":{"dark":"{color.primitive.gray.1}"}}},"primary-hover":{"$description":"Hover state for brand-violet fills/text (M2). Dark uses the brighter orchid — darkening on hover loses contrast on dark surfaces.","$value":"{color.primitive.askewly.violet-deep}","$extensions":{"com.askewly.modes":{"dark":"{color.primitive.askewly.orchid}"}}},"secondary":{"$value":"{color.primitive.askewly.orchid}","$extensions":{"com.askewly.modes":{"dark":"{color.primitive.askewly.mint}"}}},"destructive":{"$value":"{color.primitive.red.9}","$extensions":{"com.askewly.modes":{"dark":"{color.primitive.red.9}"}}}},"emphasis":{"$type":"color","$description":"Selection/emphasis accent (indigo family, M2 2026-08-01). Distinct from 'accent' (shadcn interaction-state wash, mint family) — do not merge (group-semantics collision, fresh-verifier finding).","surface":{"$description":"Chip/selected-row wash.","$value":"{color.primitive.indigo.50}","$extensions":{"com.askewly.modes":{"dark":"{color.primitive.indigo.950}"}}},"foreground":{"$description":"Text on emphasis.surface.","$value":"{color.primitive.indigo.700}","$extensions":{"com.askewly.modes":{"dark":"{color.primitive.indigo.300}"}}},"solid":{"$description":"Solid emphasis fill (buttons).","$value":"{color.primitive.indigo.600}","$extensions":{"com.askewly.modes":{"dark":"{color.primitive.indigo.500}"}}},"solid-hover":{"$value":"{color.primitive.indigo.500}","$extensions":{"com.askewly.modes":{"dark":"{color.primitive.indigo.400}"}}},"ring":{"$description":"Focus border/ring on emphasis-accented inputs.","$value":"{color.primitive.indigo.500}","$extensions":{"com.askewly.modes":{"dark":"{color.primitive.indigo.400}"}}},"on-solid":{"$description":"Text/icon on emphasis.solid fills. White holds contrast on indigo-600 (light) and indigo-500 (dark).","$value":"{color.primitive.white}","$extensions":{"com.askewly.modes":{"dark":"{color.primitive.white}"}}}},"status":{"$type":"color","$description":"Status colors (M2 2026-08-01). info=sky, success=emerald, danger=rose. Kept out of 'accent' (interaction-state group).","info-surface":{"$value":"{color.primitive.skyx.100}","$extensions":{"com.askewly.modes":{"dark":"{color.primitive.skyx.950}"}}},"info-foreground":{"$value":"{color.primitive.skyx.700}","$extensions":{"com.askewly.modes":{"dark":"{color.primitive.skyx.300}"}}},"info-link":{"$description":"Standalone info-accented link/action text.","$value":"{color.primitive.skyx.500}","$extensions":{"com.askewly.modes":{"dark":"{color.primitive.skyx.400}"}}},"info-link-hover":{"$value":"{color.primitive.skyx.600}","$extensions":{"com.askewly.modes":{"dark":"{color.primitive.skyx.300}"}}},"success-surface":{"$value":"{color.primitive.emerald.50}","$extensions":{"com.askewly.modes":{"dark":"{color.primitive.emerald.950}"}}},"success-foreground":{"$value":"{color.primitive.emerald.700}","$extensions":{"com.askewly.modes":{"dark":"{color.primitive.emerald.300}"}}},"danger-surface":{"$value":"{color.primitive.rose.100}","$extensions":{"com.askewly.modes":{"dark":"{color.primitive.rose.950}"}}},"danger-foreground":{"$value":"{color.primitive.rose.600}","$extensions":{"com.askewly.modes":{"dark":"{color.primitive.rose.400}"}}},"danger-solid":{"$value":"{color.primitive.rose.600}","$extensions":{"com.askewly.modes":{"dark":"{color.primitive.rose.600}"}}},"danger-solid-hover":{"$value":"{color.primitive.rose.500}","$extensions":{"com.askewly.modes":{"dark":"{color.primitive.rose.500}"}}}},"accent":{"$type":"color","base":{"$description":"Informational accent wash (mint family).","$value":"{color.primitive.askewly.mint}","$extensions":{"com.askewly.modes":{"dark":"{color.primitive.gray.10}"}}},"foreground":{"$value":"{color.primitive.gray.12}","$extensions":{"com.askewly.modes":{"dark":"{color.primitive.gray.1}"}}}},"border":{"$type":"color","default":{"$value":"{color.primitive.gray.6}","$extensions":{"com.askewly.modes":{"dark":"{color.primitive.gray.10}"}}},"input":{"$value":"{color.primitive.gray.6}","$extensions":{"com.askewly.modes":{"dark":"{color.primitive.gray.10}"}}},"focus":{"$description":"Focus ring.","$value":"{color.primitive.askewly.violet}","$extensions":{"com.askewly.modes":{"dark":"{color.primitive.askewly.lavender}"}}},"accent":{"$description":"Selected/active edges only.","$value":"{color.primitive.askewly.lavender}","$extensions":{"com.askewly.modes":{"dark":"{color.primitive.askewly.orchid}"}}}}},"component":{"$description":"Minimal by policy (survey criterion A-5): only overrides that semantic roles cannot express. Exemplar tier for the lint chain.","button":{"$type":"color","bg":{"$value":"{color.semantic.action.primary}"},"text":{"$value":"{color.semantic.text.on-accent}"}}}},"dimension":{"space":{"1":{"$value":{"value":4,"unit":"px"}},"2":{"$value":{"value":8,"unit":"px"}},"4":{"$value":{"value":16,"unit":"px"}},"8":{"$value":{"value":32,"unit":"px"}},"12":{"$value":{"value":48,"unit":"px"}},"16":{"$value":{"value":64,"unit":"px"}},"$type":"dimension","$description":"4/8 grid. Reference variables only - not bound into the Tailwind theme namespace."},"radius":{"$type":"dimension","$description":"Canonical = shipped site values (PSS1 visually approved), not the older 2/4px draft.","sm":{"$value":{"value":4,"unit":"px"}},"md":{"$value":{"value":6,"unit":"px"}},"lg":{"$value":{"value":8,"unit":"px"}},"xl":{"$value":{"value":12,"unit":"px"}},"2xl":{"$description":"SD gen2 (2026-07-12): large-surface corner radius for sheets/overlays that sit above the base scale — device-frame inner viewport, bottom-sheet-detents. Recurs identically in >=2 mobile components as Tailwind's built-in rounded-2xl (16px), currently outside the tokenized sm-xl range.","$value":{"value":16,"unit":"px"}}},"size":{"$type":"dimension","$description":"Fixed-purpose sizing constants that are not part of the 4/8 spacing grid. SD gen2 (2026-07-12): touch-target minimum recurs across mobile surfaces (large-title-collapsing-header back button, mobile-signup-field-stack inputs/submit, plus pre-existing search-autocomplete/showcase-card/term-visual usage) as Tailwind size-11/h-11/min-h-11 (44px) — the WCAG 2.5.5 / iOS HIG minimum hit target.","touch-target-min":{"$value":{"value":44,"unit":"px"}}},"z-index":{"$type":"number","$description":"SD gen2 (2026-07-12): layering scale distilled from recurring hardcoded z-index values across mobile overlays and dense-data sticky headers. 'sticky' = content pinned within its own scroll container (collapsing header bar, bottom-sheet/action-sheet root within DeviceFrame, table sticky header row). 'elevated' = a second layer above a sticky layer in the same local stacking context (permission-matrix-editor-grid's sticky corner cell, which must sit above both its own header row and column). 'overlay' = page-level modal/scrim/popover stacking context (ui/dialog, ui/sheet, ui/popover, ui/tooltip, full-screen confirm overlays).","sticky":{"$value":10},"elevated":{"$value":20},"overlay":{"$value":50}}},"typography":{"font":{"$type":"fontFamily","sans":{"$value":["Geist","Noto Sans KR","ui-sans-serif","system-ui","sans-serif"]},"mono":{"$value":["Geist Mono","ui-monospace","monospace"]}},"scale":{"$type":"dimension","$description":"M6 2026-08-01: expanded to the 9 steps screens actually render (M1 closeout measurement). New steps (2xs/xs/3xl/5xl/7xl) carry Tailwind v4 default values verbatim in rem — they are wired into @theme --text-* so existing utility classes route through the SSOT with zero render change. Legacy 5 names (sm/base/lg/xl/2xl, px) are kept for backward compat; lg(20) and 2xl(40) differ from Tailwind's same-named defaults and are NOT wired.","2xs":{"$description":"Micro labels (10px at default root). New utility text-2xs — Tailwind has no built-in 2xs.","$value":{"value":0.625,"unit":"rem"}},"xs":{"$value":{"value":0.75,"unit":"rem"}},"sm":{"$value":{"value":14,"unit":"px"}},"base":{"$value":{"value":16,"unit":"px"}},"lg":{"$value":{"value":20,"unit":"px"}},"xl":{"$description":"Unused by live screens as of M1 measurement (2026-08-01) — kept as record for llms consumers.","$value":{"value":28,"unit":"px"}},"2xl":{"$value":{"value":40,"unit":"px"}},"3xl":{"$value":{"value":1.875,"unit":"rem"}},"5xl":{"$value":{"value":3,"unit":"rem"}},"7xl":{"$value":{"value":4.5,"unit":"rem"}}},"weight":{"$type":"fontWeight","regular":{"$value":400},"medium":{"$value":500}}},"motion":{"$description":"SD gen2 (2026-07-12): motion timing constants. Kept minimal by the same policy as color.component — only the value that actually recurs, not a full scale invented ahead of need.","duration":{"$type":"duration","overlay":{"$description":"Open/close transition for modal-style surfaces. Recurs identically (200ms) in bottom-sheet-detents.tsx and ui/dialog.tsx.","$value":{"value":200,"unit":"ms"}}}}}
1
+ {"$description":"Askewly Design token SSOT (DTCG-compatible, stable 2025.10 subset). Tier rule: component -> semantic -> primitive, one direction only. Dark mode lives in $extensions['com.askewly.modes'].dark on semantic tokens. Generated artifacts: DESIGN.md frontmatter, examples/ui-vocabulary-site/src/tokens.css. Do not edit generated files by hand.","color":{"primitive":{"$description":"Raw values only. gray is the site's violet-tinted neutral (hue 270) formalized as a 12-step ramp; usage convention: 1-2 app background, 3-5 interactive component states, 6-8 borders, 9-10 solid/low-contrast foreground, 11-12 text.","white":{"$type":"color","$value":{"colorSpace":"oklch","components":[1,0,0]}},"black":{"$type":"color","$value":{"colorSpace":"oklch","components":[0,0,0]}},"gray":{"1":{"$value":{"colorSpace":"oklch","components":[0.985,0,0]}},"2":{"$value":{"colorSpace":"oklch","components":[0.97,0.005,270]}},"3":{"$value":{"colorSpace":"oklch","components":[0.95,0.015,270]}},"4":{"$value":{"colorSpace":"oklch","components":[0.94,0.02,270]}},"5":{"$value":{"colorSpace":"oklch","components":[0.91,0.015,270]}},"6":{"$value":{"colorSpace":"oklch","components":[0.88,0.015,270]}},"7":{"$value":{"colorSpace":"oklch","components":[0.72,0.02,270]}},"8":{"$value":{"colorSpace":"oklch","components":[0.6,0.025,270]}},"9":{"$value":{"colorSpace":"oklch","components":[0.46,0.03,270]}},"10":{"$value":{"colorSpace":"oklch","components":[0.35,0.03,270]}},"11":{"$value":{"colorSpace":"oklch","components":[0.25,0.03,270]}},"12":{"$value":{"colorSpace":"oklch","components":[0.16,0.015,270]}},"$type":"color"},"askewly":{"$type":"color","$description":"Brand palette extracted from the landing planning reference image (2026-07-05). Canonical purple: violet.","violet":{"$value":{"colorSpace":"srgb","components":[0.435294,0.176471,0.741176],"hex":"#6F2DBD"}},"orchid":{"$value":{"colorSpace":"srgb","components":[0.65098,0.388235,0.8],"hex":"#A663CC"}},"lavender":{"$value":{"colorSpace":"srgb","components":[0.698039,0.596078,0.862745],"hex":"#B298DC"}},"sky":{"$value":{"colorSpace":"srgb","components":[0.721569,0.815686,0.921569],"hex":"#B8D0EB"}},"mint":{"$value":{"colorSpace":"srgb","components":[0.72549,0.980392,0.972549],"hex":"#B9FAF8"}},"violet-deep":{"$description":"Hover shade of the canonical brand violet (M2 2026-08-01, promoted from hardcoded #5f22a8).","$value":{"colorSpace":"srgb","components":[0.372549,0.133333,0.658824],"hex":"#5F22A8"}}},"red":{"9":{"$value":{"colorSpace":"oklch","components":[0.58,0.22,27]}},"$type":"color"},"indigo":{"50":{"$value":{"colorSpace":"oklch","components":[0.962,0.018,272.314]}},"300":{"$value":{"colorSpace":"oklch","components":[0.785,0.115,274.713]}},"400":{"$value":{"colorSpace":"oklch","components":[0.673,0.182,276.935]}},"500":{"$value":{"colorSpace":"oklch","components":[0.585,0.233,277.117]}},"600":{"$value":{"colorSpace":"oklch","components":[0.511,0.262,276.966]}},"700":{"$value":{"colorSpace":"oklch","components":[0.457,0.24,277.023]}},"950":{"$value":{"colorSpace":"oklch","components":[0.257,0.09,281.288]}},"$type":"color","$description":"Emphasis/selection accent ramp (M2 2026-08-01). Values = Tailwind v4 defaults the site already rendered, promoted verbatim for light-mode visual losslessness."},"skyx":{"100":{"$value":{"colorSpace":"oklch","components":[0.951,0.026,236.824]}},"300":{"$value":{"colorSpace":"oklch","components":[0.828,0.111,230.318]}},"400":{"$value":{"colorSpace":"oklch","components":[0.746,0.16,232.661]}},"500":{"$value":{"colorSpace":"oklch","components":[0.685,0.169,237.323]}},"600":{"$value":{"colorSpace":"oklch","components":[0.588,0.158,241.966]}},"700":{"$value":{"colorSpace":"oklch","components":[0.5,0.134,242.749]}},"950":{"$value":{"colorSpace":"oklch","components":[0.293,0.066,243.157]}},"$type":"color","$description":"Informational accent ramp (M2 2026-08-01, Tailwind v4 sky — named skyx to avoid clashing with brand askewly.sky)."},"emerald":{"50":{"$value":{"colorSpace":"oklch","components":[0.979,0.021,166.113]}},"300":{"$value":{"colorSpace":"oklch","components":[0.845,0.143,164.978]}},"700":{"$value":{"colorSpace":"oklch","components":[0.508,0.118,165.612]}},"950":{"$value":{"colorSpace":"oklch","components":[0.262,0.051,172.552]}},"$type":"color","$description":"Success accent ramp (M2 2026-08-01, Tailwind v4 defaults)."},"rose":{"100":{"$value":{"colorSpace":"oklch","components":[0.941,0.03,12.58]}},"400":{"$value":{"colorSpace":"oklch","components":[0.712,0.194,13.428]}},"500":{"$value":{"colorSpace":"oklch","components":[0.645,0.246,16.439]}},"600":{"$value":{"colorSpace":"oklch","components":[0.586,0.253,17.585]}},"950":{"$value":{"colorSpace":"oklch","components":[0.271,0.105,12.094]}},"$type":"color","$description":"Danger accent ramp (M2 2026-08-01, Tailwind v4 defaults). Distinct from red.9 (--destructive): the site's danger demos render rose, and the shades intentionally differ."}},"semantic":{"$description":"Role tokens. Components and site CSS consume this layer only. Dark values in $extensions['com.askewly.modes'].dark.","surface":{"$type":"color","base":{"$value":"{color.primitive.gray.1}","$extensions":{"com.askewly.modes":{"dark":"{color.primitive.gray.12}"}}},"raised":{"$description":"Cards, panels sitting on base.","$value":"{color.primitive.white}","$extensions":{"com.askewly.modes":{"dark":"{color.primitive.gray.11}"}}},"overlay":{"$description":"Popovers, dropdowns, floating layers.","$value":"{color.primitive.white}","$extensions":{"com.askewly.modes":{"dark":"{color.primitive.gray.11}"}}},"scrim":{"$description":"Dimming layer behind modals, sheets and lightboxes. Pure black, with no dark override on purpose: a scrim must be darker than the darkest surface it covers. Keying it off --foreground inverts it in dark mode (the backdrop gets brighter); keying it off gray.12 makes it identical to the dark canvas, so it dims nothing at all. Consumers own the opacity (bg-scrim/50, /72).","$value":"{color.primitive.black}"},"muted":{"$value":"{color.primitive.gray.3}","$extensions":{"com.askewly.modes":{"dark":"{color.primitive.gray.10}"}}},"secondary":{"$description":"Secondary button / subtle filled surface.","$value":"{color.primitive.gray.4}","$extensions":{"com.askewly.modes":{"dark":"{color.primitive.gray.10}"}}},"tint":{"$description":"Informational wash, agent-ready signal surface.","$value":"{color.primitive.askewly.mint}","$extensions":{"com.askewly.modes":{"dark":"{color.primitive.gray.10}"}}}},"text":{"$type":"color","default":{"$value":"{color.primitive.gray.12}","$extensions":{"com.askewly.modes":{"dark":"{color.primitive.gray.1}"}}},"muted":{"$value":"{color.primitive.gray.9}","$extensions":{"com.askewly.modes":{"dark":"{color.primitive.gray.7}"}}},"secondary":{"$description":"Foreground on surface.secondary.","$value":"{color.primitive.gray.11}","$extensions":{"com.askewly.modes":{"dark":"{color.primitive.gray.3}"}}},"on-accent":{"$description":"Text/icon on action.primary solid fills.","$value":"{color.primitive.gray.1}","$extensions":{"com.askewly.modes":{"dark":"{color.primitive.gray.12}"}}},"on-destructive":{"$description":"Text/icon on action.destructive solid fills. Theme-invariant white (M2: promotes the settled 'text-white is correct' marker verdict into a token).","$value":"{color.primitive.white}","$extensions":{"com.askewly.modes":{"dark":"{color.primitive.white}"}}}},"action":{"$type":"color","primary":{"$value":"{color.primitive.askewly.violet}","$extensions":{"com.askewly.modes":{"dark":"{color.primitive.gray.1}"}}},"primary-hover":{"$description":"Hover state for brand-violet fills/text (M2). Dark uses the brighter orchid — darkening on hover loses contrast on dark surfaces.","$value":"{color.primitive.askewly.violet-deep}","$extensions":{"com.askewly.modes":{"dark":"{color.primitive.askewly.orchid}"}}},"secondary":{"$value":"{color.primitive.askewly.orchid}","$extensions":{"com.askewly.modes":{"dark":"{color.primitive.askewly.mint}"}}},"destructive":{"$value":"{color.primitive.red.9}","$extensions":{"com.askewly.modes":{"dark":"{color.primitive.red.9}"}}}},"emphasis":{"$type":"color","$description":"Selection/emphasis accent (indigo family, M2 2026-08-01). Distinct from 'accent' (shadcn interaction-state wash, mint family) — do not merge (group-semantics collision, fresh-verifier finding).","surface":{"$description":"Chip/selected-row wash.","$value":"{color.primitive.indigo.50}","$extensions":{"com.askewly.modes":{"dark":"{color.primitive.indigo.950}"}}},"foreground":{"$description":"Text on emphasis.surface.","$value":"{color.primitive.indigo.700}","$extensions":{"com.askewly.modes":{"dark":"{color.primitive.indigo.300}"}}},"solid":{"$description":"Solid emphasis fill (buttons).","$value":"{color.primitive.indigo.600}","$extensions":{"com.askewly.modes":{"dark":"{color.primitive.indigo.500}"}}},"solid-hover":{"$value":"{color.primitive.indigo.500}","$extensions":{"com.askewly.modes":{"dark":"{color.primitive.indigo.400}"}}},"ring":{"$description":"Focus border/ring on emphasis-accented inputs.","$value":"{color.primitive.indigo.500}","$extensions":{"com.askewly.modes":{"dark":"{color.primitive.indigo.400}"}}},"on-solid":{"$description":"Text/icon on emphasis.solid fills. White holds contrast on indigo-600 (light) and indigo-500 (dark).","$value":"{color.primitive.white}","$extensions":{"com.askewly.modes":{"dark":"{color.primitive.white}"}}}},"status":{"$type":"color","$description":"Status colors (M2 2026-08-01). info=sky, success=emerald, danger=rose. Kept out of 'accent' (interaction-state group).","info-surface":{"$value":"{color.primitive.skyx.100}","$extensions":{"com.askewly.modes":{"dark":"{color.primitive.skyx.950}"}}},"info-foreground":{"$value":"{color.primitive.skyx.700}","$extensions":{"com.askewly.modes":{"dark":"{color.primitive.skyx.300}"}}},"info-link":{"$description":"Standalone info-accented link/action text.","$value":"{color.primitive.skyx.500}","$extensions":{"com.askewly.modes":{"dark":"{color.primitive.skyx.400}"}}},"info-link-hover":{"$value":"{color.primitive.skyx.600}","$extensions":{"com.askewly.modes":{"dark":"{color.primitive.skyx.300}"}}},"success-surface":{"$value":"{color.primitive.emerald.50}","$extensions":{"com.askewly.modes":{"dark":"{color.primitive.emerald.950}"}}},"success-foreground":{"$value":"{color.primitive.emerald.700}","$extensions":{"com.askewly.modes":{"dark":"{color.primitive.emerald.300}"}}},"danger-surface":{"$value":"{color.primitive.rose.100}","$extensions":{"com.askewly.modes":{"dark":"{color.primitive.rose.950}"}}},"danger-foreground":{"$value":"{color.primitive.rose.600}","$extensions":{"com.askewly.modes":{"dark":"{color.primitive.rose.400}"}}},"danger-solid":{"$value":"{color.primitive.rose.600}","$extensions":{"com.askewly.modes":{"dark":"{color.primitive.rose.600}"}}},"danger-solid-hover":{"$value":"{color.primitive.rose.500}","$extensions":{"com.askewly.modes":{"dark":"{color.primitive.rose.500}"}}}},"accent":{"$type":"color","base":{"$description":"Informational accent wash (mint family).","$value":"{color.primitive.askewly.mint}","$extensions":{"com.askewly.modes":{"dark":"{color.primitive.gray.10}"}}},"foreground":{"$value":"{color.primitive.gray.12}","$extensions":{"com.askewly.modes":{"dark":"{color.primitive.gray.1}"}}}},"border":{"$type":"color","default":{"$value":"{color.primitive.gray.6}","$extensions":{"com.askewly.modes":{"dark":"{color.primitive.gray.10}"}}},"input":{"$value":"{color.primitive.gray.6}","$extensions":{"com.askewly.modes":{"dark":"{color.primitive.gray.10}"}}},"focus":{"$description":"Focus ring.","$value":"{color.primitive.askewly.violet}","$extensions":{"com.askewly.modes":{"dark":"{color.primitive.askewly.lavender}"}}},"accent":{"$description":"Selected/active edges only.","$value":"{color.primitive.askewly.lavender}","$extensions":{"com.askewly.modes":{"dark":"{color.primitive.askewly.orchid}"}}}}},"component":{"$description":"Minimal by policy (survey criterion A-5): only overrides that semantic roles cannot express. Exemplar tier for the lint chain.","button":{"$type":"color","bg":{"$value":"{color.semantic.action.primary}"},"text":{"$value":"{color.semantic.text.on-accent}"}}}},"dimension":{"space":{"1":{"$value":{"value":4,"unit":"px"}},"2":{"$value":{"value":8,"unit":"px"}},"4":{"$value":{"value":16,"unit":"px"}},"8":{"$value":{"value":32,"unit":"px"}},"12":{"$value":{"value":48,"unit":"px"}},"16":{"$value":{"value":64,"unit":"px"}},"$type":"dimension","$description":"4/8 grid. Reference variables only - not bound into the Tailwind theme namespace."},"radius":{"$type":"dimension","$description":"Canonical = shipped site values (PSS1 visually approved), not the older 2/4px draft.","sm":{"$value":{"value":4,"unit":"px"}},"md":{"$value":{"value":6,"unit":"px"}},"lg":{"$value":{"value":8,"unit":"px"}},"xl":{"$value":{"value":12,"unit":"px"}},"2xl":{"$description":"SD gen2 (2026-07-12): large-surface corner radius for sheets/overlays that sit above the base scale — device-frame inner viewport, bottom-sheet-detents. Recurs identically in >=2 mobile components as Tailwind's built-in rounded-2xl (16px), currently outside the tokenized sm-xl range.","$value":{"value":16,"unit":"px"}}},"size":{"$type":"dimension","$description":"Fixed-purpose sizing constants that are not part of the 4/8 spacing grid. SD gen2 (2026-07-12): touch-target minimum recurs across mobile surfaces (large-title-collapsing-header back button, mobile-signup-field-stack inputs/submit, plus pre-existing search-autocomplete/showcase-card/term-visual usage) as Tailwind size-11/h-11/min-h-11 (44px) — the WCAG 2.5.5 / iOS HIG minimum hit target.","touch-target-min":{"$value":{"value":44,"unit":"px"}}},"z-index":{"$type":"number","$description":"SD gen2 (2026-07-12): layering scale distilled from recurring hardcoded z-index values across mobile overlays and dense-data sticky headers. 'sticky' = content pinned within its own scroll container (collapsing header bar, bottom-sheet/action-sheet root within DeviceFrame, table sticky header row). 'elevated' = a second layer above a sticky layer in the same local stacking context (permission-matrix-editor-grid's sticky corner cell, which must sit above both its own header row and column). 'overlay' = page-level modal/scrim/popover stacking context (ui/dialog, ui/sheet, ui/popover, ui/tooltip, full-screen confirm overlays).","sticky":{"$value":10},"elevated":{"$value":20},"overlay":{"$value":50}}},"typography":{"font":{"$type":"fontFamily","sans":{"$value":["Geist","Noto Sans KR","ui-sans-serif","system-ui","sans-serif"]},"mono":{"$value":["Geist Mono","ui-monospace","monospace"]}},"scale":{"$type":"dimension","$description":"M6 2026-08-01: expanded to the 9 steps screens actually render (M1 closeout measurement). New steps (2xs/xs/3xl/5xl/7xl) carry Tailwind v4 default values verbatim in rem — they are wired into @theme --text-* so existing utility classes route through the SSOT with zero render change. Legacy 5 names (sm/base/lg/xl/2xl, px) are kept for backward compat; lg(20) and 2xl(40) differ from Tailwind's same-named defaults and are NOT wired.","2xs":{"$description":"Micro labels (10px at default root). New utility text-2xs — Tailwind has no built-in 2xs.","$value":{"value":0.625,"unit":"rem"}},"xs":{"$value":{"value":0.75,"unit":"rem"}},"sm":{"$value":{"value":14,"unit":"px"}},"base":{"$value":{"value":16,"unit":"px"}},"lg":{"$value":{"value":20,"unit":"px"}},"xl":{"$description":"Unused by live screens as of M1 measurement (2026-08-01) — kept as record for llms consumers.","$value":{"value":28,"unit":"px"}},"2xl":{"$value":{"value":40,"unit":"px"}},"3xl":{"$value":{"value":1.875,"unit":"rem"}},"5xl":{"$value":{"value":3,"unit":"rem"}},"7xl":{"$value":{"value":4.5,"unit":"rem"}}},"weight":{"$type":"fontWeight","regular":{"$value":400},"medium":{"$value":500}}},"motion":{"$description":"SD gen2 (2026-07-12): motion timing constants. Kept minimal by the same policy as color.component — only the value that actually recurs, not a full scale invented ahead of need.","duration":{"$type":"duration","overlay":{"$description":"Open/close transition for modal-style surfaces. Recurs identically (200ms) in bottom-sheet-detents.tsx and ui/dialog.tsx.","$value":{"value":200,"unit":"ms"}}}}}
package/dist/kickstart.js CHANGED
@@ -1,4 +1,4 @@
1
- import { existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs";
1
+ import { existsSync, mkdirSync, readdirSync, readFileSync, writeFileSync } from "node:fs";
2
2
  import { builtinModules } from "node:module";
3
3
  import path from "node:path";
4
4
  import { createInterface } from "node:readline/promises";
@@ -198,7 +198,11 @@ export function renderBrandCss(answers) {
198
198
  const a = ACCENTS[answers.color];
199
199
  const c = CANVASES[answers.tone];
200
200
  const font = FONT_STACKS[answers.type];
201
- const vars = (m, primary, onPrimary, ring) => ` --background: ${m.bg};
201
+ // `color-scheme` is what the *browser* reads. Tokens only repaint what we
202
+ // draw; scrollbars, native selects and checkboxes, and the overscroll gutter
203
+ // behind the page are the browser's, and they stay light without this.
204
+ const vars = (m, primary, onPrimary, ring, scheme) => ` color-scheme: ${scheme};
205
+ --background: ${m.bg};
202
206
  --foreground: ${m.fg};
203
207
  --card: ${m.card};
204
208
  --card-foreground: ${m.fg};
@@ -229,13 +233,32 @@ export function renderBrandCss(answers) {
229
233
  return `/* Generated by \`askewly-design init --block\` from DESIGN.md (tone=${answers.tone}, color=${answers.color}).
230
234
  * This file is the project's token definition layer — the block's required
231
235
  * CSS variables (block-contract §4/§6) are defined here, nowhere else. */
236
+
237
+ /* One switch for dark mode. Without this line Tailwind v4 keys \`dark:\`
238
+ * utilities off \`prefers-color-scheme\` while the token values below key off
239
+ * the \`.dark\` class — two independent switches, so a project that toggles the
240
+ * class gets dark tokens with light-mode utilities (and an OS-dark visitor gets
241
+ * the reverse). Transplanted shadcn primitives carry \`dark:\` utilities, so this
242
+ * is load-bearing, not cosmetic. Toggle by putting \`dark\` on <html>. */
243
+ @custom-variant dark (&:where(.dark, .dark *));
244
+
232
245
  :root {
233
246
  font-family: ${font};
234
- ${vars(c.light, a.light, a.onLight, a.ring)}
247
+ ${vars(c.light, a.light, a.onLight, a.ring, "light")}
248
+ /* Scrim — the dimming layer behind modals, sheets and lightboxes. Declared
249
+ * once, outside the light/dark pair, on purpose: a scrim means "press the
250
+ * background down", so it must read dark in BOTH modes. Keying it off
251
+ * \`--foreground\` (the obvious-looking choice) inverts it in dark mode and the
252
+ * backdrop gets *brighter* than the page behind it. Pure black rather than the
253
+ * dark canvas colour, because a scrim identical to the surface it covers dims
254
+ * nothing — measured on the dark bottom-sheet demo, where the two matched
255
+ * exactly and the sheet stopped separating from the screen behind it.
256
+ * Consumers own the opacity: bg-scrim/50, bg-scrim/72. */
257
+ --scrim: oklch(0 0 0);
235
258
  }
236
259
 
237
260
  .dark {
238
- ${vars(c.dark, a.dark, a.onDark, a.darkRing)}
261
+ ${vars(c.dark, a.dark, a.onDark, a.darkRing, "dark")}
239
262
  }
240
263
 
241
264
  @theme inline {
@@ -258,6 +281,7 @@ ${vars(c.dark, a.dark, a.onDark, a.darkRing)}
258
281
  --color-border: var(--border);
259
282
  --color-input: var(--input);
260
283
  --color-ring: var(--ring);
284
+ --color-scrim: var(--scrim);
261
285
  --color-sidebar: var(--sidebar);
262
286
  --color-sidebar-foreground: var(--sidebar-foreground);
263
287
  --color-sidebar-primary: var(--sidebar-primary);
@@ -354,6 +378,124 @@ export function blockExportName(pageSource) {
354
378
  }
355
379
  return null;
356
380
  }
381
+ // ---------------------------------------------------------------------------
382
+ // The `@/` alias the transplanted code needs.
383
+ //
384
+ // Every registry source imports through `@/…`, so a project without the alias
385
+ // configured fails to typecheck the moment the block lands — six `tsc -b`
386
+ // errors on a stock vite react-ts app, patched by hand in both M28 E2E runs.
387
+ // The printed handoff said nothing about it.
388
+ //
389
+ // Two traps this has to survive:
390
+ // 1. tsconfig files are JSONC. Parsing them would mean shipping a parser, so
391
+ // the check is a raw scan with comments removed — a commented-out `"@/*"`
392
+ // must not read as configured.
393
+ // 2. vite's react-ts template makes the root tsconfig.json a *solution* file:
394
+ // references only, no compilerOptions. `paths` written there is ignored by
395
+ // `tsc -b`. The step has to name the file that actually owns the options.
396
+ // ---------------------------------------------------------------------------
397
+ /** Blank out comments without touching string contents (JSONC + TS configs). */
398
+ function stripComments(source) {
399
+ let out = "";
400
+ let quote = null;
401
+ for (let i = 0; i < source.length; i++) {
402
+ const ch = source[i];
403
+ if (quote) {
404
+ out += ch;
405
+ if (ch === "\\") {
406
+ out += source[++i] ?? "";
407
+ }
408
+ else if (ch === quote) {
409
+ quote = null;
410
+ }
411
+ continue;
412
+ }
413
+ if (ch === '"' || ch === "'" || ch === "`") {
414
+ quote = ch;
415
+ out += ch;
416
+ continue;
417
+ }
418
+ if (ch === "/" && source[i + 1] === "/") {
419
+ while (i < source.length && source[i] !== "\n")
420
+ i++;
421
+ out += "\n";
422
+ continue;
423
+ }
424
+ if (ch === "/" && source[i + 1] === "*") {
425
+ i += 2;
426
+ while (i < source.length && !(source[i] === "*" && source[i + 1] === "/"))
427
+ i++;
428
+ i++;
429
+ continue;
430
+ }
431
+ out += ch;
432
+ }
433
+ return out;
434
+ }
435
+ const TSCONFIG_ALIAS = /"@\/\*"\s*:/;
436
+ const VITE_ALIAS = /vite-tsconfig-paths|resolve\s*:[\s\S]{0,400}?alias/;
437
+ /**
438
+ * Which half of the `@/` alias is missing.
439
+ *
440
+ * Uncertainty resolves toward printing: an extra step in an already-configured
441
+ * project costs a glance, a missing one costs the build.
442
+ */
443
+ export function detectPathAlias(targetDir) {
444
+ const names = existsSync(targetDir)
445
+ ? readdirSync(targetDir).filter((n) => /^tsconfig.*\.json$/.test(n))
446
+ : [];
447
+ const read = (name) => {
448
+ try {
449
+ return stripComments(readFileSync(path.join(targetDir, name), "utf8"));
450
+ }
451
+ catch {
452
+ return "";
453
+ }
454
+ };
455
+ const sources = new Map(names.map((n) => [n, read(n)]));
456
+ const declared = names.some((n) => TSCONFIG_ALIAS.test(sources.get(n) ?? ""));
457
+ // Name the file that owns compilerOptions — writing paths anywhere else is a
458
+ // step the reader can follow and still fail.
459
+ const owner = names.find((n) => n !== "tsconfig.json" && /"compilerOptions"\s*:/.test(sources.get(n) ?? "")) ??
460
+ names.find((n) => /"compilerOptions"\s*:/.test(sources.get(n) ?? "")) ??
461
+ names[0] ??
462
+ "tsconfig.json";
463
+ const viteConfig = existsSync(targetDir)
464
+ ? readdirSync(targetDir).find((n) => /^vite\.config\.[cm]?[jt]s$/.test(n))
465
+ : undefined;
466
+ const vite = viteConfig ? VITE_ALIAS.test(stripComments(readFileSync(path.join(targetDir, viteConfig), "utf8"))) : false;
467
+ return { tsconfig: declared ? null : owner, vite };
468
+ }
469
+ /**
470
+ * The copy-paste step, or null when both halves are already in place.
471
+ *
472
+ * Deliberately free of new npm dependencies: the printed `npm i` list is
473
+ * contracted to be what the transplanted files import (M28), and recommending
474
+ * a build-tool plugin there would blur that. `srcRel` keeps the snippet true to
475
+ * where the block was actually written.
476
+ */
477
+ export function aliasStep(status, srcRel) {
478
+ if (!status.tsconfig && status.vite)
479
+ return null;
480
+ const parts = [
481
+ `configure the \`@/\` alias — the transplanted files import through it, and \`tsc\` fails without it:`,
482
+ ];
483
+ if (status.tsconfig) {
484
+ // No `baseUrl`: paths resolve relative to the tsconfig since TS 4.1, and on
485
+ // TS 6 the option is deprecated hard enough to fail the build (TS5101,
486
+ // measured on a stock vite react-ts project — the first snippet shipped it
487
+ // and traded six missing-module errors for one deprecation error).
488
+ parts.push(` in ${status.tsconfig}, inside "compilerOptions":`);
489
+ parts.push(` "paths": { "@/*": ["${srcRel}/*"] }`);
490
+ }
491
+ if (!status.vite) {
492
+ parts.push(` in vite.config.ts:`);
493
+ parts.push(` import { fileURLToPath } from "node:url"`);
494
+ parts.push(` // inside defineConfig({ … })`);
495
+ parts.push(` resolve: { alias: { "@": fileURLToPath(new URL("${srcRel}", import.meta.url)) } }`);
496
+ }
497
+ return parts.join("\n");
498
+ }
357
499
  export async function fetchBlock(blockName, registryBase) {
358
500
  const block = await fetchJson(`${registryBase}/r/${blockName}.json`);
359
501
  if (block.meta?.tier !== "block") {
@@ -366,6 +508,7 @@ export async function transplantBlock(block, targetDir) {
366
508
  const written = [];
367
509
  const npmDeps = new Set(["clsx", "tailwind-merge"]);
368
510
  const visited = new Set();
511
+ const requiredVars = new Set();
369
512
  function write(file) {
370
513
  const dest = destFor(file, srcRoot);
371
514
  const content = rewriteImports(file.content);
@@ -377,6 +520,13 @@ export async function transplantBlock(block, targetDir) {
377
520
  npmDeps.add(pkg);
378
521
  }
379
522
  async function resolve(item) {
523
+ // Required CSS vars are collected transitively, not just off the top-level
524
+ // block. A component-tier asset pulled in as a registryDependency defines
525
+ // its own token needs, and reading only the block's meta let those slip
526
+ // through unchecked — the check passed while the transplanted asset lost
527
+ // its color (block-contract §8-5, carried contract ④).
528
+ for (const name of item.meta?.requiredCssVars ?? [])
529
+ requiredVars.add(name);
380
530
  for (const dep of item.dependencies ?? [])
381
531
  npmDeps.add(dep.split("@").slice(0, dep.startsWith("@") ? 2 : 1).join("@"));
382
532
  for (const ref of item.registryDependencies ?? []) {
@@ -401,7 +551,7 @@ export async function transplantBlock(block, targetDir) {
401
551
  writeFileSync(utils, `import { clsx, type ClassValue } from "clsx"\nimport { twMerge } from "tailwind-merge"\n\nexport function cn(...inputs: ClassValue[]) {\n return twMerge(clsx(inputs))\n}\n`);
402
552
  written.push(utils);
403
553
  }
404
- return { written, npmDeps, requiredCssVars: block.meta?.requiredCssVars ?? [] };
554
+ return { written, npmDeps, requiredCssVars: [...requiredVars] };
405
555
  }
406
556
  // ---------------------------------------------------------------------------
407
557
  // Orchestrator — brief → DESIGN.md → tokens → transplant → required-var check → verify
@@ -452,11 +602,19 @@ export async function runKickstart(dir, opts) {
452
602
  // disk. A printed step the reader cannot follow is worse than no step.
453
603
  const pagePath = path.join(blockDir, "page.tsx");
454
604
  const exportName = existsSync(pagePath) ? blockExportName(readFileSync(pagePath, "utf8")) : null;
605
+ const srcRel = srcRoot === targetDir ? "." : "./src";
606
+ const steps = [];
607
+ const alias = aliasStep(detectPathAlias(targetDir), srcRel);
608
+ if (alias)
609
+ steps.push(alias);
610
+ steps.push(`npm i ${[...result.npmDeps].sort().join(" ")}`);
611
+ steps.push(`import "./askewly-brand.css" after \`@import "tailwindcss";\` in your global stylesheet (order matters — it registers the dark variant)`);
612
+ steps.push(`toggle dark mode by putting the \`dark\` class on <html> — tokens and \`dark:\` utilities both key off it`);
613
+ steps.push(exportName
614
+ ? `render the block: import { ${exportName} } from "@/components/blocks/${opts.block}/page"`
615
+ : `render the block from "@/components/blocks/${opts.block}/page" — open it to see which component it exports`);
616
+ steps.push(`wire routing yourself — the block deliberately ships none (block-contract §3)`);
455
617
  console.log(`\nNext steps:`);
456
- console.log(` 1. npm i ${[...result.npmDeps].sort().join(" ")}`);
457
- console.log(` 2. import "./askewly-brand.css" after \`@import "tailwindcss";\` in your global stylesheet`);
458
- console.log(exportName
459
- ? ` 3. render the block: import { ${exportName} } from "@/components/blocks/${opts.block}/page"`
460
- : ` 3. render the block from "@/components/blocks/${opts.block}/page" — open it to see which component it exports`);
461
- console.log(` 4. wire routing yourself — the block deliberately ships none (block-contract §3)`);
618
+ for (const [index, step] of steps.entries())
619
+ console.log(` ${index + 1}. ${step}`);
462
620
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@askewly/design",
3
- "version": "0.4.1",
3
+ "version": "0.4.3",
4
4
  "description": "Query, inject, and verify the Askewly Design system (tokens, UI vocabulary, recipes) from your terminal or coding agent.",
5
5
  "license": "MIT",
6
6
  "homepage": "https://ui.askewly.com",