@jelinek/ui 0.9.0 → 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.
Files changed (85) hide show
  1. package/dist/components/ChapterNav.svelte +1 -1
  2. package/dist/components/Checkbox.svelte +13 -15
  3. package/dist/components/Checkbox.svelte.d.ts +1 -0
  4. package/dist/components/ContentLogo.svelte +18 -13
  5. package/dist/components/ContentLogo.svelte.d.ts +6 -5
  6. package/dist/components/Grid.svelte +9 -5
  7. package/dist/components/Label.svelte +12 -1
  8. package/dist/components/Label.svelte.d.ts +8 -0
  9. package/dist/components/PageHero.svelte +6 -7
  10. package/dist/components/Pager.svelte +5 -2
  11. package/dist/components/PhotoTile.svelte +4 -2
  12. package/dist/components/ProductCard.svelte +66 -44
  13. package/dist/components/ProductCard.svelte.d.ts +17 -4
  14. package/dist/components/Radio.svelte +12 -8
  15. package/dist/components/Radio.svelte.d.ts +1 -0
  16. package/dist/components/Select.svelte +70 -12
  17. package/dist/components/Select.svelte.d.ts +11 -0
  18. package/dist/components/SiteFooter.svelte +19 -0
  19. package/dist/components/SiteFooter.svelte.d.ts +8 -0
  20. package/dist/components/SiteHeader.svelte +11 -9
  21. package/dist/components/Slider.svelte +6 -11
  22. package/dist/components/Switch.svelte +76 -0
  23. package/dist/components/Switch.svelte.d.ts +12 -0
  24. package/dist/components/TextField.svelte +61 -10
  25. package/dist/components/TextField.svelte.d.ts +13 -0
  26. package/dist/components/Textarea.svelte +92 -0
  27. package/dist/components/Textarea.svelte.d.ts +23 -0
  28. package/dist/components/Tile.svelte +12 -11
  29. package/dist/components/Tile.svelte.d.ts +8 -2
  30. package/dist/components/blocks/CmsButton.svelte +36 -0
  31. package/dist/components/blocks/CmsButton.svelte.d.ts +12 -0
  32. package/dist/components/blocks/CmsColumns.svelte +20 -0
  33. package/dist/components/blocks/CmsColumns.svelte.d.ts +9 -0
  34. package/dist/components/blocks/CmsCover.svelte +71 -0
  35. package/dist/components/blocks/CmsCover.svelte.d.ts +17 -0
  36. package/dist/components/blocks/CmsHeading.svelte +50 -0
  37. package/dist/components/blocks/CmsHeading.svelte.d.ts +14 -0
  38. package/dist/components/blocks/CmsImage.svelte +43 -0
  39. package/dist/components/blocks/CmsImage.svelte.d.ts +13 -0
  40. package/dist/components/blocks/CmsMediaText.svelte +64 -0
  41. package/dist/components/blocks/CmsMediaText.svelte.d.ts +16 -0
  42. package/dist/components/blocks/CmsText.svelte +53 -0
  43. package/dist/components/blocks/CmsText.svelte.d.ts +10 -0
  44. package/dist/components/commerce/Breadcrumbs.svelte +22 -5
  45. package/dist/components/commerce/CartDrawer.svelte +7 -4
  46. package/dist/components/commerce/CartLineItem.svelte +1 -1
  47. package/dist/components/commerce/CheckoutProgress.svelte +1 -1
  48. package/dist/components/commerce/ConfigSection.svelte +2 -2
  49. package/dist/components/commerce/DimensionSlider.svelte +1 -1
  50. package/dist/components/commerce/Drawer.svelte +38 -24
  51. package/dist/components/commerce/Drawer.svelte.d.ts +0 -2
  52. package/dist/components/commerce/EmptyState.svelte +37 -0
  53. package/dist/components/commerce/EmptyState.svelte.d.ts +13 -0
  54. package/dist/components/commerce/FilterChip.svelte +35 -0
  55. package/dist/components/commerce/FilterChip.svelte.d.ts +10 -0
  56. package/dist/components/commerce/FreeShippingBar.svelte +1 -1
  57. package/dist/components/commerce/Gallery.svelte +4 -2
  58. package/dist/components/commerce/ListingToolbar.svelte +62 -0
  59. package/dist/components/commerce/ListingToolbar.svelte.d.ts +19 -0
  60. package/dist/components/commerce/Modal.svelte +31 -21
  61. package/dist/components/commerce/Modal.svelte.d.ts +0 -2
  62. package/dist/components/commerce/OrderSummary.svelte +1 -1
  63. package/dist/components/commerce/Pagination.svelte +115 -0
  64. package/dist/components/commerce/Pagination.svelte.d.ts +14 -0
  65. package/dist/components/commerce/PriceDisplay.svelte +3 -3
  66. package/dist/components/commerce/ProductHeader.svelte +2 -2
  67. package/dist/components/commerce/QuantityStepper.svelte +1 -1
  68. package/dist/components/commerce/RatingStars.svelte +1 -1
  69. package/dist/components/commerce/SearchField.svelte +92 -0
  70. package/dist/components/commerce/SearchField.svelte.d.ts +15 -0
  71. package/dist/components/commerce/SideToggle.svelte +1 -1
  72. package/dist/components/commerce/Skeleton.svelte +4 -1
  73. package/dist/components/commerce/StickyBar.svelte +4 -4
  74. package/dist/components/commerce/StockBadge.svelte +2 -2
  75. package/dist/components/commerce/Toaster.svelte +9 -5
  76. package/dist/components/commerce/TrustBadges.svelte +1 -1
  77. package/dist/components/commerce/overlay.d.ts +14 -0
  78. package/dist/components/commerce/overlay.js +49 -0
  79. package/dist/index.d.ts +14 -0
  80. package/dist/index.js +14 -0
  81. package/dist/theme/base.css +22 -0
  82. package/dist/theme/extras.css +44 -0
  83. package/dist/theme/fonts.css +6 -42
  84. package/dist/theme/tokens.css +16 -9
  85. package/package.json +1 -1
