@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,294 @@
1
+ <script lang="ts">
2
+ import type { HTMLButtonAttributes } from 'svelte/elements';
3
+ import { cn } from '../utils/cn.js';
4
+
5
+ interface Props {
6
+ value: string;
7
+ /**
8
+ * Reproduces jelinek.css:675-676 (`.swatch__meta code.is-empty`): a
9
+ * structurally-present but non-interactive, permanently-muted state for
10
+ * when there is nothing to copy. Native `disabled` handles both the
11
+ * click-suppression and the focus/keyboard-activation removal that the
12
+ * source's plain `cursor: default` alone doesn't give you.
13
+ */
14
+ disabled?: boolean;
15
+ /**
16
+ * Reproduces the archive's `.swatch__value` row (jelinek.css:1028-1039 +
17
+ * assets/js/jelinek.js:146-160): the code value plus a trailing copy-icon
18
+ * button, spread apart by the row. The icon is a second trigger for the
19
+ * same copy, and it is also where the feedback goes — jelinek.js's
20
+ * `flashCopied` turns the ICON --sem-success-strong for 1100ms and leaves
21
+ * the code text alone, so with the icon on, the text-swap feedback below
22
+ * is off. Default off: bare CodeCopy (prose `code[data-copy]`,
23
+ * jelinek.js:329-336) has no icon in the guide either.
24
+ */
25
+ icon?: boolean;
26
+ /**
27
+ * The guide's OTHER copy control: `code[data-copy]` in prose
28
+ * (assets/js/jelinek.js:329-336) — the dashed-chip typography of a bare
29
+ * <code> (base.css's `code` rule / jelinek.css:274-279), but clickable,
30
+ * with the same "zkopírováno ✓" text-swap this component already does.
31
+ * The DECISION note below predates this prop and stands for the
32
+ * DEFAULT look only: a chip without data-copy stays a bare <code>;
33
+ * this mode exists for the chips the archive marked copyable.
34
+ */
35
+ chip?: boolean;
36
+ /**
37
+ * The guide's `.code-line code` (jelinek.css:985-992): a full-width grey
38
+ * block for one-or-more lines of example code, pre-wrap, muted text that
39
+ * darkens on hover, click copies the whole thing. Use for the multi-line
40
+ * snippets the archive rendered as `p.code-line`.
41
+ */
42
+ block?: boolean;
43
+ /**
44
+ * Visible text when it differs from what lands on the clipboard — the
45
+ * archive's `<code data-copy="33px">33 px</code>` shape. Defaults to
46
+ * `value`.
47
+ */
48
+ label?: string;
49
+ class?: string;
50
+ }
51
+
52
+ let {
53
+ value,
54
+ disabled = false,
55
+ icon = false,
56
+ chip = false,
57
+ block = false,
58
+ label,
59
+ class: className,
60
+ ...rest
61
+ }: Props & Omit<HTMLButtonAttributes, 'type' | 'disabled'> = $props();
62
+
63
+ let copied = $state(false);
64
+ let timer: ReturnType<typeof setTimeout> | undefined;
65
+
66
+ // Ports assets/js/jelinek.js:109-121 (`copyText`) verbatim in shape: the
67
+ // Clipboard API only when `navigator.clipboard` exists AND the page is a
68
+ // secure context (both required — `navigator.clipboard` is `undefined`
69
+ // outside one, e.g. plain http:// or an unsupported browser), else a
70
+ // hidden <textarea> + `document.execCommand('copy')` fallback. The
71
+ // previous version called `navigator.clipboard.writeText` with no guard:
72
+ // in a non-secure context that throws inside an async function (an
73
+ // unhandled rejection), `copied` never flips, and the button silently
74
+ // looks dead.
75
+ function writeToClipboard(text: string): Promise<void> {
76
+ // `!== false` rather than a plain truthy check: real browsers always
77
+ // give `isSecureContext` a genuine boolean, so this is identical to the
78
+ // guide's own check there. jsdom (this file's test environment) never
79
+ // implements the property at all and leaves it `undefined` — treated
80
+ // here as "not known to be insecure", not as falling back.
81
+ if (navigator.clipboard && window.isSecureContext !== false) {
82
+ return navigator.clipboard.writeText(text);
83
+ }
84
+ return new Promise((resolve, reject) => {
85
+ try {
86
+ const ta = document.createElement('textarea');
87
+ ta.value = text;
88
+ ta.style.position = 'fixed';
89
+ ta.style.opacity = '0';
90
+ document.body.appendChild(ta);
91
+ ta.focus();
92
+ ta.select();
93
+ document.execCommand('copy');
94
+ document.body.removeChild(ta);
95
+ resolve();
96
+ } catch (e) {
97
+ reject(e);
98
+ }
99
+ });
100
+ }
101
+
102
+ async function copy(event: MouseEvent & { currentTarget: EventTarget & HTMLButtonElement }) {
103
+ if (disabled) return;
104
+ // try/catch so a rejection (either path above can reject) can never
105
+ // escape as an unhandled promise rejection — on failure the label is
106
+ // left unchanged rather than falsely showing the success text.
107
+ try {
108
+ await writeToClipboard(value);
109
+ copied = true;
110
+ clearTimeout(timer);
111
+ // assets/js/jelinek.js:319-326 — the text-swap window is 900ms; the
112
+ // icon flash (jelinek.js:123-132, flashCopied) is 1100ms. Neither is a
113
+ // round number, both are the guide's own.
114
+ timer = setTimeout(() => (copied = false), icon ? 1100 : 900);
115
+ } catch {
116
+ // Swallowed deliberately — see the try/catch comment above.
117
+ }
118
+ // Own work first, THEN the consumer's handler — same order as
119
+ // Tab/ThemeToggle/Radio. Calling rest.onclick BEFORE the copy attempt
120
+ // (as an earlier version of this fix did) meant a throwing consumer
121
+ // handler suppressed the copy entirely and reintroduced the unhandled
122
+ // rejection finding #3 removed: copy() is async, so a synchronous
123
+ // throw from rest.onclick before the try/catch was reached would
124
+ // reject copy()'s own returned promise, which nothing here awaits.
125
+ rest.onclick?.(event);
126
+ }
127
+
128
+ // Svelte 5 replacement for onDestroy — never mix the two, it breaks hydration.
129
+ $effect(() => () => clearTimeout(timer));
130
+
131
+ // CSS FIDELITY — the brief's CodeCopy (rounded-button border bg-surface-gray
132
+ // px-2 py-1 hover:bg-surface-brown chip) matches NEITHER of the two `code`
133
+ // treatments jelinek.css actually defines, and is wrong on every property:
134
+ //
135
+ // jelinek.css:274-279, generic inline `code` (used in body copy, e.g.
136
+ // ui/komponenty.html:243 `<code>--tag-h</code>`): 12px monospace,
137
+ // `color:var(--text-muted2)`, `border:1px dashed var(--text-muted2)`,
138
+ // `border-radius:4px`, `padding:1px 6px`. Task 33: those two were a hardcoded
139
+ // #ccc until the designer's b45c536 put them on the token, so base.css's
140
+ // reproduction is `var(--color-fg-muted2)` rather than an arbitrary value. But this rule has NO `cursor:pointer` and NO `:hover`
141
+ // state anywhere — it is inert documentation typography for a plain
142
+ // `<code>` element sitting in prose, not a control. Grepping the guide
143
+ // confirms every real usage is exactly that (a bare property/token name
144
+ // mentioned mid-sentence), never something clickable.
145
+ //
146
+ // jelinek.css:669-676, `.swatch__meta code` (used by every real swatch,
147
+ // e.g. brand/barvy.html:64 `<code>#FFFFFF</code>` and
148
+ // assets/js/jelinek.js:142/319, which wires a document-level click
149
+ // listener to exactly this element to copy its `data-copy`/text content):
150
+ // `font-family: ui-monospace, Menlo, Consolas, monospace`, `color:
151
+ // var(--text-muted1)`, `font-size: 0.8rem`, `cursor: pointer`, `border: 0;
152
+ // padding: 0; border-radius: 0` (explicitly STRIPS the generic code
153
+ // chrome), `:hover { color: var(--text-black) }`, and `.is-empty` /
154
+ // `.is-empty:hover` pinning it to `var(--text-muted2)` and `cursor:
155
+ // default` when there is nothing to copy.
156
+ //
157
+ // DECISION: CodeCopy always renders the `.swatch__meta code` treatment,
158
+ // never the dashed-chip one, and gets no variant toggling between them.
159
+ // Reasoning: CodeCopy's own contract (Task 10 interfaces) is a `<button
160
+ // role>` that copies `value` on click and reports the action via
161
+ // aria-label — it is unconditionally an interactive control. The generic
162
+ // `code` rule's whole visual language (a dashed border) is the opposite
163
+ // signal — every real instance of it in the guide is static, cursor
164
+ // unchanged, no hover feedback, used for words like `backdrop-filter` or
165
+ // `.ai` inline in a sentence. Skinning an actual `<button>` as "inert
166
+ // text you can't act on" is a UX/affordance bug, not a faithful
167
+ // reproduction — the *other* rule is the one describing a clickable code
168
+ // value. The dashed-chip look is real and worth having in the kit, but as
169
+ // a plain, non-interactive `<code>` typography default (applied globally
170
+ // to bare `<code>` in prose, the way jelinek.css itself does it with a
171
+ // bare-element selector), not as a mode of a component whose entire job
172
+ // is being clicked. That is out of scope for Task 10's four components.
173
+ //
174
+ // jelinek.css:322-325/327-331 (shared `.card/.panel/.alert/.tile/.demo/
175
+ // .swatch` block) does not apply here: `.swatch__meta code` is never
176
+ // listed in that selector group, and lives inside `.swatch__meta`, not
177
+ // directly inside `.swatch` — its font-family/size/weight are fully
178
+ // specified by :669-676 alone.
179
+ //
180
+ // `.swatch__meta code.is-copied` does not exist in jelinek.css — only the
181
+ // sibling `.swatch__copy` icon button and `.pal-cell` get a
182
+ // `--sem-success-strong` flash (assets/js/jelinek.js's `flashCopied`), and
183
+ // that pattern doesn't transfer here (no separate icon for CodeCopy to
184
+ // flash). But the guide's *other* copy control — assets/js/jelinek.js:
185
+ // 319-326, `code[data-copy]` outside a swatch-value row — is structurally
186
+ // the same shape as CodeCopy (one element is both the trigger and the
187
+ // display), and it DOES give visible feedback: on copy it swaps its own
188
+ // text content to the literal string "zkopírováno ✓" for 900ms, then
189
+ // restores the original. That's reproduced below (`copied ? 'zkopírováno
190
+ // ✓' : value`), alongside the aria-label swap already in place — the
191
+ // text-swap is the sighted-user feedback, the aria-label is the
192
+ // assistive-tech feedback, both present at once.
193
+ const BASE =
194
+ 'rounded-none border-0 bg-transparent p-0 font-[ui-monospace,Menlo,Consolas,monospace] ' +
195
+ 'text-[0.8rem] transition-colors';
196
+ const ENABLED = 'cursor-pointer text-fg-muted1 hover:text-fg';
197
+ const DISABLED = 'cursor-default text-fg-muted2 hover:text-fg-muted2';
198
+
199
+ // base.css's `code` rule (jelinek.css:274-279) restated on the button, since
200
+ // a <button> doesn't inherit it: 12px monospace, muted2, 1px dashed border,
201
+ // 4px radius, 6px side padding. No vertical padding and a tight line-height:
202
+ // on the guide's inline <code> the 1px padding doesn't grow the line box,
203
+ // but on an inline-block button it would — the chip was visibly taller than
204
+ // the archive's until both were dropped.
205
+ // leading must come AFTER text-[12px]: tailwind-merge treats a font-size
206
+ // utility as also owning line-height, so the other order strips the leading
207
+ // (the same trap SiteHeader's NAV_LINK documents).
208
+ const CHIP =
209
+ 'cursor-pointer rounded-[4px] border border-dashed border-fg-muted2 bg-transparent ' +
210
+ 'px-[6px] py-0 font-[ui-monospace,Menlo,Consolas,monospace] text-[12px] leading-[1.3] text-fg-muted2 ' +
211
+ 'transition-colors hover:border-fg-muted1 hover:text-fg-muted1';
212
+
213
+ // jelinek.css:985-992 (`.code-line code`): block, pre-wrap + break-word, 8px/
214
+ // 12px padding, solid --border-subtle, grey fill, muted1 text darkening on
215
+ // hover. Radius 4px comes from the generic code rule it inherits there.
216
+ const BLOCK =
217
+ 'block w-full cursor-pointer rounded-[4px] border border-solid border-border-subtle ' +
218
+ 'bg-surface-gray px-3 py-2 text-left font-[ui-monospace,Menlo,Consolas,monospace] ' +
219
+ 'text-[12px] leading-relaxed whitespace-pre-wrap [word-break:break-word] text-fg-muted1 ' +
220
+ 'transition-colors hover:text-fg';
221
+
222
+ // jelinek.css:1028-1031 (`.swatch__value`): flex row, centred, value and icon
223
+ // pushed apart, 8px gap (--space-1), `margin: 2px 0 4px`. The `.swatch__mode
224
+ // .swatch__value { flex: 1; margin: 3px 0 }` override is the CALLER's business
225
+ // (Swatch's mode row passes its own class), not hardcoded here.
226
+ const ROW = 'flex items-center justify-between gap-2 my-0.5 mb-1';
227
+ // jelinek.css:1032-1039 (`.swatch__copy`): borderless 4px-padded 6px-radius
228
+ // icon, muted2 → fg + grey wash on hover, --sem-success-strong while the
229
+ // flash lasts, 0.3 opacity when there is nothing to copy.
230
+ const ICON =
231
+ 'flex-none cursor-pointer rounded-[6px] border-0 bg-transparent p-1 leading-none ' +
232
+ 'text-fg-muted2 transition-colors hover:bg-surface-gray hover:text-fg ' +
233
+ 'disabled:cursor-default disabled:opacity-30 disabled:hover:bg-transparent ' +
234
+ 'disabled:hover:text-fg-muted2';
235
+ </script>
236
+
237
+ {#if icon}
238
+ <span class={cn(ROW, className)}>
239
+ <button
240
+ {...rest}
241
+ type="button"
242
+ disabled={disabled || undefined}
243
+ onclick={copy}
244
+ aria-label={disabled ? undefined : `Zkopírovat ${value}`}
245
+ class={cn(BASE, disabled ? DISABLED : ENABLED)}
246
+ >
247
+ {label ?? value}
248
+ </button>
249
+ <button
250
+ type="button"
251
+ disabled={disabled || undefined}
252
+ onclick={copy}
253
+ aria-label={disabled ? undefined : copied ? 'Zkopírováno' : `Kopírovat ${value}`}
254
+ class={cn(ICON, copied && 'text-sem-success-strong hover:text-sem-success-strong')}
255
+ >
256
+ <!-- assets/js/jelinek.js:23-28 (COPY_SVG), verbatim geometry. While the
257
+ flash lasts the icon swaps to a tick in the same success green —
258
+ stronger sighted feedback than the archive's colour-only flash. -->
259
+ <svg
260
+ width="15"
261
+ height="15"
262
+ viewBox="0 0 24 24"
263
+ fill="none"
264
+ stroke="currentColor"
265
+ stroke-width="2"
266
+ stroke-linecap="round"
267
+ stroke-linejoin="round"
268
+ aria-hidden="true"
269
+ >
270
+ {#if copied}
271
+ <path d="M20 6L9 17l-5-5"></path>
272
+ {:else}
273
+ <rect x="9" y="9" width="12" height="12" rx="2"></rect>
274
+ <path d="M5 15V5a2 2 0 0 1 2-2h10"></path>
275
+ {/if}
276
+ </svg>
277
+ </button>
278
+ </span>
279
+ {:else}
280
+ <button
281
+ {...rest}
282
+ type="button"
283
+ disabled={disabled || undefined}
284
+ onclick={copy}
285
+ aria-label={disabled ? undefined : copied ? 'Zkopírováno' : `Zkopírovat ${value}`}
286
+ class={block
287
+ ? cn(BLOCK, className)
288
+ : chip
289
+ ? cn(CHIP, className)
290
+ : cn(BASE, disabled ? DISABLED : ENABLED, className)}
291
+ >
292
+ {copied ? 'zkopírováno ✓' : (label ?? value)}
293
+ </button>
294
+ {/if}
@@ -0,0 +1,51 @@
1
+ import type { HTMLButtonAttributes } from 'svelte/elements';
2
+ interface Props {
3
+ value: string;
4
+ /**
5
+ * Reproduces jelinek.css:675-676 (`.swatch__meta code.is-empty`): a
6
+ * structurally-present but non-interactive, permanently-muted state for
7
+ * when there is nothing to copy. Native `disabled` handles both the
8
+ * click-suppression and the focus/keyboard-activation removal that the
9
+ * source's plain `cursor: default` alone doesn't give you.
10
+ */
11
+ disabled?: boolean;
12
+ /**
13
+ * Reproduces the archive's `.swatch__value` row (jelinek.css:1028-1039 +
14
+ * assets/js/jelinek.js:146-160): the code value plus a trailing copy-icon
15
+ * button, spread apart by the row. The icon is a second trigger for the
16
+ * same copy, and it is also where the feedback goes — jelinek.js's
17
+ * `flashCopied` turns the ICON --sem-success-strong for 1100ms and leaves
18
+ * the code text alone, so with the icon on, the text-swap feedback below
19
+ * is off. Default off: bare CodeCopy (prose `code[data-copy]`,
20
+ * jelinek.js:329-336) has no icon in the guide either.
21
+ */
22
+ icon?: boolean;
23
+ /**
24
+ * The guide's OTHER copy control: `code[data-copy]` in prose
25
+ * (assets/js/jelinek.js:329-336) — the dashed-chip typography of a bare
26
+ * <code> (base.css's `code` rule / jelinek.css:274-279), but clickable,
27
+ * with the same "zkopírováno ✓" text-swap this component already does.
28
+ * The DECISION note below predates this prop and stands for the
29
+ * DEFAULT look only: a chip without data-copy stays a bare <code>;
30
+ * this mode exists for the chips the archive marked copyable.
31
+ */
32
+ chip?: boolean;
33
+ /**
34
+ * The guide's `.code-line code` (jelinek.css:985-992): a full-width grey
35
+ * block for one-or-more lines of example code, pre-wrap, muted text that
36
+ * darkens on hover, click copies the whole thing. Use for the multi-line
37
+ * snippets the archive rendered as `p.code-line`.
38
+ */
39
+ block?: boolean;
40
+ /**
41
+ * Visible text when it differs from what lands on the clipboard — the
42
+ * archive's `<code data-copy="33px">33 px</code>` shape. Defaults to
43
+ * `value`.
44
+ */
45
+ label?: string;
46
+ class?: string;
47
+ }
48
+ type $$ComponentProps = Props & Omit<HTMLButtonAttributes, 'type' | 'disabled'>;
49
+ declare const CodeCopy: import("svelte").Component<$$ComponentProps, {}, "">;
50
+ type CodeCopy = ReturnType<typeof CodeCopy>;
51
+ export default CodeCopy;
@@ -0,0 +1,34 @@
1
+ <script lang="ts">
2
+ import { setContext, type Snippet } from 'svelte';
3
+ import type { HTMLAttributes } from 'svelte/elements';
4
+ import { cn } from '../utils/cn.js';
5
+ import { CONTAINER_CONTEXT } from './container-context.js';
6
+
7
+ interface Props {
8
+ class?: string;
9
+ children: Snippet;
10
+ }
11
+
12
+ let { class: className, children, ...rest }: Props & HTMLAttributes<HTMLDivElement> =
13
+ $props();
14
+
15
+ // Lets a descendant (currently only PageHero) detect "an ancestor already
16
+ // applies .container's max-width/padding" without reaching outside its
17
+ // own subtree — see PageHero.svelte's own comment (review round 3,
18
+ // Critical follow-up) for why it needs to know this at all: rendering its
19
+ // own equivalent wrapper INSIDE an existing Container would double the
20
+ // horizontal padding jelinek.css only ever applies once.
21
+ setContext(CONTAINER_CONTEXT, true);
22
+
23
+ // jelinek.css:287 (.container): width:100%, max-width:var(--page-max),
24
+ // margin:0 auto, padding:0 24px. w-full/max-w-page/mx-auto/px-6 reproduce
25
+ // all four values exactly (max-w-page maps to --page-max per the token
26
+ // map). mx-auto/px-6 alone don't touch the top/bottom margin or padding
27
+ // sides, but a <div>'s browser default for both is already 0, so no
28
+ // my-0/py-0 is needed to match the source rule's `0` values.
29
+ const BASE = 'mx-auto w-full max-w-page px-6';
30
+ </script>
31
+
32
+ <div {...rest} class={cn(BASE, className)}>
33
+ {@render children()}
34
+ </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 Container: import("svelte").Component<$$ComponentProps, {}, "">;
9
+ type Container = ReturnType<typeof Container>;
10
+ export default Container;
@@ -0,0 +1,151 @@
1
+ <script lang="ts">
2
+ import type { HTMLAttributes } from 'svelte/elements';
3
+ import { cn } from '../utils/cn.js';
4
+ import { DEER_MARK_PATH } from './deer-mark-path.js';
5
+
6
+ type Variant = 'brown' | 'magenta';
7
+
8
+ interface Props {
9
+ /** jelinek.css:744-745. Left unset for the bare/default rule (mark
10
+ * colour --text-white, jelinek.css:743) — NOT itself a "neutral"
11
+ * variant name, since there is no third `.content-logo--*` modifier
12
+ * in the guide to give it one. */
13
+ variant?: Variant;
14
+ /**
15
+ * Sets `--logo-mark` (default matches jelinek.css:743's own inline
16
+ * default, `96px`). Every other size in this lockup — the gap between
17
+ * mark and text, the mark's own width/height, the title and subtitle
18
+ * font-sizes — is a `calc()` off this one custom property
19
+ * (jelinek.css:743/746/749/754), so resizing the whole lockup is
20
+ * always this one prop, never a font-size or width override on the
21
+ * parts themselves.
22
+ */
23
+ logoMark?: string;
24
+ /**
25
+ * The verzálkový podnázev (uppercase subtitle) — jelinek.css's own
26
+ * comment above the rule (jelinek.css:740-742) names this as one of
27
+ * the lockup's three required parts (mark + "JELÍNEK" + subtitle),
28
+ * describing "právě prohlížený obsah" (the content currently being
29
+ * viewed: a division, campaign, claim, anniversary...). Every real
30
+ * instance in the guide (ui/logo.html, 7 of them) sets one — there is
31
+ * no documented bare-mark-plus-title case — so this is required
32
+ * rather than optional. Plain text: jelinek.css:755's
33
+ * `text-transform: uppercase` does the casing, this prop is never
34
+ * pre-uppercased.
35
+ */
36
+ subtitle: string;
37
+ class?: string;
38
+ }
39
+
40
+ let {
41
+ variant,
42
+ logoMark = '96px',
43
+ subtitle,
44
+ class: className,
45
+ ...rest
46
+ }: Props & HTMLAttributes<HTMLDivElement> = $props();
47
+
48
+ // jelinek.css:740-742 (the Czech comment above the rule): "znak : nadpis :
49
+ // podnázev = 170 : 71 : 28" states the DESIGN INTENT, but the rule that
50
+ // actually ships two lines below it (jelinek.css:754) sets the subtitle's
51
+ // font-size to `calc(var(--logo-mark) * 35 / 170)`, not 28 — confirmed by
52
+ // also reading jelinek.css:751-756 directly rather than trusting the
53
+ // comment's numbers, and cross-checked against SiteHeader.svelte's own
54
+ // subtitle, which independently reproduces the exact same 35/170 ratio
55
+ // for its own comparable text (jelinek.css:379-385) — two unrelated rules
56
+ // agreeing on 35 is strong evidence the comment's "28" is simply stale,
57
+ // not that the CSS is wrong. This component reproduces what the stylesheet
58
+ // actually declares (35), since that's what a browser renders, and flags
59
+ // the mismatch here rather than silently "correcting" either number.
60
+ //
61
+ // jelinek.css:743 (.content-logo): inline-flex, centred, gap computed from
62
+ // --logo-mark (the antler's own right-edge margin, per the rule's own
63
+ // trailing comment "mezera = pravý okraj parohů ve znaku") — same formula
64
+ // SiteHeader.svelte's __inner gap already uses for the same reason. Colour
65
+ // defaults to --text-white (text-fg-inverse) and is picked up by the
66
+ // mark's `currentColor` path below — NOT by the title or subtitle, which
67
+ // each set their own explicit colour two rules down (jelinek.css:749/752)
68
+ // and so do not inherit this. A naive implementation that let title/
69
+ // subtitle inherit this colour instead would go genuinely invisible on a
70
+ // light background in the default (text-white) state; verified this one
71
+ // does not, since both keep their own always-legible colours (near-black
72
+ // title, mid-grey subtitle) regardless of variant, and the mark's own
73
+ // antler artwork sits on an unconditionally opaque black square
74
+ // (jelinek.css:743's rect, see SVG_RECT below) rather than directly on
75
+ // whatever page background surrounds the component — so nothing here is
76
+ // actually invisible once the colour scoping below is right.
77
+ const BASE = 'inline-flex items-center gap-[calc(var(--logo-mark)*107/400)] text-fg-inverse';
78
+
79
+ // jelinek.css:744 (--brown, --JELINEK-BROWN, division-tracking) and :745
80
+ // (--magenta, --JELINEK-MAGENTA, fixed regardless of division) — same
81
+ // token pair and same division-tracking-vs-fixed distinction Eyebrow's
82
+ // own VARIANT already documents for the identical reason (tokens.css's
83
+ // mattress re-theme only ever reassigns --JELINEK-BROWN).
84
+ const VARIANT: Record<Variant, string> = {
85
+ brown: 'text-jelinek-brown',
86
+ magenta: 'text-jelinek-magenta'
87
+ };
88
+
89
+ // jelinek.css:746 (.content-logo__svg): width/height both var(--logo-mark),
90
+ // block, flex: 0 0 auto (CSS's `none` keyword is defined as exactly
91
+ // `0 0 auto` — flex-none reproduces it, same mapping SiteHeader's own
92
+ // LOGO_LINK wrapper uses for its equivalent rule).
93
+ const SVG = 'block h-[var(--logo-mark)] w-[var(--logo-mark)] flex-none';
94
+
95
+ // jelinek.css:743's markup (ui/logo.html, all 7 instances verified
96
+ // byte-identical): the SVG's <rect> is a LITERAL `fill="#000"` attribute
97
+ // with a baked-in rx/ry of 25 (a rounded square badge, NOT the plain
98
+ // square SiteHeader's own mark uses) — not a class, not `currentColor`,
99
+ // so it never re-themes with the division switch or with this
100
+ // component's own `variant` prop; only the antler <path> below does
101
+ // (`fill="currentColor"`, picking up BASE/VARIANT's `color` from the
102
+ // wrapping element). Reproduced as literal SVG attributes for the same
103
+ // reason the guide itself uses them: this rect's colour is deliberately
104
+ // NOT part of the token system.
105
+
106
+ // jelinek.css:747-750 (.content-logo__title): font-primary (GT Walsheim),
107
+ // weight 700, line-height 1, letter-spacing -0.01em, size
108
+ // calc(logo-mark*71/170), colour --text-black (text-fg) — its OWN colour,
109
+ // confirmed above, not inherited from BASE.
110
+ const TITLE =
111
+ 'font-primary text-[calc(var(--logo-mark)*71/170)] leading-none font-bold tracking-[-0.01em] text-fg';
112
+
113
+ // jelinek.css:751-756 (.content-logo__sub): block, colour --text-muted1
114
+ // (text-fg-muted1, its own colour, not inherited), weight 600 (Avenir
115
+ // Demi -> font-semibold), size calc(logo-mark*35/170), tracking 0.064em,
116
+ // uppercase, margin-top 0.3em, line-height 1.1. Font-family is a literal
117
+ // inline "Avenir Next LT Pro"/"Avenir Next"/Arial/sans-serif stack, NOT
118
+ // var(--font-secondary) (which currently also resolves to GT Walsheim Pro
119
+ // per tokens.css) — same fact, same fix, and same reasoning
120
+ // SiteHeader.svelte's own SUBTITLE_STYLE already documents for its
121
+ // byte-identical subtitle rule: kept as a literal inline `style` rather
122
+ // than a Tailwind arbitrary-font-family class, since Tailwind's bracket
123
+ // syntax for a comma-separated, quoted font stack is fragile.
124
+ const SUB =
125
+ 'mt-[0.3em] block text-[calc(var(--logo-mark)*35/170)] leading-[1.1] font-semibold ' +
126
+ 'tracking-[0.064em] text-fg-muted1 uppercase';
127
+ const SUB_STYLE = "font-family: 'Avenir Next LT Pro', 'Avenir Next', Arial, sans-serif;";
128
+
129
+ // `--logo-mark` itself has to be a real inline style (a Tailwind
130
+ // arbitrary-property class can't take a per-instance runtime value — only
131
+ // the calc()s that REFERENCE var(--logo-mark) above are static enough for
132
+ // that). Composed with a consumer's own `style` (via `rest.style`) rather
133
+ // than overwritten, same technique — and same reason — Tag.svelte's
134
+ // `mergedStyle` already uses for its own per-instance `--tag-h`/`--tag-s`
135
+ // custom properties: a bare `style={...}` placed after `{...rest}` would
136
+ // otherwise silently discard whatever `style` a consumer passed in.
137
+ const mergedStyle = $derived(
138
+ [`--logo-mark: ${logoMark};`, rest.style]
139
+ .filter((s): s is string => Boolean(s))
140
+ .map((s) => s.trim().replace(/;+$/, ''))
141
+ .join('; ')
142
+ );
143
+ </script>
144
+
145
+ <div {...rest} style={mergedStyle} class={cn(BASE, variant && VARIANT[variant], className)}>
146
+ <svg class={SVG} viewBox="0 0 400 400" xmlns="http://www.w3.org/2000/svg" aria-hidden="true">
147
+ <rect width="400" height="400" rx="25" ry="25" fill="#000" />
148
+ <path d={DEER_MARK_PATH} fill="currentColor" />
149
+ </svg>
150
+ <span class={TITLE}>JELÍNEK<span class={SUB} style={SUB_STYLE}>{subtitle}</span></span>
151
+ </div>
@@ -0,0 +1,37 @@
1
+ import type { HTMLAttributes } from 'svelte/elements';
2
+ type Variant = 'brown' | 'magenta';
3
+ interface Props {
4
+ /** jelinek.css:744-745. Left unset for the bare/default rule (mark
5
+ * colour --text-white, jelinek.css:743) — NOT itself a "neutral"
6
+ * variant name, since there is no third `.content-logo--*` modifier
7
+ * in the guide to give it one. */
8
+ variant?: Variant;
9
+ /**
10
+ * Sets `--logo-mark` (default matches jelinek.css:743's own inline
11
+ * default, `96px`). Every other size in this lockup — the gap between
12
+ * mark and text, the mark's own width/height, the title and subtitle
13
+ * font-sizes — is a `calc()` off this one custom property
14
+ * (jelinek.css:743/746/749/754), so resizing the whole lockup is
15
+ * always this one prop, never a font-size or width override on the
16
+ * parts themselves.
17
+ */
18
+ logoMark?: string;
19
+ /**
20
+ * The verzálkový podnázev (uppercase subtitle) — jelinek.css's own
21
+ * comment above the rule (jelinek.css:740-742) names this as one of
22
+ * the lockup's three required parts (mark + "JELÍNEK" + subtitle),
23
+ * describing "právě prohlížený obsah" (the content currently being
24
+ * viewed: a division, campaign, claim, anniversary...). Every real
25
+ * instance in the guide (ui/logo.html, 7 of them) sets one — there is
26
+ * no documented bare-mark-plus-title case — so this is required
27
+ * rather than optional. Plain text: jelinek.css:755's
28
+ * `text-transform: uppercase` does the casing, this prop is never
29
+ * pre-uppercased.
30
+ */
31
+ subtitle: string;
32
+ class?: string;
33
+ }
34
+ type $$ComponentProps = Props & HTMLAttributes<HTMLDivElement>;
35
+ declare const ContentLogo: import("svelte").Component<$$ComponentProps, {}, "">;
36
+ type ContentLogo = ReturnType<typeof ContentLogo>;
37
+ export default ContentLogo;