@jelinek/ui 0.5.3 → 0.9.1
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/dist/components/ChapterNav.svelte +1 -1
- package/dist/components/Checkbox.svelte +13 -15
- package/dist/components/Checkbox.svelte.d.ts +1 -0
- package/dist/components/ContentLogo.svelte +18 -13
- package/dist/components/ContentLogo.svelte.d.ts +6 -5
- package/dist/components/Grid.svelte +9 -5
- package/dist/components/Label.svelte +12 -1
- package/dist/components/Label.svelte.d.ts +8 -0
- package/dist/components/PageHero.svelte +36 -2
- package/dist/components/PageHero.svelte.d.ts +9 -0
- package/dist/components/Pager.svelte +5 -2
- package/dist/components/PhotoTile.svelte +4 -2
- package/dist/components/ProductCard.svelte +66 -44
- package/dist/components/ProductCard.svelte.d.ts +17 -4
- package/dist/components/Radio.svelte +12 -8
- package/dist/components/Radio.svelte.d.ts +1 -0
- package/dist/components/Select.svelte +70 -12
- package/dist/components/Select.svelte.d.ts +11 -0
- package/dist/components/SiteFooter.svelte +19 -0
- package/dist/components/SiteFooter.svelte.d.ts +8 -0
- package/dist/components/SiteHeader.svelte +11 -9
- package/dist/components/Slider.svelte +6 -11
- package/dist/components/Switch.svelte +76 -0
- package/dist/components/Switch.svelte.d.ts +12 -0
- package/dist/components/TextField.svelte +61 -10
- package/dist/components/TextField.svelte.d.ts +13 -0
- package/dist/components/Textarea.svelte +92 -0
- package/dist/components/Textarea.svelte.d.ts +23 -0
- package/dist/components/Tile.svelte +12 -11
- package/dist/components/Tile.svelte.d.ts +8 -2
- package/dist/components/blocks/CmsButton.svelte +36 -0
- package/dist/components/blocks/CmsButton.svelte.d.ts +12 -0
- package/dist/components/blocks/CmsColumns.svelte +20 -0
- package/dist/components/blocks/CmsColumns.svelte.d.ts +9 -0
- package/dist/components/blocks/CmsCover.svelte +71 -0
- package/dist/components/blocks/CmsCover.svelte.d.ts +17 -0
- package/dist/components/blocks/CmsHeading.svelte +50 -0
- package/dist/components/blocks/CmsHeading.svelte.d.ts +14 -0
- package/dist/components/blocks/CmsImage.svelte +43 -0
- package/dist/components/blocks/CmsImage.svelte.d.ts +13 -0
- package/dist/components/blocks/CmsMediaText.svelte +64 -0
- package/dist/components/blocks/CmsMediaText.svelte.d.ts +16 -0
- package/dist/components/blocks/CmsText.svelte +53 -0
- package/dist/components/blocks/CmsText.svelte.d.ts +10 -0
- package/dist/components/commerce/Breadcrumbs.svelte +41 -0
- package/dist/components/commerce/Breadcrumbs.svelte.d.ts +11 -0
- package/dist/components/commerce/CartDrawer.svelte +131 -0
- package/dist/components/commerce/CartDrawer.svelte.d.ts +37 -0
- package/dist/components/commerce/CartLineItem.svelte +67 -0
- package/dist/components/commerce/CartLineItem.svelte.d.ts +14 -0
- package/dist/components/commerce/CheckoutProgress.svelte +49 -0
- package/dist/components/commerce/CheckoutProgress.svelte.d.ts +11 -0
- package/dist/components/commerce/ConfigSection.svelte +24 -0
- package/dist/components/commerce/ConfigSection.svelte.d.ts +11 -0
- package/dist/components/commerce/DimensionSlider.svelte +86 -0
- package/dist/components/commerce/DimensionSlider.svelte.d.ts +18 -0
- package/dist/components/commerce/Drawer.svelte +81 -0
- package/dist/components/commerce/Drawer.svelte.d.ts +12 -0
- package/dist/components/commerce/EmptyState.svelte +37 -0
- package/dist/components/commerce/EmptyState.svelte.d.ts +13 -0
- package/dist/components/commerce/FilterChip.svelte +35 -0
- package/dist/components/commerce/FilterChip.svelte.d.ts +10 -0
- package/dist/components/commerce/FreeShippingBar.svelte +63 -0
- package/dist/components/commerce/FreeShippingBar.svelte.d.ts +12 -0
- package/dist/components/commerce/Gallery.svelte +194 -0
- package/dist/components/commerce/Gallery.svelte.d.ts +27 -0
- package/dist/components/commerce/ListingToolbar.svelte +62 -0
- package/dist/components/commerce/ListingToolbar.svelte.d.ts +19 -0
- package/dist/components/commerce/Modal.svelte +73 -0
- package/dist/components/commerce/Modal.svelte.d.ts +11 -0
- package/dist/components/commerce/OrderSummary.svelte +85 -0
- package/dist/components/commerce/OrderSummary.svelte.d.ts +25 -0
- package/dist/components/commerce/Pagination.svelte +115 -0
- package/dist/components/commerce/Pagination.svelte.d.ts +14 -0
- package/dist/components/commerce/PriceDisplay.svelte +41 -0
- package/dist/components/commerce/PriceDisplay.svelte.d.ts +14 -0
- package/dist/components/commerce/ProductHeader.svelte +62 -0
- package/dist/components/commerce/ProductHeader.svelte.d.ts +21 -0
- package/dist/components/commerce/ProductStickyBar.svelte +42 -0
- package/dist/components/commerce/ProductStickyBar.svelte.d.ts +14 -0
- package/dist/components/commerce/ProductTabs.svelte +87 -0
- package/dist/components/commerce/ProductTabs.svelte.d.ts +18 -0
- package/dist/components/commerce/QuantityStepper.svelte +44 -0
- package/dist/components/commerce/QuantityStepper.svelte.d.ts +13 -0
- package/dist/components/commerce/RatingStars.svelte +39 -0
- package/dist/components/commerce/RatingStars.svelte.d.ts +9 -0
- package/dist/components/commerce/SearchField.svelte +92 -0
- package/dist/components/commerce/SearchField.svelte.d.ts +15 -0
- package/dist/components/commerce/SideToggle.svelte +79 -0
- package/dist/components/commerce/SideToggle.svelte.d.ts +13 -0
- package/dist/components/commerce/Skeleton.svelte +13 -0
- package/dist/components/commerce/Skeleton.svelte.d.ts +7 -0
- package/dist/components/commerce/StickyBar.svelte +27 -0
- package/dist/components/commerce/StickyBar.svelte.d.ts +8 -0
- package/dist/components/commerce/StockBadge.svelte +45 -0
- package/dist/components/commerce/StockBadge.svelte.d.ts +9 -0
- package/dist/components/commerce/Toaster.svelte +38 -0
- package/dist/components/commerce/Toaster.svelte.d.ts +7 -0
- package/dist/components/commerce/TrustBadges.svelte +26 -0
- package/dist/components/commerce/TrustBadges.svelte.d.ts +16 -0
- package/dist/components/commerce/cart-types.d.ts +12 -0
- package/dist/components/commerce/cart-types.js +1 -0
- package/dist/components/commerce/money.d.ts +14 -0
- package/dist/components/commerce/money.js +26 -0
- package/dist/components/commerce/overlay.d.ts +26 -0
- package/dist/components/commerce/overlay.js +69 -0
- package/dist/components/commerce/toast.d.ts +26 -0
- package/dist/components/commerce/toast.js +44 -0
- package/dist/index.d.ts +41 -0
- package/dist/index.js +41 -0
- package/dist/theme/base.css +56 -7
- package/dist/theme/extras.css +63 -0
- package/dist/theme/fonts.css +6 -42
- package/dist/theme/tokens.css +16 -9
- package/package.json +6 -2
|
@@ -61,7 +61,7 @@
|
|
|
61
61
|
// while `display: none` holds; from 640px up the bar is `absolute`, which is
|
|
62
62
|
// what makes it the containing block for the scroll-affordance fades below.
|
|
63
63
|
const OUTER =
|
|
64
|
-
'hidden z-
|
|
64
|
+
'hidden z-(--z-chapter-nav) px-6 ' +
|
|
65
65
|
'min-[640px]:block min-[640px]:absolute min-[640px]:inset-x-0 ' +
|
|
66
66
|
'min-[640px]:top-[calc(var(--header-h)-11px)]';
|
|
67
67
|
|
|
@@ -6,6 +6,7 @@
|
|
|
6
6
|
checked?: boolean;
|
|
7
7
|
label: string;
|
|
8
8
|
id?: string;
|
|
9
|
+
disabled?: boolean;
|
|
9
10
|
class?: string;
|
|
10
11
|
}
|
|
11
12
|
|
|
@@ -13,6 +14,7 @@
|
|
|
13
14
|
checked = $bindable(false),
|
|
14
15
|
label,
|
|
15
16
|
id,
|
|
17
|
+
disabled = false,
|
|
16
18
|
class: className,
|
|
17
19
|
...rest
|
|
18
20
|
}: Props & Omit<HTMLInputAttributes, 'checked' | 'type'> = $props();
|
|
@@ -31,22 +33,18 @@
|
|
|
31
33
|
// without depending on an ambient .demo wrapper the consumer may not
|
|
32
34
|
// have. gap-2.5 matches the 10px inline gap.
|
|
33
35
|
//
|
|
34
|
-
// ACCENT COLOUR —
|
|
35
|
-
//
|
|
36
|
-
//
|
|
37
|
-
//
|
|
38
|
-
//
|
|
39
|
-
|
|
40
|
-
//
|
|
41
|
-
//
|
|
42
|
-
|
|
43
|
-
// [data-division="mattress"] (tokens.css:161-166) — the same mechanism
|
|
44
|
-
// Button relies on for its brand colours — so this accent still turns
|
|
45
|
-
// magenta automatically on mattress-themed pages, without hardcoding it.
|
|
46
|
-
const LABEL = 'flex cursor-pointer items-center gap-2.5 font-secondary text-step0 text-fg';
|
|
36
|
+
// ACCENT COLOUR — adjudicated by the owner (14. 8. 2026): forms are
|
|
37
|
+
// colour-neutral. Every emphasis in a form is BLACK (--JELINEK-BLACK,
|
|
38
|
+
// which flips to near-white in dark mode so it stays visible); the only
|
|
39
|
+
// colour a form carries is the error state. No division re-theme here —
|
|
40
|
+
// forms look the same in Nábytek and Zdravý spánek.
|
|
41
|
+
const LABEL = 'flex cursor-pointer items-center gap-2.5 font-secondary font-light text-step0 text-fg';
|
|
42
|
+
// Zakázaný stav mluví jako zakázané tlačítko (Button.svelte:117):
|
|
43
|
+
// utlumený text a cursor-not-allowed; políčko samotné tlumí prohlížeč.
|
|
44
|
+
const DISABLED = 'cursor-not-allowed text-fg-muted2';
|
|
47
45
|
</script>
|
|
48
46
|
|
|
49
|
-
<label for={inputId} class={cn(LABEL, className)}>
|
|
50
|
-
<input {...rest} id={inputId} type="checkbox" bind:checked class="accent-jelinek-
|
|
47
|
+
<label for={inputId} class={cn(LABEL, disabled && DISABLED, className)}>
|
|
48
|
+
<input {...rest} id={inputId} type="checkbox" {disabled} bind:checked class="accent-jelinek-black" />
|
|
51
49
|
{label}
|
|
52
50
|
</label>
|
|
@@ -3,13 +3,14 @@
|
|
|
3
3
|
import { cn } from '../utils/cn.js';
|
|
4
4
|
import { DEER_MARK_PATH } from './deer-mark-path.js';
|
|
5
5
|
|
|
6
|
-
type Variant = 'brown'
|
|
6
|
+
type Variant = 'brown';
|
|
7
7
|
|
|
8
8
|
interface Props {
|
|
9
|
-
/**
|
|
10
|
-
*
|
|
11
|
-
*
|
|
12
|
-
*
|
|
9
|
+
/** Jelen je jen HNĚDÝ, nebo BÍLÝ (brand → Znak, Užití s produktovými
|
|
10
|
+
* barvami; majitel, 21. 8. 2026): `brown` pro divizi Nábytek, bez
|
|
11
|
+
* varianty BÍLÝ — divize Zdravý spánek a neutrální užití (interní
|
|
12
|
+
* aplikace). Purpurová varianta NEEXISTUJE — dřívější `magenta` byla
|
|
13
|
+
* chyba proti manuálu a je odstraněná. */
|
|
13
14
|
variant?: Variant;
|
|
14
15
|
/**
|
|
15
16
|
* Sets `--logo-mark` (default matches jelinek.css:743's own inline
|
|
@@ -76,14 +77,13 @@
|
|
|
76
77
|
// actually invisible once the colour scoping below is right.
|
|
77
78
|
const BASE = 'inline-flex items-center gap-[calc(var(--logo-mark)*107/400)] text-fg-inverse';
|
|
78
79
|
|
|
79
|
-
// jelinek.css
|
|
80
|
-
//
|
|
81
|
-
//
|
|
82
|
-
//
|
|
83
|
-
//
|
|
80
|
+
// jelinek.css .content-logo--brown: the brown deer. The division re-theme
|
|
81
|
+
// remaps --JELINEK-BROWN to magenta — for the deer that is a FORBIDDEN
|
|
82
|
+
// state, so under [data-division="mattress"] every lockup flips to WHITE
|
|
83
|
+
// instead; the rule lives in extras.css and hooks on the data-content-logo
|
|
84
|
+
// attribute below, mirroring jelinek.css's own division override.
|
|
84
85
|
const VARIANT: Record<Variant, string> = {
|
|
85
|
-
brown: 'text-jelinek-brown'
|
|
86
|
-
magenta: 'text-jelinek-magenta'
|
|
86
|
+
brown: 'text-jelinek-brown'
|
|
87
87
|
};
|
|
88
88
|
|
|
89
89
|
// jelinek.css:746 (.content-logo__svg): width/height both var(--logo-mark),
|
|
@@ -142,7 +142,12 @@
|
|
|
142
142
|
);
|
|
143
143
|
</script>
|
|
144
144
|
|
|
145
|
-
<div
|
|
145
|
+
<div
|
|
146
|
+
{...rest}
|
|
147
|
+
style={mergedStyle}
|
|
148
|
+
data-content-logo=""
|
|
149
|
+
class={cn(BASE, variant && VARIANT[variant], className)}
|
|
150
|
+
>
|
|
146
151
|
<svg class={SVG} viewBox="0 0 400 400" xmlns="http://www.w3.org/2000/svg" aria-hidden="true">
|
|
147
152
|
<rect width="400" height="400" rx="25" ry="25" fill="#000" />
|
|
148
153
|
<path d={DEER_MARK_PATH} fill="currentColor" />
|
|
@@ -1,10 +1,11 @@
|
|
|
1
1
|
import type { HTMLAttributes } from 'svelte/elements';
|
|
2
|
-
type Variant = 'brown'
|
|
2
|
+
type Variant = 'brown';
|
|
3
3
|
interface Props {
|
|
4
|
-
/**
|
|
5
|
-
*
|
|
6
|
-
*
|
|
7
|
-
*
|
|
4
|
+
/** Jelen je jen HNĚDÝ, nebo BÍLÝ (brand → Znak, Užití s produktovými
|
|
5
|
+
* barvami; majitel, 21. 8. 2026): `brown` pro divizi Nábytek, bez
|
|
6
|
+
* varianty BÍLÝ — divize Zdravý spánek a neutrální užití (interní
|
|
7
|
+
* aplikace). Purpurová varianta NEEXISTUJE — dřívější `magenta` byla
|
|
8
|
+
* chyba proti manuálu a je odstraněná. */
|
|
8
9
|
variant?: Variant;
|
|
9
10
|
/**
|
|
10
11
|
* Sets `--logo-mark` (default matches jelinek.css:743's own inline
|
|
@@ -49,10 +49,11 @@
|
|
|
49
49
|
// Behaviour by width (the guide's, verbatim):
|
|
50
50
|
// w < 640 -> 1 column, every cols value
|
|
51
51
|
// 640 <= w < 1024 -> 2 columns, every cols value
|
|
52
|
-
// w >= 1024 -> the cols value itself
|
|
53
|
-
// (
|
|
54
|
-
//
|
|
55
|
-
//
|
|
52
|
+
// w >= 1024 -> the cols value itself for 2/3; cols=4 steps
|
|
53
|
+
// 3 (>=1024) -> 4 (>=1280) -> 5 (>=1600), so the
|
|
54
|
+
// four-column grid climbs 1/2/3/4/5 with width
|
|
55
|
+
// (owner's request) instead of jumping 2 -> 4.
|
|
56
|
+
// (cols=2 has no 1024px rule at all, it is already 2-up from 640px.)
|
|
56
57
|
//
|
|
57
58
|
// Why Tailwind's native `min-*` variant family and NOT arbitrary *media*
|
|
58
59
|
// variants (`[@media(min-width:1024px)]:...`), even though those can express
|
|
@@ -75,10 +76,13 @@
|
|
|
75
76
|
// `min-[1024px]:grid-cols-${n}` template, so Tailwind's static scanner can
|
|
76
77
|
// see each one literally — a template string is invisible to it and the
|
|
77
78
|
// class would never be generated.
|
|
79
|
+
// cols={4} přidává od 1600 px pátý sloupec (jelinek.css .grid--4, majitel:
|
|
80
|
+
// širší web = víc obsahu do řady; trojka a dvojka zůstávají — jsou volené
|
|
81
|
+
// kvůli obsahu, ne kvůli místu).
|
|
78
82
|
const COLS: Record<2 | 3 | 4, string> = {
|
|
79
83
|
2: 'grid-cols-1 min-[640px]:grid-cols-2',
|
|
80
84
|
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'
|
|
85
|
+
4: 'grid-cols-1 min-[640px]:grid-cols-2 min-[1024px]:grid-cols-3 min-[1280px]:grid-cols-4 min-[1600px]:grid-cols-5'
|
|
82
86
|
};
|
|
83
87
|
</script>
|
|
84
88
|
|
|
@@ -5,12 +5,21 @@
|
|
|
5
5
|
|
|
6
6
|
interface Props {
|
|
7
7
|
for?: string;
|
|
8
|
+
/**
|
|
9
|
+
* Hvězdička povinného pole za popiskem. Jen vizuální značka
|
|
10
|
+
* (aria-hidden) — povinnost samotnou hlásí nativní `required` na poli,
|
|
11
|
+
* které ji zapíná. Černá (--JELINEK-BLACK, v tmavém režimu se sama
|
|
12
|
+
* překlopí na bílou): formuláře jsou podle rozhodnutí majitele
|
|
13
|
+
* (14. 8. 2026) barevně neutrální — barvu nese jen chyba.
|
|
14
|
+
*/
|
|
15
|
+
required?: boolean;
|
|
8
16
|
class?: string;
|
|
9
17
|
children: Snippet;
|
|
10
18
|
}
|
|
11
19
|
|
|
12
20
|
let {
|
|
13
21
|
for: forId,
|
|
22
|
+
required = false,
|
|
14
23
|
class: className,
|
|
15
24
|
children,
|
|
16
25
|
...rest
|
|
@@ -33,5 +42,7 @@
|
|
|
33
42
|
for={forId}
|
|
34
43
|
class={cn('mb-1.5 block font-secondary text-[0.88rem] font-medium', className)}
|
|
35
44
|
>
|
|
36
|
-
{@render children()}
|
|
45
|
+
{@render children()}{#if required}<span aria-hidden="true" class="text-jelinek-black">
|
|
46
|
+
*</span
|
|
47
|
+
>{/if}
|
|
37
48
|
</label>
|
|
@@ -2,6 +2,14 @@ import type { Snippet } from 'svelte';
|
|
|
2
2
|
import type { HTMLLabelAttributes } from 'svelte/elements';
|
|
3
3
|
interface Props {
|
|
4
4
|
for?: string;
|
|
5
|
+
/**
|
|
6
|
+
* Hvězdička povinného pole za popiskem. Jen vizuální značka
|
|
7
|
+
* (aria-hidden) — povinnost samotnou hlásí nativní `required` na poli,
|
|
8
|
+
* které ji zapíná. Černá (--JELINEK-BLACK, v tmavém režimu se sama
|
|
9
|
+
* překlopí na bílou): formuláře jsou podle rozhodnutí majitele
|
|
10
|
+
* (14. 8. 2026) barevně neutrální — barvu nese jen chyba.
|
|
11
|
+
*/
|
|
12
|
+
required?: boolean;
|
|
5
13
|
class?: string;
|
|
6
14
|
children: Snippet;
|
|
7
15
|
}
|
|
@@ -59,6 +59,12 @@
|
|
|
59
59
|
* colour) and crops it with object-fit: cover. Mirrors the guide's
|
|
60
60
|
* `.page-hero__media`. The text content is lifted above it. */
|
|
61
61
|
media?: Snippet;
|
|
62
|
+
/** Task 46 — breadcrumb trail, the hero's first line ("under the menu"):
|
|
63
|
+
* `Sekce / Kapitola`. Entries WITH `href` render as links, the last
|
|
64
|
+
* (current page) is plain text with aria-current. Mirrors the guide's
|
|
65
|
+
* `.breadcrumbs` row (jelinek.css) — no "home" entry, the logo is the
|
|
66
|
+
* way home. */
|
|
67
|
+
crumbs?: { label: string; href?: string }[];
|
|
62
68
|
class?: string;
|
|
63
69
|
}
|
|
64
70
|
|
|
@@ -71,10 +77,20 @@
|
|
|
71
77
|
heading,
|
|
72
78
|
children,
|
|
73
79
|
media,
|
|
80
|
+
crumbs = [],
|
|
74
81
|
class: className,
|
|
75
82
|
...rest
|
|
76
83
|
}: Props & HTMLAttributes<HTMLElement> = $props();
|
|
77
84
|
|
|
85
|
+
// jelinek.css .breadcrumbs — a small muted UI row: step-down2, links darken
|
|
86
|
+
// on hover, current chapter reads in full text colour.
|
|
87
|
+
// GT Walsheim Medium — Avenir do drobečků nepatří (majitel, 21. 8. 2026):
|
|
88
|
+
// pravidla znají Avenir jen u podnázvu loga.
|
|
89
|
+
const CRUMBS =
|
|
90
|
+
'mb-4 flex flex-wrap items-center gap-2 font-secondary text-step-down2 font-medium tracking-[0.02em] text-fg-muted1';
|
|
91
|
+
const CRUMB_LINK =
|
|
92
|
+
'text-fg-muted1 no-underline transition-[color] duration-[var(--dur-base)] ease-[var(--ease)] hover:text-fg hover:no-underline';
|
|
93
|
+
|
|
78
94
|
// `hasContext` must run synchronously during this component's own
|
|
79
95
|
// initialization (Svelte's lifecycle rule for context APIs) — captured
|
|
80
96
|
// into a plain const here, NOT inside `$derived`'s callback, since that
|
|
@@ -255,8 +271,14 @@
|
|
|
255
271
|
"w-fit max-w-full mb-0 after:content-[''] " +
|
|
256
272
|
'after:block after:h-[3px] after:w-[75%] after:rounded-[2px] after:mt-4 after:mb-6';
|
|
257
273
|
|
|
258
|
-
// Which levels the guide's `:is(h1, h2, h3):has(+ .rule)` covers
|
|
259
|
-
|
|
274
|
+
// Which levels the guide's `:is(h1, h2, h3):has(+ .rule + p)` covers —
|
|
275
|
+
// and, Task 46, the owner's "underline only above text" rule: the bar
|
|
276
|
+
// draws only when something follows the heading (the `children` lede).
|
|
277
|
+
// A component cannot inspect what a snippet renders, so "children exist"
|
|
278
|
+
// stands in for the guide CSS's "a <p> follows the marker" — every real
|
|
279
|
+
// hero lede in this showcase is a paragraph, so the two agree; a bare
|
|
280
|
+
// eyebrow+heading hero (brand/znacka.html's shape) loses the bar in both.
|
|
281
|
+
const hasRule = $derived(level !== 'h4' && !!children);
|
|
260
282
|
|
|
261
283
|
// jelinek.css:356-359 (h1-h4; h5/h6 have no counterpart here since `Level`
|
|
262
284
|
// stops at h4). Note this list is NOT the same set as the underline's
|
|
@@ -305,6 +327,18 @@
|
|
|
305
327
|
contain itself" branches below render the identical markup instead of
|
|
306
328
|
two copies drifting apart. -->
|
|
307
329
|
{#snippet content()}
|
|
330
|
+
{#if crumbs.length > 0}
|
|
331
|
+
<nav class={CRUMBS} aria-label="Drobečková navigace">
|
|
332
|
+
{#each crumbs as crumb, i (crumb.label)}
|
|
333
|
+
{#if i > 0}<span class="text-fg-muted2" aria-hidden="true">/</span>{/if}
|
|
334
|
+
{#if crumb.href}
|
|
335
|
+
<a href={crumb.href} class={CRUMB_LINK}>{crumb.label}</a>
|
|
336
|
+
{:else}
|
|
337
|
+
<span class="text-fg" aria-current="page">{crumb.label}</span>
|
|
338
|
+
{/if}
|
|
339
|
+
{/each}
|
|
340
|
+
</nav>
|
|
341
|
+
{/if}
|
|
308
342
|
{#if eyebrow}
|
|
309
343
|
<Eyebrow {variant}>{@render eyebrow()}</Eyebrow>
|
|
310
344
|
{/if}
|
|
@@ -53,6 +53,15 @@ interface Props {
|
|
|
53
53
|
* colour) and crops it with object-fit: cover. Mirrors the guide's
|
|
54
54
|
* `.page-hero__media`. The text content is lifted above it. */
|
|
55
55
|
media?: Snippet;
|
|
56
|
+
/** Task 46 — breadcrumb trail, the hero's first line ("under the menu"):
|
|
57
|
+
* `Sekce / Kapitola`. Entries WITH `href` render as links, the last
|
|
58
|
+
* (current page) is plain text with aria-current. Mirrors the guide's
|
|
59
|
+
* `.breadcrumbs` row (jelinek.css) — no "home" entry, the logo is the
|
|
60
|
+
* way home. */
|
|
61
|
+
crumbs?: {
|
|
62
|
+
label: string;
|
|
63
|
+
href?: string;
|
|
64
|
+
}[];
|
|
56
65
|
class?: string;
|
|
57
66
|
}
|
|
58
67
|
type $$ComponentProps = Props & HTMLAttributes<HTMLElement>;
|
|
@@ -62,15 +62,18 @@
|
|
|
62
62
|
// a 0 basis can always be squeezed into the line). jelinek.css:1116 is
|
|
63
63
|
// `flex: 1 1 200px`; the 200px basis is what makes ROOT's `flex-wrap`
|
|
64
64
|
// actually fire. Changing one without the other is a no-op.
|
|
65
|
+
// font-light, not font-normal: jelinek.css's `.tile, .pager a` moved to
|
|
66
|
+
// weight 300 (typografická revize 21. 8. 2026 — Walsheim má jen
|
|
67
|
+
// Bold/Medium/Light); the bold lives on the inner <strong>.
|
|
65
68
|
const LINK =
|
|
66
69
|
'max-w-[340px] flex-[1_1_200px] rounded-card border border-border-subtle px-5 py-4 font-secondary ' +
|
|
67
|
-
'font-
|
|
70
|
+
'font-light text-fg no-underline shadow-resting hover:shadow-interactive ' +
|
|
68
71
|
'transition-shadow duration-[var(--dur-base)] ease-[var(--ease)] ' +
|
|
69
72
|
'dark:border-t-[var(--border-highlight-white)]';
|
|
70
73
|
|
|
71
74
|
// jelinek.css:1123 (.pager a span): block, 0.78rem (not on the --step
|
|
72
75
|
// scale), text-muted1.
|
|
73
|
-
const SUBLABEL = 'block text-[0.78rem] text-fg-muted1';
|
|
76
|
+
const SUBLABEL = 'block text-[0.78rem] font-light text-fg-muted1';
|
|
74
77
|
</script>
|
|
75
78
|
|
|
76
79
|
<nav {...rest} class={cn(ROOT, className)}>
|
|
@@ -66,8 +66,10 @@
|
|
|
66
66
|
const BASE =
|
|
67
67
|
'group relative flex flex-col justify-end overflow-hidden aspect-[4/3] ' +
|
|
68
68
|
'rounded-section shadow-resting no-underline ' +
|
|
69
|
-
|
|
70
|
-
|
|
69
|
+
// Bez vyskočení na hover (majitel 14. 8. 2026, stejně jako Tile) —
|
|
70
|
+
// zvýrazňuje jen stín.
|
|
71
|
+
'transition-[box-shadow] duration-[var(--dur-base)] ease-[var(--ease)] ' +
|
|
72
|
+
'hover:shadow-interactive ' +
|
|
71
73
|
'dark:shadow-[var(--shadow-resting),inset_0_2px_1px_-1px_var(--box-primary)] ' +
|
|
72
74
|
'dark:hover:shadow-[var(--shadow-interactive),inset_0_2px_1px_-1px_var(--box-primary)]';
|
|
73
75
|
|
|
@@ -2,18 +2,31 @@
|
|
|
2
2
|
import type { Snippet } from 'svelte';
|
|
3
3
|
import type { HTMLAttributes } from 'svelte/elements';
|
|
4
4
|
import { cn } from '../utils/cn.js';
|
|
5
|
-
import
|
|
6
|
-
import
|
|
5
|
+
import PriceDisplay from './commerce/PriceDisplay.svelte';
|
|
6
|
+
import StockBadge from './commerce/StockBadge.svelte';
|
|
7
7
|
|
|
8
8
|
interface Props {
|
|
9
9
|
title: string;
|
|
10
|
+
/** Krátký popis pod názvem — kolekce, materiál, shrnutí konfigurace. */
|
|
10
11
|
description?: string;
|
|
11
12
|
image?: string;
|
|
12
13
|
imageAlt?: string;
|
|
14
|
+
/** Odkaz na detail — celá karta je jeden odkaz, žádné tlačítko uvnitř. */
|
|
13
15
|
href?: string;
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
/**
|
|
16
|
+
/** Cena v celých korunách (formátuje `formatPrice`). */
|
|
17
|
+
price?: number;
|
|
18
|
+
/** Přeškrtnutý původ — vykreslí se jen, když je vyšší než `price`. */
|
|
19
|
+
originalPrice?: number;
|
|
20
|
+
/** Kvalifikátor „od“ před cenou (konfigurovatelné produkty). */
|
|
21
|
+
priceFrom?: boolean;
|
|
22
|
+
/** Dostupnost pod cenou (kompaktní StockBadge). */
|
|
23
|
+
stock?: 'in-stock' | 'on-order' | 'sold-out' | 'lead';
|
|
24
|
+
/** Přepis textu dostupnosti — u stavu `lead` konkrétní lhůta. */
|
|
25
|
+
stockLabel?: string;
|
|
26
|
+
/** Produktová fotka na bílém pozadí (packshot): čtverec bez ořezu.
|
|
27
|
+
Výchozí je ambientní fotka v ořezu 4:3. */
|
|
28
|
+
packshot?: boolean;
|
|
29
|
+
/** Štítek nad názvem, např. <Tag variant="instock">. */
|
|
17
30
|
tag?: Snippet;
|
|
18
31
|
class?: string;
|
|
19
32
|
}
|
|
@@ -24,58 +37,67 @@
|
|
|
24
37
|
image,
|
|
25
38
|
imageAlt = '',
|
|
26
39
|
href,
|
|
27
|
-
|
|
28
|
-
|
|
40
|
+
price,
|
|
41
|
+
originalPrice,
|
|
42
|
+
priceFrom = false,
|
|
43
|
+
stock,
|
|
44
|
+
stockLabel,
|
|
45
|
+
packshot = false,
|
|
29
46
|
tag,
|
|
30
47
|
class: className,
|
|
31
48
|
...rest
|
|
32
|
-
}: Props & HTMLAttributes<
|
|
49
|
+
}: Props & HTMLAttributes<HTMLElement> = $props();
|
|
33
50
|
|
|
34
|
-
//
|
|
35
|
-
//
|
|
36
|
-
//
|
|
37
|
-
//
|
|
38
|
-
//
|
|
39
|
-
//
|
|
40
|
-
//
|
|
51
|
+
// DEFINITIVNÍ PODOBA (audit P2, 21. 8. 2026) — do té doby bylo tohle
|
|
52
|
+
// provizorium složené z Card + Button. Předloha je karta výpisu z dema
|
|
53
|
+
// kategorie (demo/ortopedicke-lamelove-matrace), kterou schválil majitel:
|
|
54
|
+
//
|
|
55
|
+
// - CELÁ KARTA JE JEDEN ODKAZ. Ve výpisu se kliká na produkt, ne na
|
|
56
|
+
// tlačítko v něm — tlačítko uvnitř odkazu je past pro čtečky i palce.
|
|
57
|
+
// Bez `href` se vykreslí jako <div> (čistě prezentační užití).
|
|
58
|
+
// - Neutrální zvýraznění (pravidlo formulářů): klidová hrana
|
|
59
|
+
// border-subtle, na najetí tenká černá + zvednutý stín, ŽÁDNÝ posun.
|
|
60
|
+
// Barva divize na kartě není — nese ji obsah stránky, ne rámeček.
|
|
61
|
+
// - Cena a dostupnost jsou existující atomy PriceDisplay a StockBadge,
|
|
62
|
+
// karta si je nekreslí po svém.
|
|
63
|
+
// - Fotky: ambientní záběry ořez 4:3 (jako PhotoTile), packshoty na bílém
|
|
64
|
+
// pozadí čtverec bez ořezu (`packshot`) — matrace nesmí přijít o rohy.
|
|
65
|
+
const CARD =
|
|
66
|
+
'block overflow-hidden rounded-card border border-border-subtle bg-surface ' +
|
|
67
|
+
'no-underline shadow-resting transition-[border-color,box-shadow] ' +
|
|
68
|
+
'duration-[var(--dur-base)] ease-[var(--ease)]';
|
|
69
|
+
const LINKED = 'hover:border-jelinek-black hover:shadow-interactive';
|
|
41
70
|
</script>
|
|
42
71
|
|
|
43
|
-
<
|
|
72
|
+
<svelte:element this={href ? 'a' : 'div'} {...rest} {href} class={cn(CARD, href && LINKED, className)}>
|
|
44
73
|
{#if image}
|
|
45
|
-
<!--
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
<img src={image} alt={imageAlt} class="aspect-[4/3] w-full rounded-none object-cover" />
|
|
74
|
+
<!-- rounded-none: fotka lícuje s kulatými rohy karty, ořez dělá
|
|
75
|
+
overflow-hidden nahoře — viz stejná poznámka v PhotoTile. -->
|
|
76
|
+
{#if packshot}
|
|
77
|
+
<img src={image} alt={imageAlt} class="aspect-square w-full rounded-none bg-surface object-contain" />
|
|
78
|
+
{:else}
|
|
79
|
+
<img src={image} alt={imageAlt} class="aspect-[4/3] w-full rounded-none object-cover" />
|
|
80
|
+
{/if}
|
|
53
81
|
{/if}
|
|
54
82
|
<div class="p-[18px]">
|
|
55
83
|
{#if tag}
|
|
56
84
|
<div class="mb-2.5">{@render tag()}</div>
|
|
57
85
|
{/if}
|
|
58
|
-
|
|
59
|
-
Card's own [&_h3]:text-step1 [&_h3]:leading-[1.3] descendant rule
|
|
60
|
-
(Card.svelte) — repeating text-step1/leading here would duplicate
|
|
61
|
-
Card, not correct it. text-fg is still needed: Card sets no text
|
|
62
|
-
color of its own (the static site's .card h3 inherits its color
|
|
63
|
-
from body), so without it this heading would fall back to
|
|
64
|
-
whichever default the consuming app happens to have. mt-2/mb-1
|
|
65
|
-
reproduce the demo's inline `margin:8px 0 4px` exactly — no CSS
|
|
66
|
-
rule sets this margin (it's specific to that one demo instance),
|
|
67
|
-
but both values already sit on Tailwind's default spacing scale. -->
|
|
68
|
-
<h3 class="mt-2 mb-1 text-fg">{title}</h3>
|
|
86
|
+
<h3 class="mt-0 mb-1 font-primary text-step1 leading-[1.3] font-bold text-fg">{title}</h3>
|
|
69
87
|
{#if description}
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
<
|
|
88
|
+
<p class="mb-0 font-secondary font-light text-step-down1 leading-[1.3] text-fg-muted1">
|
|
89
|
+
{description}
|
|
90
|
+
</p>
|
|
91
|
+
{/if}
|
|
92
|
+
{#if price !== undefined}
|
|
93
|
+
<div class="mt-2.5 text-fg">
|
|
94
|
+
<PriceDisplay {price} {originalPrice} from={priceFrom} size="sm" />
|
|
95
|
+
</div>
|
|
76
96
|
{/if}
|
|
77
|
-
{#if
|
|
78
|
-
<
|
|
97
|
+
{#if stock}
|
|
98
|
+
<div class="mt-1.5">
|
|
99
|
+
<StockBadge state={stock} label={stockLabel} compact />
|
|
100
|
+
</div>
|
|
79
101
|
{/if}
|
|
80
102
|
</div>
|
|
81
|
-
</
|
|
103
|
+
</svelte:element>
|
|
@@ -2,17 +2,30 @@ import type { Snippet } from 'svelte';
|
|
|
2
2
|
import type { HTMLAttributes } from 'svelte/elements';
|
|
3
3
|
interface Props {
|
|
4
4
|
title: string;
|
|
5
|
+
/** Krátký popis pod názvem — kolekce, materiál, shrnutí konfigurace. */
|
|
5
6
|
description?: string;
|
|
6
7
|
image?: string;
|
|
7
8
|
imageAlt?: string;
|
|
9
|
+
/** Odkaz na detail — celá karta je jeden odkaz, žádné tlačítko uvnitř. */
|
|
8
10
|
href?: string;
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
/**
|
|
11
|
+
/** Cena v celých korunách (formátuje `formatPrice`). */
|
|
12
|
+
price?: number;
|
|
13
|
+
/** Přeškrtnutý původ — vykreslí se jen, když je vyšší než `price`. */
|
|
14
|
+
originalPrice?: number;
|
|
15
|
+
/** Kvalifikátor „od“ před cenou (konfigurovatelné produkty). */
|
|
16
|
+
priceFrom?: boolean;
|
|
17
|
+
/** Dostupnost pod cenou (kompaktní StockBadge). */
|
|
18
|
+
stock?: 'in-stock' | 'on-order' | 'sold-out' | 'lead';
|
|
19
|
+
/** Přepis textu dostupnosti — u stavu `lead` konkrétní lhůta. */
|
|
20
|
+
stockLabel?: string;
|
|
21
|
+
/** Produktová fotka na bílém pozadí (packshot): čtverec bez ořezu.
|
|
22
|
+
Výchozí je ambientní fotka v ořezu 4:3. */
|
|
23
|
+
packshot?: boolean;
|
|
24
|
+
/** Štítek nad názvem, např. <Tag variant="instock">. */
|
|
12
25
|
tag?: Snippet;
|
|
13
26
|
class?: string;
|
|
14
27
|
}
|
|
15
|
-
type $$ComponentProps = Props & HTMLAttributes<
|
|
28
|
+
type $$ComponentProps = Props & HTMLAttributes<HTMLElement>;
|
|
16
29
|
declare const ProductCard: import("svelte").Component<$$ComponentProps, {}, "">;
|
|
17
30
|
type ProductCard = ReturnType<typeof ProductCard>;
|
|
18
31
|
export default ProductCard;
|
|
@@ -8,6 +8,7 @@
|
|
|
8
8
|
value: string;
|
|
9
9
|
label: string;
|
|
10
10
|
id?: string;
|
|
11
|
+
disabled?: boolean;
|
|
11
12
|
class?: string;
|
|
12
13
|
}
|
|
13
14
|
|
|
@@ -15,6 +16,7 @@
|
|
|
15
16
|
value,
|
|
16
17
|
label,
|
|
17
18
|
id,
|
|
19
|
+
disabled = false,
|
|
18
20
|
class: className,
|
|
19
21
|
...rest
|
|
20
22
|
}: Props & Omit<HTMLInputAttributes, 'value' | 'type' | 'name'> = $props();
|
|
@@ -28,15 +30,16 @@
|
|
|
28
30
|
|
|
29
31
|
// Same reasoning as Checkbox.svelte: no CSS rule exists for radio inputs
|
|
30
32
|
// or their label; font-secondary/text-fg reproduces what the guide's own
|
|
31
|
-
// `.demo` wrapper applies ambiently (jelinek.css:322-324)
|
|
32
|
-
//
|
|
33
|
-
//
|
|
34
|
-
//
|
|
35
|
-
|
|
36
|
-
|
|
33
|
+
// `.demo` wrapper applies ambiently (jelinek.css:322-324). Accent is
|
|
34
|
+
// BLACK per the owner's 14. 8. 2026 call — forms are colour-neutral,
|
|
35
|
+
// only the error state carries colour; --JELINEK-BLACK flips to
|
|
36
|
+
// near-white in dark mode.
|
|
37
|
+
const LABEL = 'flex cursor-pointer items-center gap-2.5 font-secondary font-light text-step0 text-fg';
|
|
38
|
+
// Stejná řeč jako Checkbox: utlumený text + cursor-not-allowed.
|
|
39
|
+
const DISABLED = 'cursor-not-allowed text-fg-muted2';
|
|
37
40
|
</script>
|
|
38
41
|
|
|
39
|
-
<label for={inputId} class={cn(LABEL, className)}>
|
|
42
|
+
<label for={inputId} class={cn(LABEL, disabled && DISABLED, className)}>
|
|
40
43
|
<input
|
|
41
44
|
{...rest}
|
|
42
45
|
id={inputId}
|
|
@@ -44,11 +47,12 @@
|
|
|
44
47
|
name={group.name}
|
|
45
48
|
{value}
|
|
46
49
|
{checked}
|
|
50
|
+
{disabled}
|
|
47
51
|
onchange={(event) => {
|
|
48
52
|
group.select(value);
|
|
49
53
|
rest.onchange?.(event);
|
|
50
54
|
}}
|
|
51
|
-
class="accent-jelinek-
|
|
55
|
+
class="accent-jelinek-black"
|
|
52
56
|
/>
|
|
53
57
|
{label}
|
|
54
58
|
</label>
|