@@ -3,12 +3,17 @@
3
3
  import type { HTMLAnchorAttributes, HTMLAttributes } from 'svelte/elements';
4
4
  import { cn } from '../utils/cn.js';
5
5
 
6
- type Color = 'brown' | 'magenta';
7
-
8
6
  interface Props {
9
7
  href?: string;
8
+ /**
9
+ * Vybraná dlaždice: silný černý rámeček (2px, padding to kompenzuje,
10
+ * dlaždice nezmění rozměr). Bez rozlišení divize — rozhodnutí majitele
11
+ * (14. 8. 2026), stejná neutrální řeč jako formuláře a prstenec
12
+ * fokusu; --JELINEK-BLACK se v tmavém režimu překlopí na bílou.
13
+ * Dřívější `color` prop (hnědá/magenta výplň) zanikl s tímto
14
+ * rozhodnutím.
15
+ */
10
16
  active?: boolean;
11
- color?: Color;
12
17
  class?: string;
13
18
  children: Snippet;
14
19
  }
@@ -16,16 +21,12 @@
16
21
  let {
17
22
  href,
18
23
  active = false,
19
- color = 'brown',
20
24
  class: className,
21
25
  children,
22
26
  ...rest
23
27
  }: Props & Omit<HTMLAnchorAttributes & HTMLAttributes<HTMLDivElement>, 'href'> = $props();
24
28
 
25
- const ACTIVE: Record<Color, string> = {
26
- brown: 'border-jelinek-brown bg-surface-brown',
27
- magenta: 'border-jelinek-magenta bg-surface-magenta'
28
- };
29
+ const ACTIVE = 'border-2 border-jelinek-black p-[21px]';
29
30
 
30
31
  // jelinek.css:474-481 (shared surface, --box-accent unset by .tile so it
31
32
  // defaults to --border-subtle at both gradient stops) collapses to a flat
@@ -68,8 +69,8 @@
68
69
  const BASE =
69
70
  'block rounded-card border border-border-subtle bg-surface p-[22px] ' +
70
71
  'font-secondary font-light text-fg no-underline ' +
71
- 'transition-[box-shadow,transform] duration-[var(--dur-base)] ease-[var(--ease)] ' +
72
- 'shadow-resting hover:-translate-y-0.5 hover:shadow-interactive ' +
72
+ 'transition-[box-shadow,border-color] duration-[var(--dur-base)] ease-[var(--ease)] ' +
73
+ 'shadow-resting hover:shadow-interactive hover:border-jelinek-black ' +
73
74
  '[&_h3]:mb-1.5 [&_h3]:text-step1 [&_h3]:leading-[1.3] ' +
74
75
  '[&_p]:m-0 [&_p]:text-[0.9rem] [&_p]:text-fg-muted1 ' +
75
76
  '[&_strong]:font-medium [&_b]:font-medium ' +
@@ -105,7 +106,7 @@
105
106
  // .tile (jelinek.css:917-928) declares no cursor of its own, so this is a
106
107
  // kit-only addition, not a reproduction — the css-lock gate watches the
107
108
  // guide's rule text and is unaffected.
108
- const classes = $derived(cn(BASE, href && 'cursor-pointer', active && ACTIVE[color], className));
109
+ const classes = $derived(cn(BASE, href && 'cursor-pointer', active && ACTIVE, className));
109
110
  </script>
110
111
 
