@vamos-ui/react 0.2.0 → 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 (82) hide show
  1. package/dist/blocks/site/_kit-1.d.ts +1 -0
  2. package/dist/blocks/site/_kit-1.js +2 -0
  3. package/dist/blocks/site/cta.js +2 -1
  4. package/dist/blocks/site/faq.js +2 -1
  5. package/dist/blocks/site/feature-grid.js +2 -1
  6. package/dist/blocks/site/gallery-section.js +3 -2
  7. package/dist/blocks/site/hero.js +12 -6
  8. package/dist/blocks/site/logo-strip.d.ts +5 -3
  9. package/dist/blocks/site/logo-strip.js +8 -7
  10. package/dist/blocks/site/media-text.js +2 -1
  11. package/dist/blocks/site/pricing-table.js +2 -1
  12. package/dist/blocks/site/process.js +2 -1
  13. package/dist/blocks/site/site-shell.d.ts +11 -0
  14. package/dist/blocks/site/site-shell.js +43 -16
  15. package/dist/blocks/site/stats-band.d.ts +3 -1
  16. package/dist/blocks/site/stats-band.js +11 -7
  17. package/dist/blocks/site/testimonials.js +2 -1
  18. package/dist/business/index.d.ts +1 -0
  19. package/dist/business/index.js +2 -0
  20. package/dist/business/signer-status.d.ts +53 -0
  21. package/dist/business/signer-status.js +76 -0
  22. package/dist/charts/bar-chart.js +5 -1
  23. package/dist/charts/combo-chart.js +1 -1
  24. package/dist/components/_qr-core.d.ts +26 -0
  25. package/dist/components/_qr-core.js +376 -0
  26. package/dist/components/backup-codes.d.ts +42 -0
  27. package/dist/components/backup-codes.js +67 -0
  28. package/dist/components/breadcrumb.d.ts +2 -2
  29. package/dist/components/breadcrumb.js +1 -1
  30. package/dist/components/command.d.ts +8 -2
  31. package/dist/components/command.js +7 -2
  32. package/dist/components/input.d.ts +1 -1
  33. package/dist/components/input.js +1 -1
  34. package/dist/components/pin-map.d.ts +58 -0
  35. package/dist/components/pin-map.js +167 -0
  36. package/dist/components/qr-code.d.ts +36 -0
  37. package/dist/components/qr-code.js +27 -0
  38. package/dist/components/record-detail-layout.d.ts +2 -2
  39. package/dist/components/record-detail-layout.js +1 -1
  40. package/dist/components/sidebar.js +8 -8
  41. package/dist/components/sla-timer.d.ts +75 -0
  42. package/dist/components/sla-timer.js +142 -0
  43. package/dist/components/slug-input.js +1 -1
  44. package/dist/components/tour.d.ts +60 -0
  45. package/dist/components/tour.js +139 -0
  46. package/dist/index.d.ts +5 -0
  47. package/dist/index.js +5 -0
  48. package/dist/lib/public.d.ts +1 -0
  49. package/dist/lib/public.js +1 -0
  50. package/dist/locale-data/core/holidays.d.ts +1 -1
  51. package/dist/locale-data/core/holidays.js +1 -1
  52. package/dist/media/gallery.js +3 -1
  53. package/dist/motion/core.d.ts +32 -0
  54. package/dist/motion/core.js +77 -0
  55. package/dist/motion/count-up.d.ts +29 -0
  56. package/dist/motion/count-up.js +92 -0
  57. package/dist/motion/index.d.ts +11 -0
  58. package/dist/motion/index.js +13 -0
  59. package/dist/motion/motion-scope.d.ts +17 -0
  60. package/dist/motion/motion-scope.js +16 -0
  61. package/dist/motion/page-transition.d.ts +33 -0
  62. package/dist/motion/page-transition.js +61 -0
  63. package/dist/motion/parallax.d.ts +37 -0
  64. package/dist/motion/parallax.js +90 -0
  65. package/dist/motion/pointer.d.ts +38 -0
  66. package/dist/motion/pointer.js +117 -0
  67. package/dist/motion/reveal.d.ts +72 -0
  68. package/dist/motion/reveal.js +133 -0
  69. package/dist/motion/scroll-shrink.d.ts +18 -0
  70. package/dist/motion/scroll-shrink.js +47 -0
  71. package/dist/motion/skeleton-swap.d.ts +17 -0
  72. package/dist/motion/skeleton-swap.js +43 -0
  73. package/dist/motion/text-reveal.d.ts +30 -0
  74. package/dist/motion/text-reveal.js +29 -0
  75. package/dist/motion/ticker.d.ts +35 -0
  76. package/dist/motion/ticker.js +16 -0
  77. package/dist/sortable/sortable-tree.d.ts +4 -0
  78. package/dist/sortable/sortable-tree.js +16 -4
  79. package/dist/styles/blocks.css +3 -3
  80. package/dist/styles/motion.css +389 -0
  81. package/dist/styles.css +2 -1
  82. package/package.json +5 -1
