@jelinek/ui 0.3.0

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.
Files changed (84) hide show
  1. package/README.md +492 -0
  2. package/dist/components/Alert.svelte +59 -0
  3. package/dist/components/Alert.svelte.d.ts +13 -0
  4. package/dist/components/Button.svelte +178 -0
  5. package/dist/components/Button.svelte.d.ts +19 -0
  6. package/dist/components/Card.svelte +68 -0
  7. package/dist/components/Card.svelte.d.ts +11 -0
  8. package/dist/components/ChapterNav.svelte +290 -0
  9. package/dist/components/ChapterNav.svelte.d.ts +18 -0
  10. package/dist/components/Checkbox.svelte +52 -0
  11. package/dist/components/Checkbox.svelte.d.ts +11 -0
  12. package/dist/components/CodeCopy.svelte +294 -0
  13. package/dist/components/CodeCopy.svelte.d.ts +51 -0
  14. package/dist/components/Container.svelte +34 -0
  15. package/dist/components/Container.svelte.d.ts +10 -0
  16. package/dist/components/ContentLogo.svelte +151 -0
  17. package/dist/components/ContentLogo.svelte.d.ts +37 -0
  18. package/dist/components/Demo.svelte +111 -0
  19. package/dist/components/Demo.svelte.d.ts +12 -0
  20. package/dist/components/Eyebrow.svelte +70 -0
  21. package/dist/components/Eyebrow.svelte.d.ts +12 -0
  22. package/dist/components/Glass.svelte +75 -0
  23. package/dist/components/Glass.svelte.d.ts +10 -0
  24. package/dist/components/Grid.svelte +87 -0
  25. package/dist/components/Grid.svelte.d.ts +11 -0
  26. package/dist/components/Label.svelte +37 -0
  27. package/dist/components/Label.svelte.d.ts +11 -0
  28. package/dist/components/PageHero.svelte +312 -0
  29. package/dist/components/PageHero.svelte.d.ts +55 -0
  30. package/dist/components/Pager.svelte +98 -0
  31. package/dist/components/Pager.svelte.d.ts +13 -0
  32. package/dist/components/Panel.svelte +89 -0
  33. package/dist/components/Panel.svelte.d.ts +12 -0
  34. package/dist/components/ProductCard.svelte +81 -0
  35. package/dist/components/ProductCard.svelte.d.ts +18 -0
  36. package/dist/components/Radio.svelte +54 -0
  37. package/dist/components/Radio.svelte.d.ts +11 -0
  38. package/dist/components/RadioGroup.svelte +42 -0
  39. package/dist/components/RadioGroup.svelte.d.ts +12 -0
  40. package/dist/components/Select.svelte +65 -0
  41. package/dist/components/Select.svelte.d.ts +20 -0
  42. package/dist/components/SiteFooter.svelte +121 -0
  43. package/dist/components/SiteFooter.svelte.d.ts +13 -0
  44. package/dist/components/SiteHeader.svelte +613 -0
  45. package/dist/components/SiteHeader.svelte.d.ts +80 -0
  46. package/dist/components/Slider.svelte +55 -0
  47. package/dist/components/Slider.svelte.d.ts +14 -0
  48. package/dist/components/Swatch.svelte +174 -0
  49. package/dist/components/Swatch.svelte.d.ts +58 -0
  50. package/dist/components/Tab.svelte +88 -0
  51. package/dist/components/Tab.svelte.d.ts +11 -0
  52. package/dist/components/Tabs.svelte +306 -0
  53. package/dist/components/Tabs.svelte.d.ts +20 -0
  54. package/dist/components/Tag.svelte +71 -0
  55. package/dist/components/Tag.svelte.d.ts +16 -0
  56. package/dist/components/TextField.svelte +100 -0
  57. package/dist/components/TextField.svelte.d.ts +18 -0
  58. package/dist/components/ThemeToggle.svelte +122 -0
  59. package/dist/components/ThemeToggle.svelte.d.ts +11 -0
  60. package/dist/components/Tile.svelte +115 -0
  61. package/dist/components/Tile.svelte.d.ts +14 -0
  62. package/dist/components/TileScroller.svelte +199 -0
  63. package/dist/components/TileScroller.svelte.d.ts +25 -0
  64. package/dist/components/container-context.d.ts +1 -0
  65. package/dist/components/container-context.js +6 -0
  66. package/dist/components/deer-mark-path.d.ts +16 -0
  67. package/dist/components/deer-mark-path.js +16 -0
  68. package/dist/components/nav.d.ts +25 -0
  69. package/dist/components/nav.js +17 -0
  70. package/dist/components/radio-context.d.ts +6 -0
  71. package/dist/components/radio-context.js +1 -0
  72. package/dist/components/tabs-context.d.ts +5 -0
  73. package/dist/components/tabs-context.js +1 -0
  74. package/dist/index.d.ts +37 -0
  75. package/dist/index.js +37 -0
  76. package/dist/theme/base.css +439 -0
  77. package/dist/theme/extras.css +2 -0
  78. package/dist/theme/fonts.css +107 -0
  79. package/dist/theme/tokens.css +208 -0
  80. package/dist/utils/chapter-nav-dock.svelte.d.ts +23 -0
  81. package/dist/utils/chapter-nav-dock.svelte.js +65 -0
  82. package/dist/utils/cn.d.ts +2 -0
  83. package/dist/utils/cn.js +27 -0
  84. package/package.json +79 -0