111
112
  {#if href}
@@ -1,10 +1,16 @@
1
1
  import type { Snippet } from 'svelte';
2
2
  import type { HTMLAnchorAttributes, HTMLAttributes } from 'svelte/elements';
3
- type Color = 'brown' | 'magenta';
4
3
  interface Props {
5
4
  href?: string;
5
+ /**
6
+ * Vybraná dlaždice: silný černý rámeček (2px, padding to kompenzuje,
7
+ * dlaždice nezmění rozměr). Bez rozlišení divize — rozhodnutí majitele
8
+ * (14. 8. 2026), stejná neutrální řeč jako formuláře a prstenec
9
+ * fokusu; --JELINEK-BLACK se v tmavém režimu překlopí na bílou.
10
+ * Dřívější `color` prop (hnědá/magenta výplň) zanikl s tímto
11
+ * rozhodnutím.
12
+ */
6
13
  active?: boolean;
7
- color?: Color;
8
14
  class?: string;
9
15
  children: Snippet;
10
16
  }
@@ -0,0 +1,36 @@
1
+ <script lang="ts">
2
+ import Button from '../Button.svelte';
3
+ import { cn } from '../../utils/cn.js';
4
+
5
+ // Blok CMS „Tlačítko": ZADÁNÍ VÝSLOVNĚ ŘÍKÁ „musí se sjednotit s tlačítky
6
+ // z UI guide, ne navrhovat znovu" — blok je proto jen zarovnávací obal
7
+ // nad komponentou Button. Varianty a barvy jsou Buttonu vlastní.
8
+ interface Props {
9
+ /** Text tlačítka. */
10
+ label: string;
11
+ href: string;
12
+ align?: 'left' | 'center' | 'right';
13
+ variant?: 'primary' | 'secondary';
14
+ color?: 'brown' | 'magenta' | 'black';
15
+ class?: string;
16
+ }
17
+
18
+ let {
19
+ label,
20
+ href,
21
+ align = 'left',
22
+ variant = 'primary',
23
+ color = 'brown',
24
+ class: className
25
+ }: Props = $props();
26
+
27
+ const ALIGN: Record<string, string> = {
28
+ left: 'justify-start',
29
+ center: 'justify-center',
30
+ right: 'justify-end'
31
+ };
32
+ </script>
33
+
34
+ <div class={cn('flex', ALIGN[align], className)}>
35
+ <Button {href} {variant} {color}>{label}</Button>
36
+ </div>
@@ -0,0 +1,12 @@
1
+ interface Props {
2
+ /** Text tlačítka. */
3
+ label: string;
4
+ href: string;
5
+ align?: 'left' | 'center' | 'right';
6
+ variant?: 'primary' | 'secondary';
7
+ color?: 'brown' | 'magenta' | 'black';
8
+ class?: string;
9
+ }
10
+ declare const CmsButton: import("svelte").Component<Props, {}, "">;
11
+ type CmsButton = ReturnType<typeof CmsButton>;
12
+ export default CmsButton;
@@ -0,0 +1,20 @@
1
+ <script lang="ts">
2
+ import type { Snippet } from 'svelte';
3
+ import Grid from '../Grid.svelte';
4
+
5
+ // Blok CMS „Sloupce": 2–4 sloupce, do kterých redaktor vkládá jiné bloky
6
+ // (o jednu úroveň, bez vnořování kontejnerů). Skládání na mobilu řeší
7
+ // PŘESNĚ mřížka guidu (Grid): 1 sloupec, od 640 px 2, od 1024 px plný
8
+ // počet — odpověď na otázku zadání „kdy 4 přejdou na 2 a kdy na 1".
9
+ interface Props {
10
+ cols?: 2 | 3 | 4;
11
+ children: Snippet;
12
+ class?: string;
13
+ }
14
+
15
+ let { cols = 2, children, class: className }: Props = $props();
16
+ </script>
17
+
18
+ <Grid {cols} class={className}>
19
+ {@render children()}
20
+ </Grid>
@@ -0,0 +1,9 @@
1
+ import type { Snippet } from 'svelte';
2
+ interface Props {
3
+ cols?: 2 | 3 | 4;
4
+ children: Snippet;
5
+ class?: string;
6
+ }
7
+ declare const CmsColumns: import("svelte").Component<Props, {}, "">;
8
+ type CmsColumns = ReturnType<typeof CmsColumns>;
9
+ export default CmsColumns;
@@ -0,0 +1,71 @@
1
+ <script lang="ts">
2
+ import Button from '../Button.svelte';
3
+ import { cn } from '../../utils/cn.js';
4
+
5
+ // Blok CMS „Úvodní cover": celoplošná fotka s H1, nadtitulkem, textem
6
+ // a CTA. Vzor povýšený z dema (hero kategorie Nábytek z masivu).
7
+ // ČITELNOST na světlé i tmavé fotce řeší vždy přítomný gradient odspoda
8
+ // (černá 60 % → průhledná) + bílý text — funguje na obou, žádné
9
+ // rozhodování redaktora. Na mobilu je fotka na výšku a text sedí dole
10
+ // nad gradientem, takže se s fotkou nepere.
11
+ interface Props {
12
+ image: string;
13
+ /** Povinný alt fotky (prázdný jen u čistě dekorativního pozadí). */
14
+ imageAlt?: string;
15
+ /** Hlavní nadpis stránky (H1). */
16
+ title: string;
17
+ eyebrow?: string;
18
+ text?: string;
19
+ ctaLabel?: string;
20
+ ctaHref?: string;
21
+ /** Volba výšky ze zadání bloku. */
22
+ height?: 'nizky' | 'stredni' | 'vysoky';
23
+ class?: string;
24
+ }
25
+
26
+ let {
27
+ image,
28
+ imageAlt = '',
29
+ title,
30
+ eyebrow,
31
+ text,
32
+ ctaLabel,
33
+ ctaHref,
34
+ height = 'stredni',
35
+ class: className
36
+ }: Props = $props();
37
+
38
+ const HEIGHT: Record<string, string> = {
39
+ nizky: 'min-h-[320px]',
40
+ stredni: 'min-h-[440px]',
41
+ vysoky: 'min-h-[70dvh]'
42
+ };
43
+ </script>
44
+
45
+ <section class={cn('relative flex items-end overflow-hidden', HEIGHT[height], className)}>
46
+ <img src={image} alt={imageAlt} class="absolute inset-0 h-full w-full object-cover" />
47
+ <div
48
+ class="absolute inset-0 bg-gradient-to-t from-black/60 via-black/20 to-transparent"
49
+ ></div>
50
+ <div class="relative mx-auto w-full max-w-page px-6 pt-24 pb-10">
51
+ {#if eyebrow}
52
+ <!-- Kicker z typografie guidu (tučný, --step2, bez verzálek —
53
+ majitel, 21. 8. 2026); na fotce nese bílou místo hnědé. -->
54
+ <p class="m-0 font-secondary text-step2 font-bold text-white/85">
55
+ {eyebrow}
56
+ </p>
57
+ {/if}
58
+ <h1 class="mt-1 mb-0 font-primary text-step4 font-bold text-white [text-wrap:balance]">
59
+ {title}
60
+ </h1>
61
+ {#if text}
62
+ <!-- Perex je čtený text — Archer, jako perex v PageHero. -->
63
+ <p class="mt-3 mb-0 max-w-[560px] font-serif font-normal text-white/90">{text}</p>
64
+ {/if}
65
+ {#if ctaLabel && ctaHref}
66
+ <div class="mt-5">
67
+ <Button href={ctaHref}>{ctaLabel}</Button>
68
+ </div>
69
+ {/if}
70
+ </div>
71
+ </section>
@@ -0,0 +1,17 @@
1
+ interface Props {
2
+ image: string;
3
+ /** Povinný alt fotky (prázdný jen u čistě dekorativního pozadí). */
4
+ imageAlt?: string;
5
+ /** Hlavní nadpis stránky (H1). */
6
+ title: string;
7
+ eyebrow?: string;
8
+ text?: string;
9
+ ctaLabel?: string;
10
+ ctaHref?: string;
11
+ /** Volba výšky ze zadání bloku. */
12
+ height?: 'nizky' | 'stredni' | 'vysoky';
13
+ class?: string;
14
+ }
15
+ declare const CmsCover: import("svelte").Component<Props, {}, "">;
16
+ type CmsCover = ReturnType<typeof CmsCover>;
17
+ export default CmsCover;
@@ -0,0 +1,50 @@
1
+ <script lang="ts">
2
+ import { cn } from '../../utils/cn.js';
3
+
4
+ // Blok CMS „Nadpis" (docs/chybejici-bloky-cms.md): nadtitulek + nadpis
5
+ // úrovně h2–h4. Vzhled = typografie guidu: eyebrow, nadpisová škála,
6
+ // podtržení. Podtržení patří jen nad text (pravidlo majitele) — a jestli
7
+ // za blokem následuje textový blok, ví jen CMS renderer, proto to řídí
8
+ // prop `rule` (výchozí zapnuto: nadpis v obsahu textem obvykle pokračuje;
9
+ // h4 podtržení nemá nikdy, stejně jako v guidu).
10
+ interface Props {
11
+ /** Text nadpisu. */
12
+ text: string;
13
+ /** Nadtitulek (eyebrow) nad nadpisem. */
14
+ eyebrow?: string;
15
+ /** Úroveň nadpisu v osnově stránky. */
16
+ level?: 'h2' | 'h3' | 'h4';
17
+ /** Podtržení pod nadpisem — vypnout, když za blokem NEnásleduje text. */
18
+ rule?: boolean;
19
+ class?: string;
20
+ }
21
+
22
+ let { text, eyebrow, level = 'h2', rule = true, class: className }: Props = $props();
23
+
24
+ // Velikosti podle typografické škály guidu (h2 step3, h3 step2, h4 step1
25
+ // — stejné hodnoty používají kapitoly kitu).
26
+ const SIZE: Record<'h2' | 'h3' | 'h4', string> = {
27
+ h2: 'text-step5',
28
+ h3: 'text-step4',
29
+ h4: 'text-step3'
30
+ };
31
+ // Nadtitulek = kicker z typografie guidu (brand → Typografie, hierarchie:
32
+ // „Kicker — GT Walsheim Bold, produktová barva“; jelinek.css .eyebrow):
33
+ // tučný, hnědý, velikost --step2, BEZ verzálek — verzálky jsou vyhrazené
34
+ // menu a krátkým titulkům a dřívější uppercase tu byl proti manuálu
35
+ // (majitel, 21. 8. 2026). Stejné třídy nese komponenta Eyebrow.
36
+ const EYEBROW = 'font-secondary text-step2 font-bold text-jelinek-brown';
37
+ </script>
38
+
39
+ <div class={cn('max-w-[720px]', className)}>
40
+ {#if eyebrow}<p class="m-0 {EYEBROW}">{eyebrow}</p>{/if}
41
+ <svelte:element
42
+ this={level}
43
+ class="mt-1 mb-0 font-primary {SIZE[level]} font-bold text-fg [text-wrap:balance]"
44
+ >
45
+ {text}
46
+ </svelte:element>
47
+ {#if rule && level !== 'h4'}
48
+ <hr class="rule" />
49
+ {/if}
50
+ </div>
@@ -0,0 +1,14 @@
1
+ interface Props {
2
+ /** Text nadpisu. */
3
+ text: string;
4
+ /** Nadtitulek (eyebrow) nad nadpisem. */
5
+ eyebrow?: string;
6
+ /** Úroveň nadpisu v osnově stránky. */
7
+ level?: 'h2' | 'h3' | 'h4';
8
+ /** Podtržení pod nadpisem — vypnout, když za blokem NEnásleduje text. */
9
+ rule?: boolean;
10
+ class?: string;
11
+ }
12
+ declare const CmsHeading: import("svelte").Component<Props, {}, "">;
13
+ type CmsHeading = ReturnType<typeof CmsHeading>;
14
+ export default CmsHeading;
@@ -0,0 +1,43 @@
1
+ <script lang="ts">
2
+ import { cn } from '../../utils/cn.js';
3
+
4
+ // Blok CMS „Obrázek": fotka z mediální knihovny s POVINNÝM altem
5
+ // (přístupnost) a volitelným popiskem. Poměr stran si blok vynucuje sám
6
+ // (výchozí 3:2 s ořezem cover) — knihovna nezaručuje žádný poměr, a bez
7
+ // ořezu by velmi vysoká fotka rozbila rytmus stránky. `ratio="auto"`
8
+ // nechá přirozený poměr, když na něm záleží (např. pérovka).
9
+ interface Props {
10
+ src: string;
11
+ /** Povinný alternativní text — bez něj obrázek do CMS nepatří. */
12
+ alt: string;
13
+ /** Popisek pod obrázkem. */
14
+ caption?: string;
15
+ /** Vynucený poměr stran; „auto" = přirozený poměr fotky. */
16
+ ratio?: '3:2' | '4:3' | '16:9' | '1:1' | 'auto';
17
+ class?: string;
18
+ }
19
+
20
+ let { src, alt, caption, ratio = '3:2', class: className }: Props = $props();
21
+
22
+ const RATIO: Record<string, string> = {
23
+ '3:2': 'aspect-[3/2] object-cover',
24
+ '4:3': 'aspect-[4/3] object-cover',
25
+ '16:9': 'aspect-video object-cover',
26
+ '1:1': 'aspect-square object-cover',
27
+ auto: ''
28
+ };
29
+ </script>
30
+
31
+ <figure class={cn('m-0', className)}>
32
+ <img
33
+ {src}
34
+ {alt}
35
+ loading="lazy"
36
+ class="w-full rounded-image shadow-resting {RATIO[ratio]}"
37
+ />
38
+ {#if caption}
39
+ <figcaption class="mt-2 font-secondary text-step-down1 font-light text-fg-muted1">
40
+ {caption}
41
+ </figcaption>
42
+ {/if}
43
+ </figure>
@@ -0,0 +1,13 @@
1
+ interface Props {
2
+ src: string;
3
+ /** Povinný alternativní text — bez něj obrázek do CMS nepatří. */
4
+ alt: string;
5
+ /** Popisek pod obrázkem. */
6
+ caption?: string;
7
+ /** Vynucený poměr stran; „auto" = přirozený poměr fotky. */
8
+ ratio?: '3:2' | '4:3' | '16:9' | '1:1' | 'auto';
9
+ class?: string;
10
+ }
11
+ declare const CmsImage: import("svelte").Component<Props, {}, "">;
12
+ type CmsImage = ReturnType<typeof CmsImage>;
13
+ export default CmsImage;
@@ -0,0 +1,64 @@
1
+ <script lang="ts">
2
+ import type { Snippet } from 'svelte';
3
+ import { cn } from '../../utils/cn.js';
4
+
5
+ // Blok CMS „Obrázek + text": fotka vedle obsahu (obsah = další bloky,
6
+ // typicky Nadpis + Text + Tlačítko — kontejner o jedné úrovni vnoření,
7
+ // přesně podle zadání). Vzor povýšený z dema. NA MOBILU JE FOTKA VŽDY
8
+ // NAHOŘE — web stojí na fotkách, fotka prodává a text vysvětluje
9
+ // (odpověď na otevřenou otázku zadání).
10
+ interface Props {
11
+ image: string;
12
+ imageAlt: string;
13
+ /** Strana obrázku na desktopu. */
14
+ side?: 'left' | 'right';
15
+ /** Poměr stran fotky. */
16
+ ratio?: '3:2' | '4:3' | '1:1';
17
+ /** Fotka od kraje ke kraji (bez rádiusu a bočního okraje). */
18
+ fullBleed?: boolean;
19
+ children: Snippet;
20
+ class?: string;
21
+ }
22
+
23
+ let {
24
+ image,
25
+ imageAlt,
26
+ side = 'right',
27
+ ratio = '4:3',
28
+ fullBleed = false,
29
+ children,
30
+ class: className
31
+ }: Props = $props();
32
+
33
+ const RATIO: Record<string, string> = {
34
+ '3:2': 'aspect-[3/2]',
35
+ '4:3': 'aspect-[4/3]',
36
+ '1:1': 'aspect-square'
37
+ };
38
+ </script>
39
+
40
+ {#if fullBleed}
41
+ <!-- Varianta na celou šířku: fotka bez okrajů, text v kontejneru pod ní
42
+ (na desktopu překrývá spodní část fotky nepotřebujeme — čitelnost). -->
43
+ <section class={cn('', className)}>
44
+ <img src={image} alt={imageAlt} class="w-full {RATIO[ratio]} object-cover" />
45
+ <div class="mx-auto w-full max-w-page px-6 pt-8">
46
+ {@render children()}
47
+ </div>
48
+ </section>
49
+ {:else}
50
+ <section
51
+ class={cn('grid items-center gap-8 min-[1024px]:grid-cols-2 min-[1024px]:gap-10', className)}
52
+ >
53
+ <!-- Mobil: fotka první (DOM pořadí). Desktop: strana podle `side`. -->
54
+ <img
55
+ src={image}
56
+ alt={imageAlt}
57
+ loading="lazy"
58
+ class="w-full rounded-image {RATIO[ratio]} object-cover shadow-resting {side === 'right'
59
+ ? 'min-[1024px]:order-last'
60
+ : ''}"
61
+ />
62
+ <div>{@render children()}</div>
63
+ </section>
64
+ {/if}
@@ -0,0 +1,16 @@
1
+ import type { Snippet } from 'svelte';
2
+ interface Props {
3
+ image: string;
4
+ imageAlt: string;
5
+ /** Strana obrázku na desktopu. */
6
+ side?: 'left' | 'right';
7
+ /** Poměr stran fotky. */
8
+ ratio?: '3:2' | '4:3' | '1:1';
9
+ /** Fotka od kraje ke kraji (bez rádiusu a bočního okraje). */
10
+ fullBleed?: boolean;
11
+ children: Snippet;
12
+ class?: string;
13
+ }
14
+ declare const CmsMediaText: import("svelte").Component<Props, {}, "">;
15
+ type CmsMediaText = ReturnType<typeof CmsMediaText>;
16
+ export default CmsMediaText;
@@ -0,0 +1,53 @@
1
+ <script lang="ts">
2
+ import type { Snippet } from 'svelte';
3
+ import { cn } from '../../utils/cn.js';
4
+
5
+ // Blok CMS „Text": formátovaný text redaktora — odstavce, odrážkové
6
+ // i číslované seznamy, odkazy, tučné. KURZÍVA SE NEPOUŽÍVÁ NIKDY
7
+ // (rozhodnutí majitele 14. 8. 2026) — kdyby ji CMS přece jen poslalo,
8
+ // blok ji narovná na běžný řez, aby se zákaz nedal obejít obsahem.
9
+ // Maximální šířka řádku 720 px drží čitelnost (rozvrzeni#sirka).
10
+ interface Props {
11
+ /** Hotové HTML z CMS. Alternativně jde obsah předat jako children. */
12
+ html?: string;
13
+ children?: Snippet;
14
+ class?: string;
15
+ }
16
+
17
+ let { html, children, class: className }: Props = $props();
18
+
19
+ // PÍSMO: Archer (font-serif) — brand → Typografie: „Odstavec — Archer
20
+ // Book. Plynulý čtený text pro popisy a delší obsah mimo ohraničené
21
+ // bloky.“ Dřívější GT Walsheim Light tu byl proti manuálu (majitel,
22
+ // 21. 8. 2026); Walsheim Light patří drobnému textu a textům
23
+ // v ohraničených blocích, ne odstavcům obsahu.
24
+ const TEXT =
25
+ 'max-w-[720px] font-serif font-normal text-step0 text-fg ' +
26
+ // odstavce a mezery mezi nimi (guide: p margin 0 0 1em, line-height 1.4)
27
+ '[&_p]:my-0 [&_p]:mb-[1em] [&_p]:leading-[1.4] [&_p:last-child]:mb-0 ' +
28
+ // seznamy — odrážky i číslování, s odsazením
29
+ '[&_ul]:my-3 [&_ul]:list-disc [&_ul]:pl-6 ' +
30
+ '[&_ol]:my-3 [&_ol]:list-decimal [&_ol]:pl-6 ' +
31
+ '[&_li]:my-1 ' +
32
+ // odkaz uvnitř odstavce — tučný, akcent, podtržení až na hover (guide a{})
33
+ '[&_a]:font-bold [&_a]:text-jelinek-brown [&_a]:no-underline hover:[&_a]:underline ' +
34
+ // tučné je jediné povolené zvýraznění (Archer Bold je v sadě). Suffix
35
+ // `!`: Demo.svelte kreslí ukázkové panely s [&_strong]:font-medium
36
+ // (Walsheim konvence panelů) a jeho pravidlo by při stejné specificitě
37
+ // vyhrálo pořadím ve stylopisu — blok uvnitř ukázky by měl Archer
38
+ // Medium, zakázaný řez. Bez vnořeného obsahu v Demu by ! nebylo třeba.
39
+ '[&_strong]:font-bold! [&_b]:font-bold! ' +
40
+ // …kurzívu narovnáváme (zákaz platí i pro obsah z CMS)
41
+ '[&_em]:not-italic [&_i]:not-italic';
42
+ </script>
43
+
44
+ <div class={cn(TEXT, className)}>
45
+ {#if html}
46
+ <!-- Obsah přichází z vlastního CMS (redakční vstup), ne od anonymních
47
+ uživatelů — sanitizace je věc aplikace. -->
48
+ <!-- eslint-disable-next-line svelte/no-at-html-tags -->
49
+ {@html html}
50
+ {:else if children}
51
+ {@render children()}
52
+ {/if}
53
+ </div>
@@ -0,0 +1,10 @@
1
+ import type { Snippet } from 'svelte';
2
+ interface Props {
3
+ /** Hotové HTML z CMS. Alternativně jde obsah předat jako children. */
4
+ html?: string;
5
+ children?: Snippet;
6
+ class?: string;
7
+ }
8
+ declare const CmsText: import("svelte").Component<Props, {}, "">;
9
+ type CmsText = ReturnType<typeof CmsText>;
10
+ export default CmsText;
@@ -1,4 +1,11 @@
1
1
  <script lang="ts">
2
+ // Task 48 — UNIFIED WITH THE GUIDE'S BREADCRUMBS (the owner's spec, same
3
+ // treatment PageHero's `crumbs` and the hand-written pages' `.breadcrumbs`
4
+ // carry): a small Avenir UI row, "/" separators in muted2, ancestor links
5
+ // muted darkening on hover, the current page in full text colour — never
6
+ // the accent. The previous "›" + accent-coloured current item was this
7
+ // component's own invention and made the e-shop's trail a different design
8
+ // from the site's.
2
9
  interface Props {
3
10
  items: { href?: string; label: string }[];
4
11
  /** aria-label of the <nav> landmark. */
@@ -6,17 +13,27 @@
6
13
  }
7
14
 
8
15
  let { items, label = 'Drobečková navigace' }: Props = $props();
16
+
17
+ // GT Walsheim Medium — Avenir do drobečků nepatří (majitel, 21. 8. 2026):
18
+ // pravidla znají Avenir jen u podnázvu loga.
19
+ const LINK =
20
+ 'text-fg-muted1 no-underline transition-[color] duration-[var(--dur-base)] ease-[var(--ease)] hover:text-fg hover:no-underline';
9
21
  </script>
10
22
 
11
- <nav aria-label={label} class="overflow-x-auto whitespace-nowrap py-3 [scrollbar-width:none]">
12
- <ol class="flex items-center gap-2 text-(length:--step-down1)">
23
+ <!-- overflow-x: long product trails scroll sideways on a phone instead of
24
+ wrapping — kept from the original component, the guide's static pages
25
+ never grow trails that long. -->
26
+ <nav aria-label={label} class="overflow-x-auto font-secondary whitespace-nowrap py-3 [scrollbar-width:none]">
27
+ <ol
28
+ class="flex items-center gap-2 text-step-down2 font-medium tracking-[0.02em] text-fg-muted1"
29
+ >
13
30
  {#each items as item, i (i)}
14
31
  <li class="flex items-center gap-2">
15
- {#if i > 0}<span aria-hidden="true" class="text-fg-muted2">›</span>{/if}
32
+ {#if i > 0}<span aria-hidden="true" class="text-fg-muted2">/</span>{/if}
16
33
  {#if item.href && i < items.length - 1}
17
- <a href={item.href} class="text-fg-muted1 hover:text-fg">{item.label}</a>
34
+ <a href={item.href} class={LINK}>{item.label}</a>
18
35
  {:else}
19
- <span class="text-(--color-accent-text) font-medium" aria-current="page">{item.label}</span>
36
+ <span class="text-fg" aria-current="page">{item.label}</span>
20
37
  {/if}
21
38
  </li>
22
39
  {/each}
@@ -63,8 +63,10 @@
63
63
  const viewCartLabel = $derived(labels?.viewCart ?? 'Zobrazit košík');
64
64
  </script>
65
65
 
66
- <Drawer {open} {onclose} side="right" labelledby="cart-drawer-title" {closeLabel}>
67
- <header class="border-border-subtle flex items-center justify-between border-b px-5 py-4">
66
+ <Drawer {open} {onclose} side="right" labelledby="cart-drawer-title">
67
+ <!-- Bez vodorovných linek (majitel 14. 8. 2026) — hlavičku a patičku
68
+ odděluje jen vzduch. -->
69
+ <header class="flex items-center justify-between px-5 py-4">
68
70
  <h2 id="cart-drawer-title" class="font-primary text-(length:--step1) font-bold text-fg">
69
71
  {title} ({count})
70
72
  </h2>
@@ -92,7 +94,8 @@
92
94
  />
93
95
  </div>
94
96
  {/if}
95
- <div class="divide-border-subtle divide-y">
97
+ <!-- Řádky odděluje jen vzduch — žádné vodorovné linky (majitel 14. 8. 2026). -->
98
+ <div class="flex flex-col gap-1">
96
99
  {#each items as item (item.id)}
97
100
  <CartLineItem
98
101
  {item}
@@ -110,7 +113,7 @@
110
113
  </div>
111
114
 
112
115
  {#if items.length > 0}
113
- <footer class="border-border-subtle border-t px-5 py-4">
116
+ <footer class="px-5 py-4">
114
117
  <div class="mb-3 flex items-center justify-between text-(length:--step0) font-bold text-fg">
115
118
  <span>{subtotalLabel}</span>
116
119
  <span>{formatMoney(subtotal, currency)}</span>
@@ -26,7 +26,7 @@
26
26
  }: Props = $props();
27
27
  </script>
28
28
 
29
- <div class="flex gap-3 py-4">
29
+ <div class="flex gap-3 py-4 font-secondary font-light">
30
30
  <div class="rounded-image bg-surface-gray h-20 w-20 shrink-0 overflow-hidden">
31
31
  {#if item.imageUrl}
32
32
  <img src={item.imageUrl} alt={item.productName} class="h-full w-full object-cover" />
@@ -11,7 +11,7 @@
11
11
  let { step, steps = ['Rekapitulace', 'Doprava', 'Platba'], onStep }: Props = $props();
12
12
  </script>
13
13
 
14
- <ol class="flex items-center justify-center gap-2 sm:gap-4">
14
+ <ol class="flex items-center justify-center gap-2 font-secondary font-light sm:gap-4">
15
15
  {#each steps as label, i (i)}
16
16
  {@const n = i + 1}
17
17
  <li class="flex items-center gap-2">
@@ -15,8 +15,8 @@
15
15
  let { number, title, hint, children }: Props = $props();
16
16
  </script>
17
17
 
18
- <section class="py-6">
19
- <h3 class="flex items-baseline gap-3 text-(length:--step2) font-bold">
18
+ <section class="py-6 font-secondary font-light">
19
+ <h3 class="flex items-baseline gap-3 text-(length:--step4) font-bold">
20
20
  <span class="text-(--color-accent-text)">{number}.</span>{title}
21
21
  </h3>
22
22
  {#if hint}<p class="text-fg-muted1 mt-1 text-(length:--step-down1)">{hint}</p>{/if}
@@ -41,7 +41,7 @@
41
41
  </script>
42
42
 
43
43
  <div
44
- class="bg-surface border-border-subtle rounded-card mt-2 flex flex-col gap-2 border pt-5 pr-4 pb-4 pl-4"
44
+ class="bg-surface border-border-subtle rounded-card mt-2 flex flex-col gap-2 border font-secondary font-light pt-5 pr-4 pb-4 pl-4"
45
45
  >
46
46
  {#if note}
47
47
  <div class="text-fg-muted1 mb-1 text-(length:--step-down2) font-medium">{note}</div>