@@ -17,6 +17,7 @@ export declare const site1Labels: {
17
17
  playVideo: string;
18
18
  pauseVideo: string;
19
19
  resumeVideo: string;
20
+ pauseMotion: string;
20
21
  video: string;
21
22
  openCalendar: string;
22
23
  loadsCalendar: string;
@@ -25,6 +25,7 @@ export const site1Labels = {
25
25
  playVideo: "Play video: {title}",
26
26
  pauseVideo: "Pause background video",
27
27
  resumeVideo: "Play background video",
28
+ pauseMotion: "Pause animation",
28
29
  video: "Video",
29
30
  openCalendar: "Open calendar",
30
31
  loadsCalendar: "Loads the booking calendar from {provider}",
@@ -65,6 +66,7 @@ export const site1LabelsTr = {
65
66
  playVideo: "Videoyu oynat: {title}",
66
67
  pauseVideo: "Arka plan videosunu duraklat",
67
68
  resumeVideo: "Arka plan videosunu oynat",
69
+ pauseMotion: "Animasyonu duraklat",
68
70
  video: "Video",
69
71
  openCalendar: "Takvimi aç",
70
72
  loadsCalendar: "Rezervasyon takvimi {provider} üzerinden yüklenir",
@@ -10,6 +10,7 @@ import { CtaBooking } from "./cta-booking.js";
10
10
  import { EmbedFacade } from "./embed-facade.js";
11
11
  import { formatSitePhone } from "./navbar.js";
12
12
  import { ArrowEnd, CtaList, field, siteTemplate, siteText, useBlockIds } from "./_kit-1.js";
13
+ import { Reveal } from "../../motion/reveal.js";
13
14
  /** `https://wa.me/<digits>?text=<encoded>` from an E.164 number. */
14
15
  export function waLink(e164, text) {
15
16
  const base = `https://wa.me/${e164.replace(/\D/g, "")}`;
@@ -28,7 +29,7 @@ export function Cta(props) {
28
29
  const { base, headingId } = useBlockIds(id, "cta");
29
30
  const sectionTheme = variant === "band" ? { surface: "accent", ...theme } : theme;
30
31
  const inner = { ...props, headingId };
31
- return (_jsx(Section, { theme: sectionTheme, anchor: anchor, headingId: headingId, blockId: base, blockType: "cta", editMode: editMode, components: components, labels: labels, "data-variant": variant, className: cn("vb-cta", className), children: _jsx("div", { className: "vb-reveal", children: variant === "split" ? _jsx(Split, { ...inner }) : variant === "minimal" ? _jsx(Minimal, { ...inner }) : variant === "newsletter" ? _jsx(Newsletter, { ...inner }) : variant === "booking" ? _jsx(Booking, { ...inner }) : _jsx(Band, { ...inner }) }) }));
32
+ return (_jsx(Section, { theme: sectionTheme, anchor: anchor, headingId: headingId, blockId: base, blockType: "cta", editMode: editMode, components: components, labels: labels, "data-variant": variant, className: cn("vb-cta", className), children: _jsx(Reveal, { disabled: editMode, children: variant === "split" ? _jsx(Split, { ...inner }) : variant === "minimal" ? _jsx(Minimal, { ...inner }) : variant === "newsletter" ? _jsx(Newsletter, { ...inner }) : variant === "booking" ? _jsx(Booking, { ...inner }) : _jsx(Band, { ...inner }) }) }));
32
33
  }
33
34
  function Heading({ p, size = "display-l", className }) {
34
35
  return _jsx(BlockHeading, { id: p.headingId, level: p.headingLevel ?? 2, size: size, text: p.title, editMode: p.editMode, field: "props.title", labels: p.labels, locale: p.locale, className: className });
@@ -7,6 +7,7 @@ import { BlockLink } from "../block-link.js";
7
7
  import { RichText } from "../rich-text.js";
8
8
  import { richToText } from "../schema/rich-doc.js";
9
9
  import { ArrowEnd, siteText, useBlockIds } from "./_kit-1.js";
10
+ import { Stagger } from "../../motion/reveal.js";
10
11
  /** `FAQPage` JSON-LD for the items. */
11
12
  export function faqJsonLd(items) {
12
13
  return {
@@ -36,7 +37,7 @@ export function Faq({ variant = "split", eyebrow, title, lead, items, exclusive
36
37
  const answer = (it, i) => (_jsx(RichText, { doc: it.a, headingOffset: 2, components: components, labels: labels, locale: locale, editMode: editMode, field: `props.items.${i}.a`, className: "text-fg-muted" }));
37
38
  const contactEl = contact ? (_jsxs("p", { className: "flex flex-col gap-1 text-small text-fg-muted", children: [_jsx("span", { children: t("faqMore") }), _jsxs(BlockLink, { href: contact.link.href, newTab: contact.link.newTab, appearance: "link", components: components, labels: labels, locale: locale, className: "w-fit text-body", children: [contact.link.label, _jsx(ArrowEnd, {})] })] })) : null;
38
39
  const header = title || eyebrow || lead ? _jsx(SectionHeader, { eyebrow: eyebrow, title: title, lead: lead, headingLevel: headingLevel, headingId: headingId, editMode: editMode, labels: labels, locale: locale }) : null;
39
- const list = variant === "columns" ? (_jsx("dl", { className: "grid gap-x-(--gutter) @4xl:grid-cols-2", children: items.map((it, i) => (_jsxs("div", { id: itemId(it), className: "flex flex-col gap-3 border-t border-line py-7", children: [_jsx("dt", { className: "text-h3 font-semibold tracking-[-0.012em] text-fg text-balance", "data-field": editMode ? `props.items.${i}.q` : undefined, children: it.q }), _jsx("dd", { className: "m-0", children: answer(it, i) })] }, i))) })) : (_jsx("div", { className: "vb-faq-list border-b border-line", children: items.map((it, i) => (_jsxs("details", { id: itemId(it), name: exclusive ? `${base}-faq` : undefined, open: openFirst && i === 0 ? true : undefined, className: "vb-faq-item group/faq border-t border-line", children: [_jsxs("summary", { className: "vb-faq-q flex cursor-pointer list-none items-start justify-between gap-6 py-6 text-start focus-ring rounded-[2px]", children: [_jsx(QTag, { className: "text-h3 font-semibold tracking-[-0.012em] text-fg text-balance", "data-field": editMode ? `props.items.${i}.q` : undefined, children: it.q }), _jsx("span", { "aria-hidden": true, className: "vb-faq-marker relative mt-[0.45em] size-3.5 shrink-0" })] }), _jsx("div", { className: "vb-faq-a max-w-[64ch] pb-7 pe-10", children: answer(it, i) })] }, i))) }));
40
+ const list = variant === "columns" ? (_jsx(Stagger, { asChild: true, effect: "fade", disabled: editMode, children: _jsx("dl", { className: "grid gap-x-(--gutter) @4xl:grid-cols-2", children: items.map((it, i) => (_jsxs("div", { id: itemId(it), className: "flex flex-col gap-3 border-t border-line py-7", children: [_jsx("dt", { className: "text-h3 font-semibold tracking-[-0.012em] text-fg text-balance", "data-field": editMode ? `props.items.${i}.q` : undefined, children: it.q }), _jsx("dd", { className: "m-0", children: answer(it, i) })] }, i))) }) })) : (_jsx(Stagger, { asChild: true, effect: "fade", disabled: editMode, children: _jsx("div", { className: "vb-faq-list border-b border-line", children: items.map((it, i) => (_jsxs("details", { id: itemId(it), name: exclusive ? `${base}-faq` : undefined, open: openFirst && i === 0 ? true : undefined, className: "vb-faq-item group/faq border-t border-line", children: [_jsxs("summary", { className: "vb-faq-q flex cursor-pointer list-none items-start justify-between gap-6 py-6 text-start focus-ring rounded-[2px]", children: [_jsx(QTag, { className: "text-h3 font-semibold tracking-[-0.012em] text-fg text-balance", "data-field": editMode ? `props.items.${i}.q` : undefined, children: it.q }), _jsx("span", { "aria-hidden": true, className: "vb-faq-marker relative mt-[0.45em] size-3.5 shrink-0" })] }), _jsx("div", { className: "vb-faq-a max-w-[64ch] pb-7 pe-10", children: answer(it, i) })] }, i))) }) }));
40
41
  let content;
41
42
  if (variant === "split") {
42
43
  content = (_jsxs(Container, { className: "grid gap-x-(--gutter) gap-y-10 @4xl:grid-cols-12", children: [_jsx("div", { className: "@4xl:col-span-4", children: _jsxs("div", { className: "flex flex-col gap-8 @4xl:sticky @4xl:top-[calc(var(--vb-nav-h,4.5rem)+2rem)]", children: [header, contactEl] }) }), _jsx("div", { className: "@4xl:col-span-7 @4xl:col-start-6", children: list })] }));
@@ -7,6 +7,7 @@ import { BlockLink } from "../block-link.js";
7
7
  import { FeatureTabs } from "./feature-tabs.js";
8
8
  import { SiteIcon } from "./_site-icon-1.js";
9
9
  import { ArrowEnd, indexLabel, useBlockIds } from "./_kit-1.js";
10
+ import { Stagger } from "../../motion/reveal.js";
10
11
  const plain = (n) => (typeof n === "string" ? n : undefined);
11
12
  /**
12
13
  * Services, treatments or product modules — without the card wall. Server component; the `tabs`
@@ -45,5 +46,5 @@ export function FeatureGrid({ variant = "list", eyebrow, title, lead, actions, i
45
46
  else {
46
47
  body = (_jsx("ol", { className: "grid gap-x-(--gutter) @4xl:grid-cols-2", children: items.map((it, i) => (_jsxs("li", { className: cn("group/fi relative grid grid-cols-[3rem_1fr] gap-x-4 gap-y-3 border-t border-line py-7 @2xl:grid-cols-[4.5rem_1fr]", it.link && "vb-linked", !showIndex && "grid-cols-1!"), children: [showIndex ? _jsx("span", { className: "row-span-3 pt-1.5 font-mono text-eyebrow tabular text-fg-subtle", children: indexLabel(i) }) : null, itemTitle(it, i), itemText(it, i, "max-w-[52ch]"), more(it)] }, i))) }));
47
48
  }
48
- return (_jsx(Section, { theme: theme, anchor: anchor, headingId: title ? headingId : undefined, blockId: base, blockType: "features", editMode: editMode, components: components, labels: labels, className: cn("vb-features", className), "data-variant": v, children: _jsxs(Container, { className: "flex flex-col gap-[calc(var(--section-y)*0.45)]", children: [title || eyebrow || lead ? (_jsx(SectionHeader, { eyebrow: eyebrow, title: title, lead: lead, align: lead ? "split" : "start", headingLevel: headingLevel, headingId: headingId, editMode: editMode, labels: labels, locale: locale, actions: actions?.length ? actions.map((a, i) => (_jsxs(BlockLink, { href: a.link.href, newTab: a.link.newTab, appearance: a.style ?? "link", ...linkProps, children: [a.link.label, (a.style ?? "link") === "link" ? _jsx(ArrowEnd, {}) : null] }, i))) : undefined })) : null, _jsx("div", { className: v === "list" || v === "grid" ? "vb-reveal" : undefined, children: body })] }) }));
49
+ return (_jsx(Section, { theme: theme, anchor: anchor, headingId: title ? headingId : undefined, blockId: base, blockType: "features", editMode: editMode, components: components, labels: labels, className: cn("vb-features", className), "data-variant": v, children: _jsxs(Container, { className: "flex flex-col gap-[calc(var(--section-y)*0.45)]", children: [title || eyebrow || lead ? (_jsx(SectionHeader, { eyebrow: eyebrow, title: title, lead: lead, align: lead ? "split" : "start", headingLevel: headingLevel, headingId: headingId, editMode: editMode, labels: labels, locale: locale, actions: actions?.length ? actions.map((a, i) => (_jsxs(BlockLink, { href: a.link.href, newTab: a.link.newTab, appearance: a.style ?? "link", ...linkProps, children: [a.link.label, (a.style ?? "link") === "link" ? _jsx(ArrowEnd, {}) : null] }, i))) : undefined })) : null, v === "list" || v === "grid" ? (_jsx(Stagger, { asChild: true, disabled: editMode, children: body })) : (_jsx("div", { children: body }))] }) }));
49
50
  }
@@ -7,6 +7,7 @@ import { LightboxTrigger } from "../../media/lightbox.js";
7
7
  import { site2Text } from "./shared/labels-2.js";
8
8
  import { textOf, useBlockIds2 } from "./shared/kit-2.js";
9
9
  import { GalleryLightbox } from "./shared/gallery-lightbox.js";
10
+ import { Stagger } from "../../motion/reveal.js";
10
11
  /**
11
12
  * Venue, food and project photos — the section restaurants, spas and hotels live on. Server
12
13
  * grid; the lightbox is the Media group's viewer (focus trap, Esc, arrows, "Image 3 of 12"),
@@ -27,14 +28,14 @@ export function GallerySection({ variant: variantProp = "mosaic", eyebrow, title
27
28
  body = (_jsxs("div", { className: "flex flex-col gap-4", children: [_jsx("ul", { className: "vb-filmstrip scrollbar-none flex snap-x snap-mandatory gap-2 overflow-x-auto pb-1", tabIndex: 0, "aria-label": textOf(title).replace(/\*/g, "") || t("galleryImages", { count: images.length }), children: images.map((g, i) => (_jsx("li", { className: "shrink-0 snap-start", children: _jsx(Cell, { g: g, i: i, ctx: ctx, className: "h-[clamp(15rem,38cqi,30rem)]", style: { aspectRatio: g.image.width && g.image.height ? `${g.image.width} / ${g.image.height}` : "4 / 5" }, sizes: "(min-width: 56rem) 40vw, 80vw", fill: true }) }, i))) }), _jsx("p", { className: "vb-container font-mono text-eyebrow tabular uppercase tracking-[0.08em] text-fg-muted", children: t("galleryImages", { count: images.length }) })] }));
28
29
  }
29
30
  else if (variant === "mosaic") {
30
- body = (_jsx("ul", { className: "vb-mosaic grid grid-cols-2 gap-2 @2xl:grid-cols-12", children: images.map((g, i) => (_jsx("li", { "data-span": g.span ?? "1", className: "vb-mosaic-cell min-h-0", children: _jsx(Cell, { g: g, i: i, ctx: ctx, className: "size-full", sizes: "(min-width: 42rem) 66vw, 100vw", fill: true }) }, i))) }));
31
+ body = (_jsx(Stagger, { asChild: true, effect: "clip", disabled: editMode, children: _jsx("ul", { className: "vb-mosaic grid grid-cols-2 gap-2 @2xl:grid-cols-12", children: images.map((g, i) => (_jsx("li", { "data-span": g.span ?? "1", className: "vb-mosaic-cell min-h-0", children: _jsx(Cell, { g: g, i: i, ctx: ctx, className: "size-full", sizes: "(min-width: 42rem) 66vw, 100vw", fill: true }) }, i))) }) }));
31
32
  }
32
33
  else if (variant === "split-caption") {
33
34
  body = null;
34
35
  }
35
36
  else {
36
37
  const a = aspect === "mixed" ? undefined : aspect;
37
- body = (_jsx("ul", { className: "grid grid-cols-2 gap-x-3 gap-y-6 @4xl:grid-cols-3 @4xl:gap-x-4", children: images.map((g, i) => (_jsx("li", { children: _jsxs("figure", { className: "flex flex-col gap-2.5", children: [_jsx(Cell, { g: g, i: i, ctx: ctx, aspect: a ?? "3:2", sizes: "(min-width: 56rem) 33vw, 50vw" }), g.caption ? _jsx("figcaption", { className: "text-small text-fg-muted", children: g.caption }) : null] }) }, i))) }));
38
+ body = (_jsx(Stagger, { asChild: true, disabled: editMode, children: _jsx("ul", { className: "grid grid-cols-2 gap-x-3 gap-y-6 @4xl:grid-cols-3 @4xl:gap-x-4", children: images.map((g, i) => (_jsx("li", { children: _jsxs("figure", { className: "flex flex-col gap-2.5", children: [_jsx(Cell, { g: g, i: i, ctx: ctx, aspect: a ?? "3:2", sizes: "(min-width: 56rem) 33vw, 50vw" }), g.caption ? _jsx("figcaption", { className: "text-small text-fg-muted", children: g.caption }) : null] }) }, i))) }) }));
38
39
  }
39
40
  const content = variant === "split-caption" ? (_jsxs(Container, { className: "grid gap-x-(--gutter) gap-y-10 @4xl:grid-cols-12", children: [_jsx("div", { className: "@4xl:col-span-4", children: _jsx("div", { className: "flex flex-col gap-6 @4xl:sticky @4xl:top-[calc(var(--topbar-h,0px)+2rem)]", children: header }) }), _jsx("ol", { className: "flex flex-col gap-12 @4xl:col-span-7 @4xl:col-start-6", children: images.map((g, i) => (_jsx("li", { children: _jsxs("figure", { className: "flex flex-col gap-4", children: [_jsx(Cell, { g: g, i: i, ctx: ctx, sizes: "(min-width: 56rem) 58vw, 100vw" }), g.caption ? (_jsxs("figcaption", { className: "flex gap-4 text-small text-fg-muted text-pretty", children: [_jsx("span", { "aria-hidden": true, className: "font-mono text-eyebrow tabular text-fg-subtle", children: t("imageCounter", { index: i + 1, total: images.length }) }), _jsx("span", { children: g.caption })] })) : null] }) }, i))) })] })) : (_jsxs(_Fragment, { children: [header ? _jsx(Container, { children: header }) : null, variant === "filmstrip" ? _jsx("div", { className: "vb-filmstrip-wrap", children: body }) : _jsx(Container, { children: body })] }));
40
41
  return (_jsx(Section, { theme: theme, anchor: anchor, headingId: title ? headingId : undefined, blockId: id, blockType: "gallery", editMode: editMode, className: cn("vb-gallery", className), "data-variant": variant, children: _jsx("div", { className: "flex flex-col gap-10 @4xl:gap-14", children: lightbox ? (_jsx(GalleryLightbox, { items: images.map((g) => ({ src: g.image.src, alt: g.image.alt, width: g.image.width, height: g.image.height, caption: textOf(g.caption) || undefined, focalPoint: g.image.focal })), children: content })) : (content) }) }));
@@ -21,6 +21,12 @@ export function Hero(props) {
21
21
  const sectionTheme = variant === "full-bleed" ? { ...theme, mode: "dark" } : theme;
22
22
  return (_jsx(Section, { theme: sectionTheme, anchor: anchor, headingId: headingId, blockId: base, blockType: "hero", editMode: editMode, components: components, labels: labels, priority: true, "data-variant": variant, className: cn("vb-hero", className), children: variant === "split-media" ? (_jsx(SplitMedia, { ...props, headingId: headingId, headingLevel: headingLevel })) : variant === "full-bleed" ? (_jsx(FullBleed, { ...props, headingId: headingId, headingLevel: headingLevel })) : variant === "product" ? (_jsx(Product, { ...props, headingId: headingId, headingLevel: headingLevel })) : variant === "statement" ? (_jsx(Statement, { ...props, headingId: headingId, headingLevel: headingLevel })) : (_jsx(Editorial, { ...props, headingId: headingId, headingLevel: headingLevel })) }));
23
23
  }
24
+ /**
25
+ * Entrance cascade (motion.css `.vm-enter`): CSS keyframes from the first paint, no JS, so the
26
+ * hero never waits for hydration. Follows the SiteShell motion level; off in edit mode.
27
+ */
28
+ const ENTER = ["[--vm-i:0]", "[--vm-i:1]", "[--vm-i:2]", "[--vm-i:3]", "[--vm-i:4]", "[--vm-i:5]"];
29
+ const enter = (editMode, i, kind) => editMode ? undefined : cn("vm-enter", ENTER[i], kind === "title" && "vm-enter-title", kind === "media" && "vm-enter-media");
24
30
  function Eyebrow({ children, editMode, className }) {
25
31
  if (!children)
26
32
  return null;
@@ -37,31 +43,31 @@ function Meta({ items, className }) {
37
43
  return (_jsx("dl", { className: cn("flex flex-col gap-1.5 font-mono text-small text-fg-muted", className), children: items.slice(0, 4).map((m, i) => (_jsxs("div", { className: "flex gap-2", children: [_jsx("dt", { className: "text-fg-subtle after:content-['\u00B7'] after:ps-2", children: m.label }), _jsx("dd", { className: "text-fg", children: m.value })] }, i))) }));
38
44
  }
39
45
  function Title({ p, size, className }) {
40
- return (_jsx(BlockHeading, { id: p.headingId, level: p.headingLevel, size: size, text: p.title, editMode: p.editMode, field: "props.title", labels: p.labels, locale: p.locale, className: cn("vb-hero-title", className) }));
46
+ return (_jsx(BlockHeading, { id: p.headingId, level: p.headingLevel, size: size, text: p.title, editMode: p.editMode, field: "props.title", labels: p.labels, locale: p.locale, className: cn("vb-hero-title", enter(p.editMode, 1, "title"), className) }));
41
47
  }
42
48
  /* editorial ----------------------------------------------------------- */
43
49
  function Editorial(p) {
44
50
  const { eyebrow, lead, actions, meta, media, components, labels, locale, editMode } = p;
45
- return (_jsxs(_Fragment, { children: [_jsxs(Container, { className: "flex flex-col gap-8", children: [_jsx(Eyebrow, { editMode: editMode, children: eyebrow }), _jsx(Title, { p: p, size: "display-xl", className: "max-w-[18ch]" }), _jsxs("div", { className: "grid gap-x-(--gutter) gap-y-8 pt-2 @4xl:grid-cols-12 @4xl:items-end", children: [_jsxs("div", { className: "flex flex-col gap-6 @4xl:col-span-6 @6xl:col-span-5", children: [_jsx(Lead, { editMode: editMode, children: lead }), _jsx(CtaList, { actions: actions, components: components, labels: labels, locale: locale, max: 1, editMode: editMode })] }), _jsx(Meta, { items: meta, className: "@4xl:col-span-4 @4xl:col-start-9 @4xl:items-end @4xl:text-end" })] })] }), media ? (_jsx("div", { className: "vb-hero-strip mt-[calc(var(--section-y)*0.6)]", children: _jsx(BlockImage, { ...media, aspect: "21:9", priority: true, sizes: "100vw", components: components, labels: labels, editMode: editMode, field: "props.media" }) })) : null] }));
51
+ return (_jsxs(_Fragment, { children: [_jsxs(Container, { className: "flex flex-col gap-8", children: [_jsx(Eyebrow, { editMode: editMode, className: enter(editMode, 0), children: eyebrow }), _jsx(Title, { p: p, size: "display-xl", className: "max-w-[18ch]" }), _jsxs("div", { className: "grid gap-x-(--gutter) gap-y-8 pt-2 @4xl:grid-cols-12 @4xl:items-end", children: [_jsxs("div", { className: "flex flex-col gap-6 @4xl:col-span-6 @6xl:col-span-5", children: [_jsx(Lead, { editMode: editMode, className: enter(editMode, 2), children: lead }), _jsx(CtaList, { actions: actions, components: components, labels: labels, locale: locale, max: 1, editMode: editMode, className: enter(editMode, 3) })] }), _jsx(Meta, { items: meta, className: cn("@4xl:col-span-4 @4xl:col-start-9 @4xl:items-end @4xl:text-end", enter(editMode, 4)) })] })] }), media ? (_jsx("div", { className: cn("vb-hero-strip mt-[calc(var(--section-y)*0.6)]", enter(editMode, 4, "media")), children: _jsx(BlockImage, { ...media, aspect: "21:9", priority: true, sizes: "100vw", components: components, labels: labels, editMode: editMode, field: "props.media" }) })) : null] }));
46
52
  }
47
53
  /* split-media ---------------------------------------------------------- */
48
54
  function SplitMedia(p) {
49
55
  const { eyebrow, lead, actions, meta, media, caption, components, labels, locale, editMode } = p;
50
- return (_jsxs(Container, { className: "grid gap-x-(--gutter) gap-y-10 @4xl:grid-cols-12 @4xl:items-start", children: [_jsxs("div", { className: "order-2 flex flex-col gap-7 @4xl:order-1 @4xl:col-span-7 @4xl:pt-[calc(var(--section-y)*0.35)] @6xl:pe-[calc(var(--gutter)*2)]", children: [_jsx(Eyebrow, { editMode: editMode, children: eyebrow }), _jsx(Title, { p: p, size: "display-xl" }), _jsx(Lead, { editMode: editMode, className: "max-w-[46ch]", children: lead }), _jsx(CtaList, { actions: actions, components: components, labels: labels, locale: locale, editMode: editMode, className: "pt-2" }), _jsx(Meta, { items: meta, className: "mt-auto border-t border-line pt-5 @4xl:mt-10" })] }), media ? (_jsxs("figure", { className: "vb-hero-portrait vb-bleed-end relative order-1 my-0 @4xl:order-2 @4xl:col-span-5", ...field(editMode, "media"), children: [_jsx(BlockImage, { ...media, aspect: "4:5", priority: true, sizes: "(min-width: 56rem) 42vw, 100vw", className: "aspect-square! @4xl:aspect-[4/5]!", components: components, labels: labels }), caption ? (_jsx("figcaption", { className: "absolute bottom-0 start-0 bg-scene px-3 py-2 font-mono text-eyebrow uppercase tracking-[0.08em] text-fg-muted", ...field(editMode, "caption"), children: caption })) : null] })) : null] }));
56
+ return (_jsxs(Container, { className: "grid gap-x-(--gutter) gap-y-10 @4xl:grid-cols-12 @4xl:items-start", children: [_jsxs("div", { className: "order-2 flex flex-col gap-7 @4xl:order-1 @4xl:col-span-7 @4xl:pt-[calc(var(--section-y)*0.35)] @6xl:pe-[calc(var(--gutter)*2)]", children: [_jsx(Eyebrow, { editMode: editMode, className: enter(editMode, 0), children: eyebrow }), _jsx(Title, { p: p, size: "display-xl" }), _jsx(Lead, { editMode: editMode, className: cn("max-w-[46ch]", enter(editMode, 2)), children: lead }), _jsx(CtaList, { actions: actions, components: components, labels: labels, locale: locale, editMode: editMode, className: cn("pt-2", enter(editMode, 3)) }), _jsx(Meta, { items: meta, className: cn("mt-auto border-t border-line pt-5 @4xl:mt-10", enter(editMode, 4)) })] }), media ? (_jsxs("figure", { className: cn("vb-hero-portrait vb-bleed-end relative order-1 my-0 @4xl:order-2 @4xl:col-span-5", enter(editMode, 1, "media")), ...field(editMode, "media"), children: [_jsx(BlockImage, { ...media, aspect: "4:5", priority: true, sizes: "(min-width: 56rem) 42vw, 100vw", className: "aspect-square! @4xl:aspect-[4/5]!", components: components, labels: labels }), caption ? (_jsx("figcaption", { className: "absolute bottom-0 start-0 bg-scene px-3 py-2 font-mono text-eyebrow uppercase tracking-[0.08em] text-fg-muted", ...field(editMode, "caption"), children: caption })) : null] })) : null] }));
51
57
  }
52
58
  /* full-bleed ----------------------------------------------------------- */
53
59
  function FullBleed(p) {
54
60
  const { eyebrow, lead, actions, media, video, aside, height = "auto", components, labels, locale, editMode } = p;
55
61
  const t = siteText(labels, locale);
56
- return (_jsxs("div", { className: cn("vb-hero-bleed", height === "screen" && "vb-hero-screen"), children: [_jsxs("div", { className: "vb-hero-media", "aria-hidden": video ? undefined : "true", children: [media ? (_jsx(BlockImage, { ...media, decorative: true, priority: true, sizes: "100vw", className: "absolute inset-0 h-full", components: components, labels: labels, editMode: editMode, field: "props.media" })) : null, video && media ? _jsx(HeroVideo, { src: video.src, poster: media.src, pauseLabel: t("pauseVideo"), playLabel: t("resumeVideo") }) : null] }), _jsxs(Container, { className: "relative z-[1] grid w-full content-end gap-x-(--gutter) gap-y-10 @4xl:grid-cols-12 @4xl:items-end", children: [_jsxs("div", { className: "flex flex-col gap-6 @4xl:col-span-8 @6xl:col-span-7", children: [_jsx(Eyebrow, { editMode: editMode, children: eyebrow }), _jsx(Title, { p: p, size: "display-xl" }), _jsx(Lead, { editMode: editMode, className: "max-w-[52ch] text-fg", children: lead }), _jsx(CtaList, { actions: actions, components: components, labels: labels, locale: locale, editMode: editMode, className: "pt-2" })] }), aside ? (_jsxs("aside", { className: "vb-hero-aside surface flex flex-col gap-3 rounded-ui p-5 @4xl:col-span-4 @4xl:col-start-9 @6xl:col-span-3 @6xl:col-start-10", ...field(editMode, "aside"), children: [_jsx("p", { className: "font-mono text-eyebrow uppercase tracking-[0.08em] text-fg-muted", children: aside.title }), aside.body ? _jsx("p", { className: "text-body tabular text-fg", children: aside.body }) : null, aside.cta ? _jsx(CtaList, { actions: [aside.cta], components: components, labels: labels, locale: locale, size: "md", max: 1, className: "pt-1 [&>a]:w-full" }) : null] })) : null] })] }));
62
+ return (_jsxs("div", { className: cn("vb-hero-bleed", height === "screen" && "vb-hero-screen"), children: [_jsxs("div", { className: cn("vb-hero-media", enter(editMode, 0, "media")), "aria-hidden": video ? undefined : "true", children: [media ? (_jsx(BlockImage, { ...media, decorative: true, priority: true, sizes: "100vw", className: "absolute inset-0 h-full", components: components, labels: labels, editMode: editMode, field: "props.media" })) : null, video && media ? _jsx(HeroVideo, { src: video.src, poster: media.src, pauseLabel: t("pauseVideo"), playLabel: t("resumeVideo") }) : null] }), _jsxs(Container, { className: "relative z-[1] grid w-full content-end gap-x-(--gutter) gap-y-10 @4xl:grid-cols-12 @4xl:items-end", children: [_jsxs("div", { className: "flex flex-col gap-6 @4xl:col-span-8 @6xl:col-span-7", children: [_jsx(Eyebrow, { editMode: editMode, className: enter(editMode, 0), children: eyebrow }), _jsx(Title, { p: p, size: "display-xl" }), _jsx(Lead, { editMode: editMode, className: cn("max-w-[52ch] text-fg", enter(editMode, 2)), children: lead }), _jsx(CtaList, { actions: actions, components: components, labels: labels, locale: locale, editMode: editMode, className: cn("pt-2", enter(editMode, 3)) })] }), aside ? (_jsxs("aside", { className: cn("vb-hero-aside surface flex flex-col gap-3 rounded-ui p-5 @4xl:col-span-4 @4xl:col-start-9 @6xl:col-span-3 @6xl:col-start-10", enter(editMode, 4)), ...field(editMode, "aside"), children: [_jsx("p", { className: "font-mono text-eyebrow uppercase tracking-[0.08em] text-fg-muted", children: aside.title }), aside.body ? _jsx("p", { className: "text-body tabular text-fg", children: aside.body }) : null, aside.cta ? _jsx(CtaList, { actions: [aside.cta], components: components, labels: labels, locale: locale, size: "md", max: 1, className: "pt-1 [&>a]:w-full" }) : null] })) : null] })] }));
57
63
  }
58
64
  /* product -------------------------------------------------------------- */
59
65
  function Product(p) {
60
66
  const { eyebrow, lead, actions, release, screenshot, visual, meta, components, labels, locale, editMode } = p;
61
- return (_jsxs(Container, { className: "flex flex-col", children: [_jsxs("div", { className: "flex flex-col gap-7", children: [release ? (_jsxs(BlockLink, { href: release.href, components: components, labels: labels, locale: locale, editMode: editMode, field: "props.release", className: "group/rel inline-flex w-fit items-center gap-3 rounded-full border border-line py-1 ps-1 pe-3.5 text-small text-fg-muted transition-colors hover:border-field-border-hover hover:text-fg focus-ring", children: [release.tag ? _jsx("span", { className: "rounded-full bg-accent-soft px-2.5 py-0.5 font-mono text-eyebrow tracking-[0.04em] text-accent-ink", children: release.tag }) : null, _jsx("span", { children: release.label }), _jsx(ArrowEnd, { className: "group-hover/rel:translate-x-0.5 rtl:group-hover/rel:-translate-x-0.5" })] })) : (_jsx(Eyebrow, { editMode: editMode, children: eyebrow })), _jsxs("div", { className: "grid gap-x-(--gutter) gap-y-7 @4xl:grid-cols-12 @4xl:items-end", children: [_jsx(Title, { p: p, size: "display-xl", className: "@4xl:col-span-8" }), _jsxs("div", { className: "flex flex-col gap-6 @4xl:col-span-4", children: [_jsx(Lead, { editMode: editMode, children: lead }), _jsx(CtaList, { actions: actions, components: components, labels: labels, locale: locale, size: "md", editMode: editMode })] })] }), _jsx(Meta, { items: meta, className: "flex-row flex-wrap gap-x-6" })] }), screenshot || visual ? (_jsx("div", { className: "vb-hero-shot mt-[calc(var(--section-y)*0.55)]", ...field(editMode, "screenshot"), children: visual ?? _jsx(BlockImage, { ...screenshot, priority: true, sizes: "min(100vw, 90rem)", components: components, labels: labels }) })) : null] }));
67
+ return (_jsxs(Container, { className: "flex flex-col", children: [_jsxs("div", { className: "flex flex-col gap-7", children: [release ? (_jsxs(BlockLink, { href: release.href, components: components, labels: labels, locale: locale, editMode: editMode, field: "props.release", className: cn("group/rel inline-flex w-fit items-center gap-3 rounded-full border border-line py-1 ps-1 pe-3.5 text-small text-fg-muted transition-colors hover:border-field-border-hover hover:text-fg focus-ring", enter(editMode, 0)), children: [release.tag ? _jsx("span", { className: "rounded-full bg-accent-soft px-2.5 py-0.5 font-mono text-eyebrow tracking-[0.04em] text-accent-ink", children: release.tag }) : null, _jsx("span", { children: release.label }), _jsx(ArrowEnd, { className: "group-hover/rel:translate-x-0.5 rtl:group-hover/rel:-translate-x-0.5" })] })) : (_jsx(Eyebrow, { editMode: editMode, className: enter(editMode, 0), children: eyebrow })), _jsxs("div", { className: "grid gap-x-(--gutter) gap-y-7 @4xl:grid-cols-12 @4xl:items-end", children: [_jsx(Title, { p: p, size: "display-xl", className: "@4xl:col-span-8" }), _jsxs("div", { className: "flex flex-col gap-6 @4xl:col-span-4", children: [_jsx(Lead, { editMode: editMode, className: enter(editMode, 2), children: lead }), _jsx(CtaList, { actions: actions, components: components, labels: labels, locale: locale, size: "md", editMode: editMode, className: enter(editMode, 3) })] })] }), _jsx(Meta, { items: meta, className: cn("flex-row flex-wrap gap-x-6", enter(editMode, 4)) })] }), screenshot || visual ? (_jsx("div", { className: cn("vb-hero-shot mt-[calc(var(--section-y)*0.55)]", enter(editMode, 5, "media")), ...field(editMode, "screenshot"), children: visual ?? _jsx(BlockImage, { ...screenshot, priority: true, sizes: "min(100vw, 90rem)", components: components, labels: labels }) })) : null] }));
62
68
  }
63
69
  /* statement ------------------------------------------------------------ */
64
70
  function Statement(p) {
65
71
  const { eyebrow, services, actions, components, labels, locale, editMode } = p;
66
- return (_jsxs(Container, { className: "flex flex-col items-center gap-10 text-center", children: [_jsx(Eyebrow, { editMode: editMode, children: eyebrow }), _jsx(Title, { p: p, size: "display-2xl", className: "max-w-[16ch]" }), services?.length ? (_jsx("ul", { className: "flex max-w-(--prose) flex-wrap justify-center gap-x-3 gap-y-2 text-lead text-fg-muted", ...field(editMode, "services"), children: services.map((s, i) => (_jsxs("li", { className: "flex items-baseline gap-3", children: [i > 0 ? (_jsx("span", { "aria-hidden": true, className: "text-fg-subtle", children: "/" })) : null, _jsx(BlockLink, { href: s.href, components: components, labels: labels, locale: locale, className: "rounded-[2px] text-fg underline decoration-line underline-offset-[0.25em] transition-[text-decoration-color] hover:decoration-current focus-ring", children: s.label })] }, i))) })) : null, _jsx(CtaList, { actions: actions, components: components, labels: labels, locale: locale, editMode: editMode, className: "justify-center" })] }));
72
+ return (_jsxs(Container, { className: "flex flex-col items-center gap-10 text-center", children: [_jsx(Eyebrow, { editMode: editMode, className: enter(editMode, 0), children: eyebrow }), _jsx(Title, { p: p, size: "display-2xl", className: "max-w-[16ch]" }), services?.length ? (_jsx("ul", { className: cn("flex max-w-(--prose) flex-wrap justify-center gap-x-3 gap-y-2 text-lead text-fg-muted", enter(editMode, 2)), ...field(editMode, "services"), children: services.map((s, i) => (_jsxs("li", { className: "flex items-baseline gap-3", children: [i > 0 ? (_jsx("span", { "aria-hidden": true, className: "text-fg-subtle", children: "/" })) : null, _jsx(BlockLink, { href: s.href, components: components, labels: labels, locale: locale, className: "rounded-[2px] text-fg underline decoration-line underline-offset-[0.25em] transition-[text-decoration-color] hover:decoration-current focus-ring", children: s.label })] }, i))) })) : null, _jsx(CtaList, { actions: actions, components: components, labels: labels, locale: locale, editMode: editMode, className: cn("justify-center", enter(editMode, 3)) })] }));
67
73
  }
@@ -16,7 +16,8 @@ export interface LogoItem {
16
16
  export interface LogoStripProps extends Omit<BlockBaseProps, "variant" | "labels"> {
17
17
  /**
18
18
  * row: caption at the start, logos on one line at an equal optical height (28px).
19
- * marquee: an endless band with faded edges; pauses on hover/focus; static under reduced motion.
19
+ * marquee: an endless band with faded edges (Ticker); pauses on hover/focus and with its pause
20
+ * control; static under reduced motion and `motion="off"`.
20
21
  * grid: 4–6 columns separated by 1px gridlines, logos centred in cells.
21
22
  */
22
23
  variant?: LogoStripVariant;
@@ -32,8 +33,9 @@ export interface LogoStripProps extends Omit<BlockBaseProps, "variant" | "labels
32
33
  labels?: SiteBlockLabels;
33
34
  }
34
35
  /**
35
- * "Brands we work with" proof. Server component; the marquee is CSS keyframes (paused on
36
- * hover, focus and reduced motion; the duplicate copy is `aria-hidden`).
36
+ * "Brands we work with" proof. Server component; the marquee is the motion kit's Ticker: CSS
37
+ * keyframes, paused on hover, focus and with its pause control; static under reduced motion and
38
+ * SiteShell `motion="off"`; the duplicate copy is `aria-hidden` and `inert`).
37
39
  *
38
40
  * ```tsx
39
41
  * <LogoStrip caption="Trusted by 120+ businesses since 2014" logos={[{ name: "Kıyı", image: { src, width, height } }]} />
@@ -3,10 +3,12 @@ import { cn } from "../../lib/cn.js";
3
3
  import { Container, Section } from "../section.js";
4
4
  import { BlockImage } from "../block-image.js";
5
5
  import { BlockLink } from "../block-link.js";
6
+ import { Ticker } from "../../motion/ticker.js";
6
7
  import { siteText, useBlockIds } from "./_kit-1.js";
7
8
  /**
8
- * "Brands we work with" proof. Server component; the marquee is CSS keyframes (paused on
9
- * hover, focus and reduced motion; the duplicate copy is `aria-hidden`).
9
+ * "Brands we work with" proof. Server component; the marquee is the motion kit's Ticker: CSS
10
+ * keyframes, paused on hover, focus and with its pause control; static under reduced motion and
11
+ * SiteShell `motion="off"`; the duplicate copy is `aria-hidden` and `inert`).
10
12
  *
11
13
  * ```tsx
12
14
  * <LogoStrip caption="Trusted by 120+ businesses since 2014" logos={[{ name: "Kıyı", image: { src, width, height } }]} />
@@ -16,11 +18,10 @@ export function LogoStrip({ variant = "row", caption, logos, monochrome = true,
16
18
  const { base, headingId } = useBlockIds(id, "logos");
17
19
  const t = siteText(labels, locale);
18
20
  const marquee = variant === "marquee" && logos.length >= 4;
19
- const logo = (l, i, hidden) => {
20
- const img = (_jsxs(_Fragment, { children: [_jsx(BlockImage, { ...l.image, alt: hidden ? undefined : l.name, decorative: hidden, fit: "contain", className: cn("vb-logo", l.imageDark && "vb-logo-light"), components: components }), l.imageDark ? _jsx(BlockImage, { ...l.imageDark, decorative: true, fit: "contain", className: "vb-logo vb-logo-dark", components: components }) : null] }));
21
- const inner = l.link && !hidden ? (_jsx(BlockLink, { href: l.link.href, newTab: l.link.newTab, components: components, labels: labels, locale: locale, className: "flex items-center rounded-[2px] focus-ring", children: img })) : (img);
22
- return (_jsx("li", { "aria-hidden": hidden || undefined, "data-dup": hidden ? "" : undefined, className: "vb-logo-item flex shrink-0 items-center justify-center", "data-field": editMode && !hidden ? `props.logos.${i}` : undefined, children: inner }, `${hidden ? "d" : "l"}${i}`));
21
+ const logo = (l, i) => {
22
+ const img = (_jsxs(_Fragment, { children: [_jsx(BlockImage, { ...l.image, alt: l.name, fit: "contain", className: cn("vb-logo", l.imageDark && "vb-logo-light"), components: components }), l.imageDark ? _jsx(BlockImage, { ...l.imageDark, decorative: true, fit: "contain", className: "vb-logo vb-logo-dark", components: components }) : null] }));
23
+ return (_jsx("li", { className: "vb-logo-item flex shrink-0 items-center justify-center", "data-field": editMode ? `props.logos.${i}` : undefined, children: l.link ? (_jsx(BlockLink, { href: l.link.href, newTab: l.link.newTab, components: components, labels: labels, locale: locale, className: "flex items-center rounded-[2px] focus-ring", children: img })) : (img) }, i));
23
24
  };
24
25
  const captionEl = caption ? (_jsx("p", { id: headingId, className: cn("text-small text-fg-muted text-pretty", variant === "row" && "max-w-[26ch] shrink-0"), "data-field": editMode ? "props.caption" : undefined, children: caption })) : null;
25
- return (_jsx(Section, { theme: theme, anchor: anchor, headingId: caption ? headingId : undefined, "aria-label": caption ? undefined : t("logos"), blockId: base, blockType: "logo-strip", editMode: editMode, className: cn("vb-logos", className), "data-variant": marquee ? "marquee" : variant === "marquee" ? "row" : variant, "data-mono": monochrome ? "" : undefined, children: marquee ? (_jsxs("div", { className: "flex flex-col gap-8", children: [captionEl ? _jsx(Container, { children: captionEl }) : null, _jsx("div", { className: "vb-marquee", style: { "--vb-marquee-duration": `${speed}s` }, children: _jsxs("ul", { className: "vb-marquee-track", children: [logos.map((l, i) => logo(l, i)), logos.map((l, i) => logo(l, i, true))] }) })] })) : variant === "grid" ? (_jsxs(Container, { className: "flex flex-col gap-8", children: [captionEl, _jsx("ul", { className: cn("vb-logo-grid grid grid-cols-2 @2xl:grid-cols-3", columns === 4 && "@4xl:grid-cols-4", columns === 5 && "@4xl:grid-cols-5", columns === 6 && "@4xl:grid-cols-4 @6xl:grid-cols-6"), children: logos.map((l, i) => logo(l, i)) })] })) : (_jsxs(Container, { className: "flex flex-col gap-8 @4xl:flex-row @4xl:items-center @4xl:gap-(--gutter)", children: [captionEl, _jsx("ul", { className: "flex flex-1 flex-wrap items-center gap-x-[clamp(2rem,5cqi,4rem)] gap-y-6", children: logos.map((l, i) => logo(l, i)) })] })) }));
26
+ return (_jsx(Section, { theme: theme, anchor: anchor, headingId: caption ? headingId : undefined, "aria-label": caption ? undefined : t("logos"), blockId: base, blockType: "logo-strip", editMode: editMode, className: cn("vb-logos", className), "data-variant": marquee ? "marquee" : variant === "marquee" ? "row" : variant, "data-mono": monochrome ? "" : undefined, children: marquee ? (_jsxs("div", { className: "flex flex-col gap-8", children: [captionEl ? _jsx(Container, { children: captionEl }) : null, _jsx(Ticker, { as: "ul", duration: speed, pauseLabel: t("pauseMotion"), className: "vb-marquee-band", "data-motion": editMode ? "off" : undefined, children: logos.map((l, i) => logo(l, i)) })] })) : variant === "grid" ? (_jsxs(Container, { className: "flex flex-col gap-8", children: [captionEl, _jsx("ul", { className: cn("vb-logo-grid grid grid-cols-2 @2xl:grid-cols-3", columns === 4 && "@4xl:grid-cols-4", columns === 5 && "@4xl:grid-cols-5", columns === 6 && "@4xl:grid-cols-4 @6xl:grid-cols-6"), children: logos.map((l, i) => logo(l, i)) })] })) : (_jsxs(Container, { className: "flex flex-col gap-8 @4xl:flex-row @4xl:items-center @4xl:gap-(--gutter)", children: [captionEl, _jsx("ul", { className: "flex flex-1 flex-wrap items-center gap-x-[clamp(2rem,5cqi,4rem)] gap-y-6", children: logos.map((l, i) => logo(l, i)) })] })) }));
26
27
  }
@@ -7,6 +7,7 @@ import { BlockImage } from "../block-image.js";
7
7
  import { RichText } from "../rich-text.js";
8
8
  import { EmbedFacade } from "./embed-facade.js";
9
9
  import { CtaList, siteText, useBlockIds } from "./_kit-1.js";
10
+ import { Reveal } from "../../motion/reveal.js";
10
11
  /** Embed URL for a video facade (YouTube → youtube-nocookie, Vimeo → player). */
11
12
  export function videoEmbedUrl(v) {
12
13
  if (v.provider === "youtube") {
@@ -51,5 +52,5 @@ export function MediaText({ variant = "side", eyebrow, title, body, bullets, cta
51
52
  else {
52
53
  content = (_jsxs(Container, { className: "grid gap-x-(--gutter) gap-y-10 @4xl:grid-cols-12 @4xl:items-start", children: [_jsx("div", { className: cn("@4xl:col-span-5", end && "@4xl:order-2 @4xl:col-start-8"), children: image("(min-width: 56rem) 40vw, 100vw", "rounded-ui") }), _jsx("div", { className: cn("@4xl:col-span-6", end ? "@4xl:order-1 @4xl:col-start-1" : "@4xl:col-start-7"), children: text })] }));
53
54
  }
54
- return (_jsx(Section, { theme: theme, anchor: anchor, headingId: headingId, blockId: base, blockType: "media-text", editMode: editMode, className: cn("vb-media-text", variant === "overlap" && "vb-media-overlap", className), "data-variant": variant, "data-side": mediaSide, children: _jsx("div", { className: "vb-reveal", children: content }) }));
55
+ return (_jsx(Section, { theme: theme, anchor: anchor, headingId: headingId, blockId: base, blockType: "media-text", editMode: editMode, className: cn("vb-media-text", variant === "overlap" && "vb-media-overlap", className), "data-variant": variant, "data-side": mediaSide, children: _jsx(Reveal, { disabled: editMode, children: content }) }));
55
56
  }
@@ -8,6 +8,7 @@ import { BlockLink } from "../block-link.js";
8
8
  import { RichText } from "../rich-text.js";
9
9
  import { site2Text } from "./shared/labels-2.js";
10
10
  import { fieldAttr, formatMoney, formatMoneyLong, useBlockIds2 } from "./shared/kit-2.js";
11
+ import { Reveal, Stagger } from "../../motion/reveal.js";
11
12
  /**
12
13
  * Plan pricing for SaaS subscriptions, agency packages and retainers. Money through `Intl` per
13
14
  * locale and currency, a period suffix, a mandatory tax note. Server component; the
@@ -25,7 +26,7 @@ export function PricingTable({ variant = "columns", eyebrow, title, lead, billin
25
26
  const ctx = { t, locale, billing, components, labels, editMode };
26
27
  // One primary per block: the highlighted plan, else the first plan asking for it.
27
28
  const primaryIndex = Math.max(0, plans.findIndex((p) => p.highlight) >= 0 ? plans.findIndex((p) => p.highlight) : plans.findIndex((p) => p.cta?.style === "primary"));
28
- return (_jsx(Section, { theme: theme, anchor: anchor, headingId: title ? headingId : undefined, blockId: id, blockType: "pricing", editMode: editMode, className: cn("vb-pricing", className), "data-billing": billing, "data-variant": single ? "single" : variant, children: _jsxs(Container, { className: "flex flex-col gap-12 @4xl:gap-16", children: [title || eyebrow || lead || billing === "both" ? (_jsxs("div", { className: "flex flex-col gap-8 @4xl:flex-row @4xl:items-end @4xl:justify-between", children: [_jsx(SectionHeader, { eyebrow: eyebrow, title: title, lead: lead, headingId: headingId, headingLevel: headingLevel, editMode: editMode, labels: labels, locale: locale }), billing === "both" ? _jsx(BillingToggle, { name: `${base}-period`, defaultPeriod: defaultPeriod, saving: yearlySaving, t: t }) : null] })) : null, single ? (_jsx(SinglePlan, { plan: plans[0], ctx: ctx })) : variant === "rows" ? (_jsx("ol", { className: "flex flex-col border-b border-line", children: plans.map((plan, i) => (_jsx(PlanRow, { plan: plan, index: i, ctx: ctx, primary: i === primaryIndex }, i))) })) : (_jsx("ul", { className: "vb-plan-grid grid", "data-cols": Math.min(plans.length, 4), children: plans.map((plan, i) => (_jsx(PlanColumn, { plan: plan, index: i, ctx: ctx, primary: i === primaryIndex }, i))) })), taxNote || footnote ? (_jsxs("div", { className: "flex flex-col gap-2 border-t border-line pt-5 text-small text-fg-muted @2xl:flex-row @2xl:items-start @2xl:justify-between @2xl:gap-10", children: [taxNote ? (_jsx("p", { className: "shrink-0 font-mono text-eyebrow uppercase tracking-[0.08em]", ...fieldAttr(editMode, "taxNote"), children: taxNote })) : null, footnote ? (_jsx("div", { className: "max-w-(--measure) @2xl:text-end", ...fieldAttr(editMode, "footnote"), children: isRichDoc(footnote) ? _jsx(RichText, { doc: footnote, size: "sm", components: components, labels: labels, locale: locale }) : footnote })) : null] })) : null] }) }));
29
+ return (_jsx(Section, { theme: theme, anchor: anchor, headingId: title ? headingId : undefined, blockId: id, blockType: "pricing", editMode: editMode, className: cn("vb-pricing", className), "data-billing": billing, "data-variant": single ? "single" : variant, children: _jsxs(Container, { className: "flex flex-col gap-12 @4xl:gap-16", children: [title || eyebrow || lead || billing === "both" ? (_jsxs("div", { className: "flex flex-col gap-8 @4xl:flex-row @4xl:items-end @4xl:justify-between", children: [_jsx(SectionHeader, { eyebrow: eyebrow, title: title, lead: lead, headingId: headingId, headingLevel: headingLevel, editMode: editMode, labels: labels, locale: locale }), billing === "both" ? _jsx(BillingToggle, { name: `${base}-period`, defaultPeriod: defaultPeriod, saving: yearlySaving, t: t }) : null] })) : null, single ? (_jsx(Reveal, { disabled: editMode, children: _jsx(SinglePlan, { plan: plans[0], ctx: ctx }) })) : variant === "rows" ? (_jsx(Stagger, { asChild: true, effect: "fade", disabled: editMode, children: _jsx("ol", { className: "flex flex-col border-b border-line", children: plans.map((plan, i) => (_jsx(PlanRow, { plan: plan, index: i, ctx: ctx, primary: i === primaryIndex }, i))) }) })) : (_jsx(Stagger, { asChild: true, disabled: editMode, children: _jsx("ul", { className: "vb-plan-grid grid", "data-cols": Math.min(plans.length, 4), children: plans.map((plan, i) => (_jsx(PlanColumn, { plan: plan, index: i, ctx: ctx, primary: i === primaryIndex }, i))) }) })), taxNote || footnote ? (_jsxs("div", { className: "flex flex-col gap-2 border-t border-line pt-5 text-small text-fg-muted @2xl:flex-row @2xl:items-start @2xl:justify-between @2xl:gap-10", children: [taxNote ? (_jsx("p", { className: "shrink-0 font-mono text-eyebrow uppercase tracking-[0.08em]", ...fieldAttr(editMode, "taxNote"), children: taxNote })) : null, footnote ? (_jsx("div", { className: "max-w-(--measure) @2xl:text-end", ...fieldAttr(editMode, "footnote"), children: isRichDoc(footnote) ? _jsx(RichText, { doc: footnote, size: "sm", components: components, labels: labels, locale: locale }) : footnote })) : null] })) : null] }) }));
29
30
  }
30
31
  const isRichDoc = (v) => !!v && typeof v === "object" && v.type === "doc" && Array.isArray(v.content);
31
32
  function BillingToggle({ name, defaultPeriod, saving, t }) {
@@ -4,6 +4,7 @@ import { Container, Section } from "../section.js";
4
4
  import { SectionHeader } from "../section-header.js";
5
5
  import { BlockImage } from "../block-image.js";
6
6
  import { CtaList, indexLabel, useBlockIds } from "./_kit-1.js";
7
+ import { Reveal } from "../../motion/reveal.js";
7
8
  /**
8
9
  * "How we work" — discovery → design → launch; a first spa visit; SaaS setup. An ordered list
9
10
  * (the order is announced; the numbers are visual). Server component.
@@ -37,5 +38,5 @@ export function Steps({ variant = "vertical", eyebrow, title, lead, steps, actio
37
38
  else {
38
39
  content = (_jsxs(Container, { className: "grid gap-x-(--gutter) gap-y-12 @4xl:grid-cols-12", children: [_jsxs("div", { className: "flex flex-col gap-8 @4xl:col-span-5", children: [header, actionsEl] }), _jsx("ol", { className: "vb-steps-v flex flex-col @4xl:col-span-6 @4xl:col-start-7", children: steps.map((s, i) => (_jsxs("li", { className: "vb-step relative flex flex-col gap-3 ps-12 pb-10 last:pb-0", children: [_jsx("span", { "aria-hidden": true, className: cn("vb-step-badge absolute start-0 top-0 inline-flex size-8 items-center justify-center rounded-full border border-line bg-scene font-mono text-[0.6875rem] tabular text-fg-muted", i === 0 && "vb-step-badge-first"), children: indexLabel(i) }), _jsxs("div", { className: "flex flex-wrap items-baseline justify-between gap-x-4 gap-y-2 pt-1", children: [stepTitle(s, i), duration(s, i)] }), stepText(s, i), media(s)] }, i))) })] }));
39
40
  }
40
- return (_jsx(Section, { theme: theme, anchor: anchor, headingId: title ? headingId : undefined, blockId: base, blockType: "steps", editMode: editMode, className: cn("vb-steps", className), "data-variant": variant, children: _jsx("div", { className: "vb-reveal", children: content }) }));
41
+ return (_jsx(Section, { theme: theme, anchor: anchor, headingId: title ? headingId : undefined, blockId: base, blockType: "steps", editMode: editMode, className: cn("vb-steps", className), "data-variant": variant, children: _jsx(Reveal, { disabled: editMode, children: content }) }));
41
42
  }
@@ -36,6 +36,13 @@ export interface SiteShellProps extends Omit<React.HTMLAttributes<HTMLDivElement
36
36
  pageParam?: string;
37
37
  /** Query key for the locale. Default `"lang"`. */
38
38
  localeParam?: string;
39
+ /**
40
+ * Motion level for the whole site → `data-motion`. subtle (default): short fades and small
41
+ * rises, count-ups, the logo ticker. full: longer, wider, a wiping hero title, magnetic
42
+ * calls-to-action. off: final state everywhere. Reduced motion always wins. Page and
43
+ * language switches cross-fade (View Transitions where available, a fade otherwise).
44
+ */
45
+ motion?: "full" | "subtle" | "off";
39
46
  }
40
47
  export interface SiteShellContextValue {
41
48
  page: string;
@@ -46,6 +53,10 @@ export interface SiteShellContextValue {
46
53
  navigate: (to: SiteLocation) => void;
47
54
  /** The href for a page (respects `routing` and `pageParam`). */
48
55
  hrefFor: (page: string, anchor?: string) => string;
56
+ /** The shell's motion level. */
57
+ motion: "full" | "subtle" | "off";
58
+ /** True after a switch that should fade the new page in (no View Transitions available). */
59
+ pageEnter: boolean;
49
60
  }
50
61
  /** Page, locale and navigation of the closest Site shell. Throws outside one. */
51
62
  export declare function useSiteShell(): SiteShellContextValue;
@@ -5,6 +5,8 @@ import { cn } from "../../lib/cn.js";
5
5
  import { ThemeScope } from "../../lib/theme-scope.js";
6
6
  import { useControllableState } from "../../lib/use-controllable-state.js";
7
7
  import { isBuiltInAccent, siteAccent, siteAccentCss, siteAccentKey } from "./shared/site-accent.js";
8
+ import { motionAllowed } from "../../motion/core.js";
9
+ import { startPageTransition, viewTransitionsSupported } from "../../motion/page-transition.js";
8
10
  import { parseSiteHref, readSiteLocation, sitePageHref, writeSiteLocation } from "./shared/site-routing.js";
9
11
  const SiteShellContext = React.createContext(null);
10
12
  /** Page, locale and navigation of the closest Site shell. Throws outside one. */
@@ -32,7 +34,7 @@ export function useSiteShell() {
32
34
  * </SiteShell>
33
35
  * ```
34
36
  */
35
- export const SiteShell = React.forwardRef(function SiteShell({ accent, accentDark, font, material, density, theme, dir, locales = ["en", "tr"], locale: localeProp, defaultLocale, onLocaleChange, pages = ["home"], page: pageProp, defaultPage, onPageChange, routing = "search", pageParam = "page", localeParam = "lang", className, onClick, children, ...props }, ref) {
37
+ export const SiteShell = React.forwardRef(function SiteShell({ accent, accentDark, font, material, density, theme, dir, locales = ["en", "tr"], locale: localeProp, defaultLocale, onLocaleChange, pages = ["home"], page: pageProp, defaultPage, onPageChange, routing = "search", pageParam = "page", localeParam = "lang", motion = "subtle", className, onClick, children, ...props }, ref) {
36
38
  const homeLocale = defaultLocale ?? locales[0] ?? "en";
37
39
  const homePage = defaultPage ?? pages[0] ?? "home";
38
40
  const [locale, setLocale] = useControllableState({ value: localeProp, defaultValue: homeLocale, onChange: onLocaleChange, name: "SiteShell" });
@@ -47,6 +49,19 @@ export const SiteShell = React.forwardRef(function SiteShell({ accent, accentDar
47
49
  }, [ref]);
48
50
  /** What to do after the next render: focus the h1, scroll to the top or to an anchor. */
49
51
  const pending = React.useRef(null);
52
+ const [pageEnter, setPageEnter] = React.useState(false);
53
+ /** Swap page/locale as a page transition: a View Transition, or a fade on the new page. */
54
+ const transition = React.useCallback((commit) => {
55
+ const root = rootRef.current;
56
+ const animate = motion !== "off" && motionAllowed(root);
57
+ setPageEnter(animate && !viewTransitionsSupported());
58
+ if (animate)
59
+ startPageTransition(commit, { scope: root });
60
+ else
61
+ commit();
62
+ }, [motion]);
63
+ const transitionRef = React.useRef(transition);
64
+ transitionRef.current = transition;
50
65
  const pageKey = pages.join("|");
51
66
  const localeKey = locales.join("|");
52
67
  const params = React.useMemo(() => ({ pageParam, localeParam }), [pageParam, localeParam]);
@@ -63,10 +78,14 @@ export const SiteShell = React.forwardRef(function SiteShell({ accent, accentDar
63
78
  }
64
79
  if (pageChanged)
65
80
  pending.current = { focus: !to.anchor, scroll: true, anchor: to.anchor };
66
- if (nextLocale !== locale)
67
- setLocale(nextLocale);
68
- if (pageChanged)
69
- setPage(nextPage);
81
+ if (pageChanged || nextLocale !== locale) {
82
+ transition(() => {
83
+ if (nextLocale !== locale)
84
+ setLocale(nextLocale);
85
+ if (pageChanged)
86
+ setPage(nextPage);
87
+ });
88
+ }
70
89
  else if (to.anchor)
71
90
  scrollToAnchor(rootRef.current, to.anchor, false);
72
91
  // The Navbar's mobile menu is a native popover: close it after any switch.
@@ -80,7 +99,7 @@ export const SiteShell = React.forwardRef(function SiteShell({ accent, accentDar
80
99
  });
81
100
  },
82
101
  // eslint-disable-next-line react-hooks/exhaustive-deps
83
- [page, locale, pageKey, localeKey, routing, homePage, homeLocale, params, setLocale, setPage]);
102
+ [page, locale, pageKey, localeKey, routing, homePage, homeLocale, params, setLocale, setPage, transition]);
84
103
  // URL → state on mount and on back/forward.
85
104
  React.useEffect(() => {
86
105
  if (routing === "memory")
@@ -90,12 +109,18 @@ export const SiteShell = React.forwardRef(function SiteShell({ accent, accentDar
90
109
  const nextPage = loc.page && pages.includes(loc.page) ? loc.page : homePage;
91
110
  const nextLocale = loc.locale && locales.includes(loc.locale) ? loc.locale : homeLocale;
92
111
  const anchor = routing === "search" && window.location.hash.length > 1 ? decodeURIComponent(window.location.hash.slice(1)) : undefined;
93
- setLocale(nextLocale);
94
- setPage((prev) => {
95
- if (prev !== nextPage)
96
- pending.current = { focus: !initial && !anchor, scroll: !initial, anchor: initial ? anchor : undefined };
97
- return nextPage;
98
- });
112
+ const commit = () => {
113
+ setLocale(nextLocale);
114
+ setPage((prev) => {
115
+ if (prev !== nextPage)
116
+ pending.current = { focus: !initial && !anchor, scroll: !initial, anchor: initial ? anchor : undefined };
117
+ return nextPage;
118
+ });
119
+ };
120
+ if (initial)
121
+ commit();
122
+ else
123
+ transitionRef.current(commit);
99
124
  };
100
125
  // A deep link (`?page=order`) is server-rendered as home and switched here. As a transition,
101
126
  // React finishes hydrating the home tree (and any Suspense boundary still dehydrated) before
@@ -167,9 +192,11 @@ export const SiteShell = React.forwardRef(function SiteShell({ accent, accentDar
167
192
  locales,
168
193
  navigate: (to) => apply(to, "push"),
169
194
  hrefFor: (p, anchor) => sitePageHref(p, anchor, { routing, pageParam }),
195
+ motion,
196
+ pageEnter,
170
197
  }),
171
198
  // eslint-disable-next-line react-hooks/exhaustive-deps
172
- [page, locale, pageKey, localeKey, apply, routing, pageParam]);
199
+ [page, locale, pageKey, localeKey, apply, routing, pageParam, motion, pageEnter]);
173
200
  // A colour → scoped variables. A name (built-in preset or your own, like "brand") goes
174
201
  // straight to data-accent.
175
202
  const custom = accent && !isBuiltInAccent(accent) && looksLikeColour(accent) ? accent : undefined;
@@ -186,7 +213,7 @@ export const SiteShell = React.forwardRef(function SiteShell({ accent, accentDar
186
213
  return null;
187
214
  }
188
215
  }, [custom, accentDark]);
189
- return (_jsx(SiteShellContext.Provider, { value: ctx, children: _jsxs(ThemeScope, { ref: setRoot, theme: theme, material: material, density: density, font: font, dir: dir, accent: accentStyle ? "site" : custom ? undefined : accent, "data-site-accent": accentStyle?.key, "data-site-shell": "", "data-page": page, lang: locale, className: cn("vb-scope font-sans text-fg", className), onClick: handleClick, ...props, children: [accentStyle ? _jsx("style", { children: accentStyle.css }) : null, children] }) }));
216
+ return (_jsx(SiteShellContext.Provider, { value: ctx, children: _jsxs(ThemeScope, { ref: setRoot, theme: theme, material: material, density: density, font: font, dir: dir, accent: accentStyle ? "site" : custom ? undefined : accent, "data-site-accent": accentStyle?.key, "data-site-shell": "", "data-motion": motion, "data-page": page, lang: locale, className: cn("vb-scope font-sans text-fg", className), onClick: handleClick, ...props, children: [accentStyle ? _jsx("style", { children: accentStyle.css }) : null, children] }) }));
190
217
  });
191
218
  const looksLikeColour = (v) => v.trim().startsWith("#") || v.includes("(");
192
219
  const cssString = (s) => s.replace(/["\\]/g, "\\$&");
@@ -198,7 +225,7 @@ function scrollToAnchor(root, anchor, instant) {
198
225
  }
199
226
  /** One inner page: rendered only while it is the shell's current page. */
200
227
  export function SiteShellPage({ id, title, children }) {
201
- const { page } = useSiteShell();
228
+ const { page, pageEnter } = useSiteShell();
202
229
  const active = page === id;
203
230
  const first = React.useRef(true);
204
231
  React.useEffect(() => {
@@ -208,7 +235,7 @@ export function SiteShellPage({ id, title, children }) {
208
235
  }, [active, title]);
209
236
  if (!active)
210
237
  return null;
211
- return (_jsx("div", { "data-site-page": id, className: "contents", children: children }));
238
+ return (_jsx("div", { "data-site-page": id, "data-page-enter": pageEnter ? "" : undefined, className: "contents", children: children }));
212
239
  }
213
240
  /** One language version: rendered only while it is the shell's current locale (with `lang`). */
214
241
  export function SiteShellLocale({ code, children }) {
@@ -31,7 +31,9 @@ export interface StatsBandProps extends Omit<BlockBaseProps, "variant" | "labels
31
31
  }
32
32
  /**
33
33
  * Concrete, sourced numbers ("4.8 on Google · 1,240 reviews"). Semantic `dl`, Intl formatting,
34
- * units spelled out for screen readers, no count-up animation. Server component.
34
+ * units spelled out for screen readers. Numeric figures count up once when they scroll into view
35
+ * (motion kit CountUp: width reserved, final value for screen readers, still under reduced
36
+ * motion / `motion="off"`); the items reveal in sequence. Server component.
35
37
  *
36
38
  * ```tsx
37
39
  * <StatsBand items={[{ value: 4.8, label: "Google rating", source: "1,240 reviews, Nov 2026" }]} />
@@ -3,6 +3,8 @@ import { cn } from "../../lib/cn.js";
3
3
  import { Container, Section } from "../section.js";
4
4
  import { SectionHeader } from "../section-header.js";
5
5
  import { RichText } from "../rich-text.js";
6
+ import { CountUp } from "../../motion/count-up.js";
7
+ import { Reveal, Stagger } from "../../motion/reveal.js";
6
8
  import { siteText, useBlockIds } from "./_kit-1.js";
7
9
  function formatValue(v, locale) {
8
10
  if (typeof v !== "number")
@@ -12,7 +14,9 @@ function formatValue(v, locale) {
12
14
  }
13
15
  /**
14
16
  * Concrete, sourced numbers ("4.8 on Google · 1,240 reviews"). Semantic `dl`, Intl formatting,
15
- * units spelled out for screen readers, no count-up animation. Server component.
17
+ * units spelled out for screen readers. Numeric figures count up once when they scroll into view
18
+ * (motion kit CountUp: width reserved, final value for screen readers, still under reduced
19
+ * motion / `motion="off"`); the items reveal in sequence. Server component.
16
20
  *
17
21
  * ```tsx
18
22
  * <StatsBand items={[{ value: 4.8, label: "Google rating", source: "1,240 reviews, Nov 2026" }]} />
@@ -23,19 +27,19 @@ export function StatsBand({ variant = "inline", eyebrow, title, lead, body, item
23
27
  const { base, headingId } = useBlockIds(id, "stats");
24
28
  const figure = (s, size) => {
25
29
  const spoken = s.unitLabel ?? (s.unit === "%" ? t("percent") : s.unit);
26
- return (_jsxs("span", { className: cn("font-display tabular text-fg", size), children: [formatValue(s.value, locale), s.unit ? (_jsxs(_Fragment, { children: [_jsx("sup", { "aria-hidden": true, className: "vb-stat-unit ms-[0.08em] align-super font-sans text-[0.38em] font-medium tracking-normal text-fg-muted", children: s.unit }), _jsxs("span", { className: "sr-only", children: [" ", spoken] })] })) : null] }));
30
+ return (_jsxs("span", { className: cn("font-display tabular text-fg", size), children: [typeof s.value === "number" && !editMode ? _jsx(CountUp, { value: s.value, locale: locale }) : formatValue(s.value, locale), s.unit ? (_jsxs(_Fragment, { children: [_jsx("sup", { "aria-hidden": true, className: "vb-stat-unit ms-[0.08em] align-super font-sans text-[0.38em] font-medium tracking-normal text-fg-muted", children: s.unit }), _jsxs("span", { className: "sr-only", children: [" ", spoken] })] })) : null] }));
27
31
  };
28
32
  const source = (s, cls) => s.source ? (_jsxs("dd", { className: cn("font-mono text-[0.6875rem] leading-snug tracking-[0.02em] text-fg-subtle", cls), children: [_jsx("span", { className: "sr-only", children: t("source", { source: "" }) }), s.source] })) : null;
29
- const header = title || eyebrow || (lead && variant !== "with-text") ? (_jsx(SectionHeader, { eyebrow: eyebrow, title: title, lead: variant === "with-text" ? undefined : lead, headingLevel: headingLevel, headingId: headingId, editMode: editMode, labels: labels, locale: locale })) : null;
33
+ const header = title || eyebrow || (lead && variant !== "with-text") ? (_jsx(Reveal, { disabled: editMode, children: _jsx(SectionHeader, { eyebrow: eyebrow, title: title, lead: variant === "with-text" ? undefined : lead, headingLevel: headingLevel, headingId: headingId, editMode: editMode, labels: labels, locale: locale }) })) : null;
30
34
  let content;
31
35
  if (variant === "ledger") {
32
- content = (_jsxs("div", { className: "grid gap-x-(--gutter) gap-y-10 @4xl:grid-cols-12", children: [header ? _jsx("div", { className: "@4xl:col-span-4", children: header }) : null, _jsx("dl", { className: cn("flex flex-col border-t border-line", header ? "@4xl:col-span-7 @4xl:col-start-6" : "@4xl:col-span-12"), children: items.map((s, i) => (_jsxs("div", { className: "grid grid-cols-[auto_1fr_auto] items-baseline gap-x-4 gap-y-1 border-b border-line py-5", "data-field": editMode ? `props.items.${i}` : undefined, children: [_jsx("dt", { className: "text-body text-fg", children: s.label }), _jsx("span", { "aria-hidden": true, className: "vb-leader self-end" }), _jsx("dd", { className: "text-end", children: figure(s, "text-display-m") }), source(s, "col-span-3")] }, i))) })] }));
36
+ content = (_jsxs("div", { className: "grid gap-x-(--gutter) gap-y-10 @4xl:grid-cols-12", children: [header ? _jsx("div", { className: "@4xl:col-span-4", children: header }) : null, _jsx(Stagger, { asChild: true, effect: "fade", disabled: editMode, children: _jsx("dl", { className: cn("flex flex-col border-t border-line", header ? "@4xl:col-span-7 @4xl:col-start-6" : "@4xl:col-span-12"), children: items.map((s, i) => (_jsxs("div", { className: "grid grid-cols-[auto_1fr_auto] items-baseline gap-x-4 gap-y-1 border-b border-line py-5", "data-field": editMode ? `props.items.${i}` : undefined, children: [_jsx("dt", { className: "text-body text-fg", children: s.label }), _jsx("span", { "aria-hidden": true, className: "vb-leader self-end" }), _jsx("dd", { className: "text-end", children: figure(s, "text-display-m") }), source(s, "col-span-3")] }, i))) }) })] }));
33
37
  }
34
38
  else if (variant === "with-text") {
35
- content = (_jsxs("div", { className: "grid gap-x-(--gutter) gap-y-12 @4xl:grid-cols-12 @4xl:items-start", children: [_jsxs("div", { className: "flex flex-col gap-6 @4xl:col-span-5", children: [header, lead ? _jsx("p", { className: "vb-lead text-lead text-fg-muted", children: lead }) : null, body ? _jsx(RichText, { doc: body, components: components, labels: labels, locale: locale, headingOffset: 1, editMode: editMode, field: "props.body" }) : null] }), _jsx("dl", { className: "grid grid-cols-2 border-s border-t border-line @4xl:col-span-6 @4xl:col-start-7", children: items.slice(0, 4).map((s, i) => (_jsxs("div", { className: "flex flex-col gap-2 border-e border-b border-line p-[clamp(1rem,3cqi,2rem)]", "data-field": editMode ? `props.items.${i}` : undefined, children: [_jsx("dt", { className: "order-2 text-small text-fg-muted text-pretty", children: s.label }), _jsx("dd", { className: "order-1", children: figure(s, "text-display-l") }), source(s, "order-3 mt-auto pt-3")] }, i))) })] }));
39
+ content = (_jsxs("div", { className: "grid gap-x-(--gutter) gap-y-12 @4xl:grid-cols-12 @4xl:items-start", children: [_jsxs("div", { className: "flex flex-col gap-6 @4xl:col-span-5", children: [header, lead ? _jsx("p", { className: "vb-lead text-lead text-fg-muted", children: lead }) : null, body ? _jsx(RichText, { doc: body, components: components, labels: labels, locale: locale, headingOffset: 1, editMode: editMode, field: "props.body" }) : null] }), _jsx(Stagger, { asChild: true, disabled: editMode, children: _jsx("dl", { className: "grid grid-cols-2 border-s border-t border-line @4xl:col-span-6 @4xl:col-start-7", children: items.slice(0, 4).map((s, i) => (_jsxs("div", { className: "flex flex-col gap-2 border-e border-b border-line p-[clamp(1rem,3cqi,2rem)]", "data-field": editMode ? `props.items.${i}` : undefined, children: [_jsx("dt", { className: "order-2 text-small text-fg-muted text-pretty", children: s.label }), _jsx("dd", { className: "order-1", children: figure(s, "text-display-l") }), source(s, "order-3 mt-auto pt-3")] }, i))) }) })] }));
36
40
  }
37
41
  else {
38
- content = (_jsxs("div", { className: "flex flex-col gap-12", children: [header, _jsx("dl", { className: cn("grid grid-cols-2 gap-y-10", items.length >= 3 && "@4xl:grid-cols-3", items.length >= 4 && "@4xl:grid-cols-4", items.length >= 5 && "@6xl:grid-cols-5"), children: items.map((s, i) => (_jsxs("div", { className: "vb-stat-inline flex flex-col gap-2 border-line ps-[clamp(0.75rem,2.5cqi,2rem)]", "data-field": editMode ? `props.items.${i}` : undefined, children: [_jsx("dt", { className: "order-2 max-w-[24ch] text-small text-fg-muted text-pretty", children: s.label }), _jsx("dd", { className: "order-1", children: figure(s, "text-display-l") }), source(s, "order-3")] }, i))) })] }));
42
+ content = (_jsxs("div", { className: "flex flex-col gap-12", children: [header, _jsx(Stagger, { asChild: true, disabled: editMode, children: _jsx("dl", { className: cn("grid grid-cols-2 gap-y-10", items.length >= 3 && "@4xl:grid-cols-3", items.length >= 4 && "@4xl:grid-cols-4", items.length >= 5 && "@6xl:grid-cols-5"), children: items.map((s, i) => (_jsxs("div", { className: "vb-stat-inline flex flex-col gap-2 border-line ps-[clamp(0.75rem,2.5cqi,2rem)]", "data-field": editMode ? `props.items.${i}` : undefined, children: [_jsx("dt", { className: "order-2 max-w-[24ch] text-small text-fg-muted text-pretty", children: s.label }), _jsx("dd", { className: "order-1", children: figure(s, "text-display-l") }), source(s, "order-3")] }, i))) }) })] }));
39
43
  }
40
- return (_jsx(Section, { theme: theme, anchor: anchor, headingId: title ? headingId : undefined, blockId: base, blockType: "stats", editMode: editMode, className: cn("vb-stats", className), "data-variant": variant, children: _jsx(Container, { className: "vb-reveal", children: content }) }));
44
+ return (_jsx(Section, { theme: theme, anchor: anchor, headingId: title ? headingId : undefined, blockId: base, blockType: "stats", editMode: editMode, className: cn("vb-stats", className), "data-variant": variant, children: _jsx(Container, { children: content }) }));
41
45
  }
@@ -11,6 +11,7 @@ import { labelTemplates, site2Text } from "./shared/labels-2.js";
11
11
  import { fieldAttr, formatDate2, isoDay, useBlockIds2 } from "./shared/kit-2.js";
12
12
  import { TestimonialsCarousel } from "./shared/testimonials-carousel.js";
13
13
  import { carouselLabelKeys } from "./shared/island-keys.js";
14
+ import { Reveal, Stagger } from "../../motion/reveal.js";
14
15
  export const REVIEW_SOURCE_NAMES = { google: "Google", yandex: "Yandex", tripadvisor: "Tripadvisor", trustpilot: "Trustpilot", direct: "" };
15
16
  /**
16
17
  * Customer voices: agency references, Google reviews for a spa or restaurant, SaaS quotes.
@@ -27,7 +28,7 @@ export function Testimonials({ variant: variantProp = "feature", eyebrow, title,
27
28
  const variant = variantProp === "carousel" && items.length < 2 ? "feature" : variantProp;
28
29
  const ctx = { t, locale, components, labels, editMode };
29
30
  const header = title || eyebrow || lead ? _jsx(SectionHeader, { eyebrow: eyebrow, title: title, lead: lead, headingId: headingId, headingLevel: headingLevel, editMode: editMode, labels: labels, locale: locale }) : null;
30
- return (_jsx(Section, { theme: theme, anchor: anchor, headingId: title ? headingId : undefined, blockId: id, blockType: "testimonials", editMode: editMode, className: cn("vb-testimonials", className), "data-variant": variant, children: _jsx(Container, { className: "flex flex-col gap-12 @4xl:gap-16", children: variant === "carousel" ? (_jsx(TestimonialsCarousel, { header: header, count: items.length, labels: labelTemplates(labels, carouselLabelKeys), locale: locale, children: items.map((item, i) => (_jsx(QuoteFigure, { item: item, index: i, ctx: ctx, size: "m" }, i))) })) : (_jsxs(_Fragment, { children: [variant !== "reviews" ? header : null, variant === "feature" ? (_jsx(FeatureQuote, { item: items[0], ctx: ctx })) : variant === "wall" ? (_jsx("ul", { className: "vb-quote-wall gap-x-(--gutter) @2xl:columns-2 @4xl:columns-3", children: items.map((item, i) => (_jsx("li", { className: "mb-12 break-inside-avoid border-t border-line pt-6", children: _jsx(QuoteFigure, { item: item, index: i, ctx: ctx }) }, i))) })) : (_jsx(ReviewsLayout, { header: header, items: items, summary: summary, ctx: ctx }))] })) }) }));
31
+ return (_jsx(Section, { theme: theme, anchor: anchor, headingId: title ? headingId : undefined, blockId: id, blockType: "testimonials", editMode: editMode, className: cn("vb-testimonials", className), "data-variant": variant, children: _jsx(Container, { className: "flex flex-col gap-12 @4xl:gap-16", children: variant === "carousel" ? (_jsx(TestimonialsCarousel, { header: header, count: items.length, labels: labelTemplates(labels, carouselLabelKeys), locale: locale, children: items.map((item, i) => (_jsx(QuoteFigure, { item: item, index: i, ctx: ctx, size: "m" }, i))) })) : (_jsxs(_Fragment, { children: [variant !== "reviews" ? header : null, variant === "feature" ? (_jsx(Reveal, { disabled: editMode, children: _jsx(FeatureQuote, { item: items[0], ctx: ctx }) })) : variant === "wall" ? (_jsx(Stagger, { asChild: true, disabled: editMode, children: _jsx("ul", { className: "vb-quote-wall gap-x-(--gutter) @2xl:columns-2 @4xl:columns-3", children: items.map((item, i) => (_jsx("li", { className: "mb-12 break-inside-avoid border-t border-line pt-6", children: _jsx(QuoteFigure, { item: item, index: i, ctx: ctx }) }, i))) }) })) : (_jsx(Reveal, { disabled: editMode, children: _jsx(ReviewsLayout, { header: header, items: items, summary: summary, ctx: ctx }) }))] })) }) }));
31
32
  }
32
33
  /** Monochrome stars with a text alternative ("4.8 out of 5"). */
33
34
  const RATING_STAR_SIZE = { sm: "size-3", md: "size-3.5", lg: "size-5" };
@@ -9,3 +9,4 @@ export { exportToBlob, toExportMatrix, exportFilename, downloadBlob, maskValue,
9
9
  export { SetupChecklist, SetupChecklistLauncher, setupChecklistLabels, type SetupChecklistProps, type SetupChecklistLauncherProps, type SetupChecklistLabels, type SetupItem, type SetupItemStatus, } from "./setup-checklist.js";
10
10
  export { PermissionMatrix, PermissionMatrixCell, PermissionDiff, PermissionCompare, permissionMatrixLabels, type PermissionMatrixProps, type PermissionMatrixCellProps, type PermissionDiffProps, type PermissionCompareProps, type PermissionMatrixLabels, type PermissionResource, type PermissionAction, } from "./permission-matrix.js";
11
11
  export { diffPermissions, applyDependencies, effectiveLevel, scopeRank, isGranted, setMany, setPermission, SCOPE_ORDER, type PermissionScope, type PermissionLevel, type PermissionValue, type PermissionDependency, type PermissionChange, } from "./permission-core.js";
12
+ export { SignerStatus, signerStatusLabels, type SignerStatusProps, type SignerStatusLabels, type Signer, type SignerState, } from "./signer-status.js";