@@ -0,0 +1,111 @@
1
+ <script lang="ts">
2
+ import type { Snippet } from 'svelte';
3
+ import type { HTMLAttributes } from 'svelte/elements';
4
+ import { cn } from '../utils/cn.js';
5
+
6
+ type Variant = 'light' | 'brown' | 'magenta' | 'black';
7
+
8
+ interface Props {
9
+ variant?: Variant;
10
+ class?: string;
11
+ children: Snippet;
12
+ }
13
+
14
+ let {
15
+ variant,
16
+ class: className,
17
+ children,
18
+ ...rest
19
+ }: Props & HTMLAttributes<HTMLDivElement> = $props();
20
+
21
+ // CSS FIDELITY — the brief gave Demo a `dark` boolean that only applied
22
+ // `bg-surface-black text-fg-inverse`. jelinek.css defines no `.demo--dark`
23
+ // at all; Demo's variants are `--light`/`--brown`/`--magenta` (shared with
24
+ // .panel/.card, jelinek.css:486/491-492) plus its OWN `--black`
25
+ // (jelinek.css:792-796) — a different and much bigger rule than a plain
26
+ // dark background. Replaced the boolean with a `variant` prop, same shape
27
+ // as Panel, but with `black` instead of `dark` because that's the class
28
+ // that actually exists for .demo, and — critically — it is not a drop-in
29
+ // synonym for Panel's `dark` (see below).
30
+ //
31
+ // jelinek.css:474-482/483 (shared box system + 24px padding, same as
32
+ // Panel — see Panel.svelte's comment for the border-gradient-collapse and
33
+ // dropped-margin reasoning, identical here) and :322-325/327-331 (shared
34
+ // font-secondary/font-light + nested strong/b→font-medium) all apply
35
+ // identically to .demo.
36
+ //
37
+ // jelinek.css:486 `.demo--light { --box-fill: var(--bg-gray) }` →
38
+ // bg-surface-gray.
39
+ // jelinek.css:491 `.demo--brown { --box-fill: var(--bg-brown) }` →
40
+ // bg-surface-brown. jelinek.css:492 `.demo--magenta` → bg-surface-magenta.
41
+ //
42
+ // TASK 34 — CORRECTION. --box-primary used to be dropped here on the
43
+ // "inert for this component" reasoning Panel.svelte carried. That reasoning
44
+ // was false: jelinek.css:251-257 names `.demo` among the five surfaces that
45
+ // take `box-shadow: inset 0 2px 1px -1px var(--box-primary)` in dark mode.
46
+ // See Panel.svelte for the full note. The highlight is in BASE below and the
47
+ // two divisional overrides are back on the variants.
48
+ //
49
+ // `.demo--black` (jelinek.css:1129-1133) does NOT override --box-primary, so
50
+ // a black demo keeps the :root 50% grey highlight — matching the guide.
51
+ //
52
+ // jelinek.css:790-796 `.demo--black` — the serious one. It is NOT just a
53
+ // dark background: it locally re-maps the neutral brand tokens so
54
+ // black-accented content nested inside (e.g. a `<Button color="black">`)
55
+ // stays readable on a black surface, exactly the same technique the
56
+ // site's own dark mode and the mattress-division re-theme
57
+ // (tokens.css:161-166, `[data-division="mattress"]`) use — overriding the
58
+ // *source* custom property (`--JELINEK-BLACK`, `--text-muted1`, …), not
59
+ // the derived Tailwind token (`--color-jelinek-black`). Because
60
+ // tokens.css's `@theme inline` makes every `--color-*` an unresolved
61
+ // `var(--GUIDE-NAME)` reference (not a baked-in value), overriding the
62
+ // guide-name variable locally on this element cascades correctly to any
63
+ // descendant using the mapped utility (`bg-jelinek-black`,
64
+ // `text-fg-muted1`, …), the same mechanism already used by
65
+ // SiteHeader.svelte's `[--logo-mark:46px]` and by the mattress division
66
+ // override:
67
+ // --box-fill: var(--bg-black); color: var(--text-white);
68
+ // → bg-surface-black text-fg-inverse
69
+ // --JELINEK-BLACK: hsl(0,0%,50%); --JELINEK-BLACK-HIGHLIGHT: hsl(0,0%,60%);
70
+ // --ON-JELINEK-BLACK: hsl(0,0%,5%); --text-muted1: hsl(0,0%,62%);
71
+ // --text-muted2: hsl(0,0%,42%);
72
+ // → arbitrary-property utilities re-declaring those exact five
73
+ // custom properties locally. These are hardcoded literals in
74
+ // the guide itself (not references to other tokens), so arbitrary
75
+ // values are the correct, faithful reproduction, not a shortcut.
76
+ //
77
+ // Task 33 — the five moved from hex to hsl() in the designer's b45c536.
78
+ // Three are exact (#808080 = 50%, #999999 = 60%, #0D0D0D = 5%); TWO ARE NOT,
79
+ // and it is worth writing down that we checked rather than trusting the
80
+ // commit message's "bit-identical": --text-muted1 went #9F9F9F (159) →
81
+ // hsl(0,0%,62%) (158) and --text-muted2 went #6A6A6A (106) →
82
+ // hsl(0,0%,42%) (107). One step of 255 on a muted grey, in a demo panel —
83
+ // non-visual, and taken as-is rather than "corrected" back, because the
84
+ // round number is what the designer is now authoring against.
85
+ //
86
+ // Deliberately NOT named `dark` to match Panel: Panel's `--dark` is the
87
+ // simple case (fill + text colour only, no token remap) and Demo has no
88
+ // equivalent of it — offering a `dark` option here would invite exactly
89
+ // the bug this task flagged (black-on-near-black content going
90
+ // invisible), so the option that exists is spelled `black` and does the
91
+ // full remap, full stop.
92
+ const BASE =
93
+ 'rounded-card border border-border-subtle bg-surface p-6 font-secondary font-light ' +
94
+ '[&_strong]:font-medium [&_b]:font-medium ' +
95
+ 'dark:shadow-[inset_0_2px_1px_-1px_var(--box-primary)]';
96
+
97
+ const VARIANT: Record<Variant, string> = {
98
+ light: 'bg-surface-gray',
99
+ brown: 'bg-surface-brown [--box-primary:var(--JELINEK-BROWN)]',
100
+ magenta: 'bg-surface-magenta [--box-primary:var(--JELINEK-MAGENTA)]',
101
+ black:
102
+ 'bg-surface-black text-fg-inverse ' +
103
+ '[--JELINEK-BLACK:hsl(0,0%,50%)] [--JELINEK-BLACK-HIGHLIGHT:hsl(0,0%,60%)] ' +
104
+ '[--ON-JELINEK-BLACK:hsl(0,0%,5%)] ' +
105
+ '[--text-muted1:hsl(0,0%,62%)] [--text-muted2:hsl(0,0%,42%)]'
106
+ };
107
+ </script>
108
+
109
+ <div {...rest} class={cn(BASE, variant && VARIANT[variant], className)}>
110
+ {@render children()}
111
+ </div>
@@ -0,0 +1,12 @@
1
+ import type { Snippet } from 'svelte';
2
+ import type { HTMLAttributes } from 'svelte/elements';
3
+ type Variant = 'light' | 'brown' | 'magenta' | 'black';
4
+ interface Props {
5
+ variant?: Variant;
6
+ class?: string;
7
+ children: Snippet;
8
+ }
9
+ type $$ComponentProps = Props & HTMLAttributes<HTMLDivElement>;
10
+ declare const Demo: import("svelte").Component<$$ComponentProps, {}, "">;
11
+ type Demo = ReturnType<typeof Demo>;
12
+ export default Demo;
@@ -0,0 +1,70 @@
1
+ <script lang="ts">
2
+ import type { Snippet } from 'svelte';
3
+ import type { HTMLAttributes } from 'svelte/elements';
4
+ import { cn } from '../utils/cn.js';
5
+
6
+ type Variant = 'brown' | 'magenta' | 'black';
7
+
8
+ interface Props {
9
+ variant?: Variant;
10
+ class?: string;
11
+ children: Snippet;
12
+ }
13
+
14
+ let {
15
+ variant = 'brown',
16
+ class: className,
17
+ children,
18
+ ...rest
19
+ }: Props & HTMLAttributes<HTMLParagraphElement> = $props();
20
+
21
+ // jelinek.css:394-399 (.eyebrow): font-weight 700, margin-bottom 0.35em
22
+ // (mb-[0.35em] — not on Tailwind's spacing scale).
23
+ //
24
+ // TASK 31 — THE SIZE IS NOW ON THE MODULAR SCALE. It was a fixed 1.05rem
25
+ // literal (16.8px, deliberately off-scale); the guide replaced it with
26
+ // `font-size: var(--step2)` (jelinek.css:398), which is
27
+ // clamp(1.3125rem, …, 1.5rem) — 21px on a phone growing to 24px from 1024px
28
+ // up. So this is a real, visible size increase of roughly 25-43%, not a
29
+ // re-spelling, and it is deliberate: the guide's own comment on that line
30
+ // calls --step2 the "velikost „h5" (o stupeň pod h4)" — h5's size, one step
31
+ // below h4 — and the section header above it (:343) states the hierarchy
32
+ // this belongs to, "eyebrow / číslo kapitoly (velikost „h5") → H1 → H2 …".
33
+ // The eyebrow is a kicker in the heading hierarchy, so it is sized from the
34
+ // scale like every other member of it. --step2 IS mapped (tokens.css's
35
+ // @theme inline block), so this is the named `text-step2` utility rather
36
+ // than an arbitrary value — and it therefore stays fluid with the viewport
37
+ // on its own, with no variant needed.
38
+ //
39
+ // jelinek.css:373-377 puts .eyebrow in the shared font-secondary selector
40
+ // list (`.eyebrow, .small, .tag, .tab, figcaption, label.field-label, …`)
41
+ // — Avenir/GT Walsheim Pro per tokens.css, NOT the Archer serif body
42
+ // inherits — reproduced as font-secondary.
43
+ const BASE = 'font-secondary font-bold text-step2 mb-[0.35em]';
44
+
45
+ // jelinek.css:396 (.eyebrow, the bare/default rule): color is
46
+ // var(--JELINEK-BROWN), NOT a hardcoded brown — tokens.css:193-200
47
+ // ([data-division="mattress"]) reassigns --JELINEK-BROWN itself to
48
+ // --JELINEK-MAGENTA, so text-jelinek-brown (which resolves through
49
+ // var(--color-jelinek-brown) -> var(--JELINEK-BROWN)) automatically
50
+ // follows the division switch, same mechanism Button/Checkbox already
51
+ // rely on for their own default brown. jelinek.css:400 (.eyebrow--magenta)
52
+ // uses text-jelinek-magenta instead: --JELINEK-MAGENTA is only ever READ
53
+ // by the division re-theme, never itself reassigned by it, so this variant
54
+ // stays magenta regardless of division — verified by reading
55
+ // tokens.css:193-200, which touches --JELINEK-BROWN/-HIGHLIGHT/--bg-brown
56
+ // only. jelinek.css:401 (.eyebrow--black) -> text-jelinek-black, which
57
+ // still correctly inverts under dark mode via --JELINEK-BLACK's own
58
+ // tokens.css dark-mode block (no dark: override needed here, same
59
+ // reasoning Tab.svelte/Tag.svelte document for their own bare
60
+ // jelinek-black usage).
61
+ const VARIANT: Record<Variant, string> = {
62
+ brown: 'text-jelinek-brown',
63
+ magenta: 'text-jelinek-magenta',
64
+ black: 'text-jelinek-black'
65
+ };
66
+ </script>
67
+
68
+ <p {...rest} class={cn(BASE, VARIANT[variant], className)}>
69
+ {@render children()}
70
+ </p>
@@ -0,0 +1,12 @@
1
+ import type { Snippet } from 'svelte';
2
+ import type { HTMLAttributes } from 'svelte/elements';
3
+ type Variant = 'brown' | 'magenta' | 'black';
4
+ interface Props {
5
+ variant?: Variant;
6
+ class?: string;
7
+ children: Snippet;
8
+ }
9
+ type $$ComponentProps = Props & HTMLAttributes<HTMLParagraphElement>;
10
+ declare const Eyebrow: import("svelte").Component<$$ComponentProps, {}, "">;
11
+ type Eyebrow = ReturnType<typeof Eyebrow>;
12
+ export default Eyebrow;
@@ -0,0 +1,75 @@
1
+ <script lang="ts">
2
+ import type { Snippet } from 'svelte';
3
+ import type { HTMLAttributes } from 'svelte/elements';
4
+ import { cn } from '../utils/cn.js';
5
+
6
+ interface Props {
7
+ class?: string;
8
+ children: Snippet;
9
+ }
10
+
11
+ let { class: className, children, ...rest }: Props & HTMLAttributes<HTMLDivElement> = $props();
12
+
13
+ // jelinek.css:1144-1151 (.glassmorphism) — a standalone surface primitive
14
+ // (per task direction, its own component rather than a boolean bolted
15
+ // onto Panel/Card/Demo: the guide treats it as one rule, unrelated to the
16
+ // shared box system those three use — no --box-fill/--box-primary custom
17
+ // properties, no border-border-subtle, no p-6/rounded-card). `backdrop-
18
+ // filter` only produces a visible effect when something behind the
19
+ // element is actually rendered — a flat card in front of a flat
20
+ // background demonstrates nothing, which is why the showcase demo (Task
21
+ // 23 report) places this over a photo, not another surface colour.
22
+ //
23
+ // TASK 31 — THE GUIDE NOW HAS ONE SHARED DEFINITION OF GLASS, and this
24
+ // component is no longer the only thing that uses it. The designer pulled
25
+ // the fill, the border and the filter out into three tokens
26
+ // (jelinek.css:132-136) and pointed .glassmorphism, .site-header__inner,
27
+ // .chapter-nav__inner and the sticky .is-sticky switcher at all three. Every
28
+ // value in this component therefore changed:
29
+ // - background-color: was the literal `rgba(0,0,0,0)` — fully transparent,
30
+ // which is why this component previously emitted no bg-* utility at all.
31
+ // It is now `var(--glass-fill)`: rgba(255,255,255,0.5) light,
32
+ // rgba(0,0,0,0.6) dark. So the glass finally has a fill, and the dark
33
+ // mode difference is carried entirely by that token.
34
+ // - border: was the literal `rgba(255,255,255,0.125)`, now
35
+ // `var(--glass-border)` = rgba(255,255,255,0.75) — SIX TIMES more
36
+ // opaque, and deliberately the same hairline in both themes (the token's
37
+ // own comment says so: "stejná hrana v obou režimech").
38
+ // - backdrop-filter: was `blur(5px) brightness(140%)`, now
39
+ // `var(--glass-filter)` = `blur(25px) saturate(140%)`. Five times the
40
+ // blur, and brightness is replaced by saturation.
41
+ // - the `:root[data-theme="dark"] .glassmorphism` rule is GONE from the
42
+ // guide entirely, so the `dark:` brightness override here goes with it.
43
+ // Dark mode no longer darkens the glass with a filter; it darkens the
44
+ // FILL (--glass-fill flips to rgba(0,0,0,0.6)). One less rule, same
45
+ // effect. The old `dark:backdrop-brightness-[60%]` would now fight the
46
+ // shared filter for no reason.
47
+ // border-radius: 15px is unchanged and still off the radius scale
48
+ // (--radius-card is clamp(10px,1vw,15px), fluid, not a fixed 15px), so it
49
+ // stays arbitrary.
50
+ //
51
+ // WHY THE FILTER IS AN ARBITRARY *PROPERTY* PAIR and not
52
+ // `backdrop-blur-[25px] backdrop-saturate-[140%]`: both compile, but only
53
+ // this form REFERENCES the token. If the designer retunes --glass-filter
54
+ // (he already has, twice), a component holding `blur(25px)` as a literal
55
+ // diverges silently and NO gate catches it — check:css-lock hashes
56
+ // `.glassmorphism`'s rule text, which would still read
57
+ // `var(--glass-filter)` and so would not have changed, and gen-tokens only
58
+ // checks tokens.css. Referencing the token is what makes the retune flow
59
+ // through. Both the prefixed and unprefixed properties are written out
60
+ // because, unlike Tailwind's backdrop-* utilities, an arbitrary property
61
+ // emits exactly what it is given — verified against compiled output, not
62
+ // assumed.
63
+ //
64
+ // box-shadow: var(--shadow-interactive) -> shadow-interactive, the
65
+ // mapped Tailwind token (tokens.css's @theme inline block), tracking
66
+ // both light/dark automatically like every other shadow-* usage in this
67
+ // kit.
68
+ const BASE =
69
+ 'rounded-[15px] bg-[var(--glass-fill)] border border-[var(--glass-border)] shadow-interactive ' +
70
+ '[-webkit-backdrop-filter:var(--glass-filter)] [backdrop-filter:var(--glass-filter)]';
71
+ </script>
72
+
73
+ <div {...rest} class={cn(BASE, className)}>
74
+ {@render children()}
75
+ </div>
@@ -0,0 +1,10 @@
1
+ import type { Snippet } from 'svelte';
2
+ import type { HTMLAttributes } from 'svelte/elements';
3
+ interface Props {
4
+ class?: string;
5
+ children: Snippet;
6
+ }
7
+ type $$ComponentProps = Props & HTMLAttributes<HTMLDivElement>;
8
+ declare const Glass: import("svelte").Component<$$ComponentProps, {}, "">;
9
+ type Glass = ReturnType<typeof Glass>;
10
+ export default Glass;
@@ -0,0 +1,87 @@
1
+ <script lang="ts">
2
+ import type { Snippet } from 'svelte';
3
+ import type { HTMLAttributes } from 'svelte/elements';
4
+ import { cn } from '../utils/cn.js';
5
+
6
+ interface Props {
7
+ cols?: 2 | 3 | 4;
8
+ class?: string;
9
+ children: Snippet;
10
+ }
11
+
12
+ let {
13
+ cols = 3,
14
+ class: className,
15
+ children,
16
+ ...rest
17
+ }: Props & HTMLAttributes<HTMLDivElement> = $props();
18
+
19
+ // jelinek.css:709-717 (.grid/.grid--2/--3/--4): as of the Task 30 merge the
20
+ // guide is MOBILE-FIRST here — base `grid-template-columns: 1fr` for every
21
+ // variant, `@media (min-width: 640px)` gives 2 columns,
22
+ // `@media (min-width: 1024px)` gives .grid--3 three and .grid--4 four.
23
+ // Reproduced 1:1 below.
24
+ //
25
+ // Task 31 RE-VERIFIED this against the merged stylesheet rather than
26
+ // trusting the merge report's claim that grid already matched, and it does —
27
+ // declaration for declaration, including that the old desktop-first
28
+ // `@media (max-width: 900px)` tier became `min-width: 1024px`, not 900. The
29
+ // group's other rule, `.grid .panel, .grid .card, .grid .demo
30
+ // { margin-bottom: 0 }` (:699), has nothing to reproduce either: Card, Panel
31
+ // and Demo all deliberately drop their own `margin-bottom` (see e.g.
32
+ // Panel.svelte's comment on standing rule 5), so there is no margin here for
33
+ // a grid to zero out.
34
+ //
35
+ // THE `min-[620.02px]` / `min-[900.02px]` EPSILON IS GONE, and this is the
36
+ // one place in the kit where removing it is not a rounding decision. The
37
+ // epsilon existed for exactly one reason: the guide expressed this component
38
+ // desktop-first with INCLUSIVE `max-width` boundaries
39
+ // (`@media (max-width: 900px)` / `@media (max-width: 620px)`, both true AT
40
+ // the boundary value), and Tailwind's `max-[620px]:` compiles to the
41
+ // EXCLUSIVE `width < 620px`. `min-[620.02px]:` was the gapless,
42
+ // non-overlapping inverse of `<= 620px`. The brand designer rewrote these
43
+ // rules mobile-first with `min-width` queries, so the inclusive boundary
44
+ // this component was compensating for no longer exists: `min-[640px]:` and
45
+ // `min-[1024px]:` are now EXACT translations of the guide's own queries,
46
+ // character for character. Do not reintroduce an epsilon here — it would
47
+ // now put the kit 0.02px away from the stylesheet instead of onto it.
48
+ //
49
+ // Behaviour by width (the guide's, verbatim):
50
+ // w < 640 -> 1 column, every cols value
51
+ // 640 <= w < 1024 -> 2 columns, every cols value
52
+ // w >= 1024 -> the cols value itself (2/3/4)
53
+ // (.grid--3 and .grid--4 both collapse to 2 columns between 640 and 1024,
54
+ // so cols=4 goes 4 -> 2 -> 1 and never passes through 3; cols=2 has no
55
+ // 1024px rule at all, it is already 2-up from 640px.)
56
+ //
57
+ // Why Tailwind's native `min-*` variant family and NOT arbitrary *media*
58
+ // variants (`[@media(min-width:1024px)]:...`), even though those can express
59
+ // any query verbatim: a prior attempt at the desktop-first version used that
60
+ // form and was reverted after a real `vite build` showed Tailwind sorts
61
+ // arbitrary media variants by the *query text itself*, not by breakpoint
62
+ // magnitude — "620" sorts before "900" lexically regardless of which is
63
+ // numerically narrower. That put the narrow rule after the wide one in the
64
+ // compiled stylesheet and, at equal specificity, the later rule won the
65
+ // cascade across the whole range. Tailwind's native `min-*` family sorts
66
+ // ascending by magnitude instead — confirmed against the compiled CSS for
67
+ // this component, not assumed. Never paper over a future regression here
68
+ // with `!important`: it would break the class-override contract
69
+ // `cn.test.ts` guarantees.
70
+ //
71
+ // gap: var(--space-3) (24px) = gap-6 on the default Tailwind spacing scale
72
+ // (--space-3 is listed passthrough-but-equal to Tailwind's spacing scale in
73
+ // global-constraints.md).
74
+ // Classes are written out per-cols-value in full, not built from a
75
+ // `min-[1024px]:grid-cols-${n}` template, so Tailwind's static scanner can
76
+ // see each one literally — a template string is invisible to it and the
77
+ // class would never be generated.
78
+ const COLS: Record<2 | 3 | 4, string> = {
79
+ 2: 'grid-cols-1 min-[640px]:grid-cols-2',
80
+ 3: 'grid-cols-1 min-[640px]:grid-cols-2 min-[1024px]:grid-cols-3',
81
+ 4: 'grid-cols-1 min-[640px]:grid-cols-2 min-[1024px]:grid-cols-4'
82
+ };
83
+ </script>
84
+
85
+ <div {...rest} class={cn('grid gap-6', COLS[cols], className)}>
86
+ {@render children()}
87
+ </div>
@@ -0,0 +1,11 @@
1
+ import type { Snippet } from 'svelte';
2
+ import type { HTMLAttributes } from 'svelte/elements';
3
+ interface Props {
4
+ cols?: 2 | 3 | 4;
5
+ class?: string;
6
+ children: Snippet;
7
+ }
8
+ type $$ComponentProps = Props & HTMLAttributes<HTMLDivElement>;
9
+ declare const Grid: import("svelte").Component<$$ComponentProps, {}, "">;
10
+ type Grid = ReturnType<typeof Grid>;
11
+ export default Grid;
@@ -0,0 +1,37 @@
1
+ <script lang="ts">
2
+ import type { Snippet } from 'svelte';
3
+ import type { HTMLLabelAttributes } from 'svelte/elements';
4
+ import { cn } from '../utils/cn.js';
5
+
6
+ interface Props {
7
+ for?: string;
8
+ class?: string;
9
+ children: Snippet;
10
+ }
11
+
12
+ let {
13
+ for: forId,
14
+ class: className,
15
+ children,
16
+ ...rest
17
+ }: Props & HTMLLabelAttributes = $props();
18
+
19
+ // jelinek.css:662 (`label.field-label`): display:block, font-weight:500,
20
+ // font-size:0.88rem (fixed literal, not on the --step scale), margin
21
+ // 0 0 6px (mb-1.5). The selector only matches an actual <label> element,
22
+ // which this component always renders, so it always applies. Crucially
23
+ // this rule declares no `color` at all — text colour is left to inherit
24
+ // from context, so it must NOT be forced to text-fg-muted1 as the draft
25
+ // brief did. font-family IS legitimately font-secondary, but from a
26
+ // *separate* rule at jelinek.css:313-318 (the shared Avenir group that
27
+ // also covers .eyebrow/.tag/.tab/etc.), which targets the same
28
+ // `label.field-label` selector — not from the :662 block itself.
29
+ </script>
30
+
31
+ <label
32
+ {...rest}
33
+ for={forId}
34
+ class={cn('mb-1.5 block font-secondary text-[0.88rem] font-medium', className)}
35
+ >
36
+ {@render children()}
37
+ </label>
@@ -0,0 +1,11 @@
1
+ import type { Snippet } from 'svelte';
2
+ import type { HTMLLabelAttributes } from 'svelte/elements';
3
+ interface Props {
4
+ for?: string;
5
+ class?: string;
6
+ children: Snippet;
7
+ }
8
+ type $$ComponentProps = Props & HTMLLabelAttributes;
9
+ declare const Label: import("svelte").Component<$$ComponentProps, {}, "">;
10
+ type Label = ReturnType<typeof Label>;
11
+ export default Label;