/* Page-builder blocks — shared by every theme, like commerce.css.
   Blocks reuse the theme's own component classes (.btn, .product-grid, .collection-card,
   .newsletter, .lead, .eyebrow, .muted) wherever one exists; this file only supplies the
   structure those classes do not cover, and it reads colours and fonts from the active
   theme's custom properties so a built page always matches the chosen design. */

/* Written against the **semantic colour roles**, which the theme engine emits per mode, and not
   against `--color-text`/`--color-background`. Those two come from `settings_schema` defaults, so
   `css_vars` emits the same value for light and dark — the mode-static legacy pair a test already
   forbids inside a theme stylesheet. This file is shared rather than a theme's, so it was never
   swept: in dark mode `--pb-soft` was a near-white panel behind the quote, video and FAQ blocks,
   `--pb-line` was an invisible border, `--pb-muted` was dark grey on a dark ground (3.1:1) and
   `.pb-prose h2` was near-black ink on it. Every built page, on all six themes. */
.builder-page {
  --pb-ink: var(--color-ink, var(--c-text, #0f172a));
  --pb-bg: var(--color-surface, var(--c-bg, #fff));
  --pb-line: color-mix(in srgb, var(--pb-ink) 15%, transparent);
  --pb-soft: color-mix(in srgb, var(--pb-ink) 6%, var(--pb-bg));
  --pb-muted: var(--color-ink-muted, color-mix(in srgb, var(--pb-ink) 65%, var(--pb-bg)));
  --pb-radius: 14px;
}
.builder-page .pb-block { width: min(72rem, 100% - 3rem); margin-inline: auto; padding-block: var(--pb-space, clamp(1.5rem, 4vw, 3rem)); }
.builder-page .pb-block > * + * { margin-top: 1.25rem; }
.builder-page .pb-heading { margin: 0; }
/* The contact block's sentence under its heading is not a direct child of the block, so the
   rhythm above never reached it and it sat on the heading. */
.builder-page .pb-contact-copy > .muted { margin-top: 0.75rem; }
.builder-page .pb-block-head { display: flex; align-items: baseline; justify-content: space-between; gap: 1rem; flex-wrap: wrap; }
.builder-page .pb-actions { display: flex; flex-wrap: wrap; gap: 0.75rem; }
.builder-page .pb-empty, .builder-page .pb-caption { color: var(--pb-muted); }
.builder-page .pb-placeholder {
  display: grid; place-items: center; min-height: 10rem; border: 2px dashed var(--pb-line);
  border-radius: var(--pb-radius); color: var(--pb-muted); font-size: 0.9rem;
}

/* The layout group ------------------------------------------------------------------------
   Alignment, width and vertical space are asked of every block (`blocks.LAYOUT_SETTINGS`) and
   arrive as these classes on its root element via `{{ layout_class }}`. A theme restyles the
   whole vocabulary by overriding this handful of rules — which is why the block templates
   carry semantic classes rather than the editor writing per-page CSS. */
.builder-page .pb-align-left { text-align: left; }
.builder-page .pb-align-center { text-align: center; }
.builder-page .pb-align-right { text-align: right; }
.builder-page .pb-align-center .pb-actions, .builder-page .pb-align-center .lead { margin-inline: auto; justify-content: center; }
.builder-page .pb-align-right .pb-actions { justify-content: flex-end; }
/* `.lead` and `.pb-prose` keep a readable measure at every width; the *container* is what the
   width setting moves, so a full-bleed hero still holds a column of legible copy. */
.builder-page .pb-align-center .pb-prose { margin-inline: auto; }
/* **And the heading is a box too.** The rule above was applied to `.lead`, `.pb-actions` and
   `.pb-prose` and not to headings — so a theme that gives a heading a `max-width` (most do,
   for a readable measure) left its box hard against the gutter while `text-align: center`
   centred the words *inside that box*. On vault's centred hero the heading's centre measured
   510px against 720px for the standfirst and the button directly beneath it: three elements
   that are all "centred" and visibly line up with nothing. Only geometry can see it — the
   markup and the CSS both read exactly as intended, which is why
   `tests/themes/test_block_alignment_browser.py` measures boxes rather than reading rules. */
.builder-page .pb-align-center .pb-heading,
.builder-page .pb-align-center .pb-hero-heading { margin-inline: auto; }
.builder-page .pb-align-right .pb-heading,
.builder-page .pb-align-right .pb-hero-heading { margin-inline: auto 0; }
.builder-page .pb-align-left .pb-heading,
.builder-page .pb-align-left .pb-hero-heading { margin-inline: 0; }
/* Alignment has to move the box, not only the text inside it. A theme styles `.lead` for its
   *own* hero and most of them centre it — aurora's is `max-width: 38rem; margin-inline: auto` —
   so a left-aligned builder hero put its heading and its button hard against the gutter and
   floated the sentence between them. That is the About page every new store ships. */
.builder-page .pb-align-left .lead, .builder-page .pb-align-left .pb-prose { margin-inline: 0; }
.builder-page .pb-align-left .pb-actions { justify-content: flex-start; }
.builder-page .pb-align-right .lead, .builder-page .pb-align-right .pb-prose { margin-inline: auto 0; }
.builder-page .pb-width-narrow { width: min(48rem, 100% - 3rem); }
.builder-page .pb-width-wide { width: min(72rem, 100% - 3rem); }
.builder-page .pb-width-full { width: 100%; }
/* A narrow block is a narrower *container*, and a centred one — so a left-aligned narrow block
   sat indented while the wide block above it started at the gutter, and a page mixing the two
   (hero wide, copy narrow: the shipped default) had two left edges and lined up with neither.
   Aligned left or right, a narrow block takes the wide track's edge and spends its slack on the
   other side. `(100% - 72rem) / 2` is that track's own gutter, floored at the 1.5rem the width
   rules use once the viewport is narrower than the track. */
.builder-page .pb-align-left.pb-width-narrow { margin-inline: max(1.5rem, (100% - 72rem) / 2) auto; }
.builder-page .pb-align-right.pb-width-narrow { margin-inline: auto max(1.5rem, (100% - 72rem) / 2); }
.builder-page .pb-width-narrow .pb-prose, .builder-page .pb-width-narrow > img { max-width: 42rem; }
.builder-page .pb-space-none { --pb-space: 0rem; }

/* The first band on a page touches the header only when it *bleeds*.
   A block's vertical space is `padding-block`, which for a toned section is painted inside the
   tone — so an inset coloured band began flush against the header with white gutters either
   side, reading as a box shoved under the navigation rather than as the top of the page. That
   was harbour (soft) and depot (inverted); a full-bleed hero touching the header is the design,
   which is why `.pb-bleed` is exempt rather than the rule being "always leave a gap". */
.builder-page > .pb-section:first-child > * > .pb-block.pb-tone-soft:not(.pb-bleed),
.builder-page > .pb-section:first-child > * > .pb-block.pb-tone-raised:not(.pb-bleed),
.builder-page > .pb-section:first-child > * > .pb-block.pb-tone-accent:not(.pb-bleed),
.builder-page > .pb-section:first-child > * > .pb-block.pb-tone-inverted:not(.pb-bleed) {
  margin-top: clamp(1.25rem, 3vw, 2.5rem);
}

.builder-page .pb-space-small { --pb-space: clamp(0.75rem, 2vw, 1.5rem); }
.builder-page .pb-space-medium { --pb-space: clamp(1.5rem, 4vw, 3rem); }
.builder-page .pb-space-large { --pb-space: clamp(3rem, 7vw, 6rem); }

/* Grids ---------------------------------------------------------------------------------- */
.builder-page .pb-grid { display: grid; gap: 1.5rem; }
.builder-page .pb-cols-2 { grid-template-columns: repeat(auto-fit, minmax(min(16rem, 100%), 1fr)); }
.builder-page .pb-cols-3 { grid-template-columns: repeat(auto-fit, minmax(min(14rem, 100%), 1fr)); }
.builder-page .pb-cols-4 { grid-template-columns: repeat(auto-fit, minmax(min(12rem, 100%), 1fr)); }

/* Hero -----------------------------------------------------------------------------------
   A hero with a picture shows the **picture**. This was `opacity: 0.28`, which is a wash and
   not a photograph: on a light palette the image all but vanished, so a front page that had
   been given real artwork still read as an empty band of colour — and because that is the one
   screen the theme gallery previews, it is a large part of why seven different themes
   photographed as one. The legibility of the words over it is a `scrim`'s job, not the
   image's, for the reason the carousel already documents: dim the picture and there is no
   contrast figure to measure, tint behind the text and there is.

   The scrim is **two layers, and the flat one is the load-bearing half**. A merchant uploads a
   photograph nobody has measured, so a purely directional gradient is a contrast figure that
   depends on the picture: `--pb-hero-veil` covers the whole band and is what guarantees a
   floor, and `--pb-hero-cover` adds the rest behind the copy so the far side still reads as a
   photograph rather than a tint. A theme raises either for its own palette.

   It follows the block's own alignment, because a gradient that always came from the left put
   its cover on the empty side of a centred or right-aligned hero — the same shape as
   `pb-align-*` centring text without moving the box. */
.builder-page .pb-hero { position: relative; overflow: hidden; border-radius: var(--pb-radius); }
/* Raised from 0.5/0.45 after looking at a real shop: a bright, busy photograph (a flower
   stall) left the standfirst hard to read while the heading just held. The floor has to
   be set for the worst photograph a merchant will upload, not the average one — and
   nothing measures this, because the sweeps compare ink against the *painted* backdrop
   rather than against the picture. */
.builder-page .pb-hero[data-has-image] { --pb-hero-veil: 0.62; --pb-hero-cover: 0.55; }
.builder-page .pb-hero-image { position: absolute; inset: 0; width: 100%; height: 100%; object-fit: cover; }
.builder-page .pb-hero[data-has-image]::after {
  content: ""; position: absolute; inset: 0; pointer-events: none;
  background:
    linear-gradient(
      to right,
      color-mix(in srgb, var(--pb-bg) calc(var(--pb-hero-cover) * 100%), transparent) 0%,
      color-mix(in srgb, var(--pb-bg) calc(var(--pb-hero-cover) * 55%), transparent) 52%,
      transparent 88%),
    color-mix(in srgb, var(--pb-bg) calc(var(--pb-hero-veil) * 100%), transparent);
}
.builder-page .pb-hero[data-has-image].pb-align-right::after { background-position: right; transform: scaleX(-1); }
.builder-page .pb-hero[data-has-image].pb-align-center::after {
  background:
    radial-gradient(
      ellipse 70% 80% at 50% 50%,
      color-mix(in srgb, var(--pb-bg) calc(var(--pb-hero-cover) * 100%), transparent) 0%,
      transparent 100%),
    color-mix(in srgb, var(--pb-bg) calc(var(--pb-hero-veil) * 100%), transparent);
}
/* `z-index` because the scrim is an `::after`, which paints after every child: without it the
   tint that exists to make the words readable would be drawn over the words. */
.builder-page .pb-hero-body { position: relative; z-index: 1; padding: clamp(1.5rem, 5vw, 3.5rem) 0; }
.builder-page .pb-hero[data-has-image] .pb-hero-body { padding-block: clamp(3rem, 12vw, 7rem); }

/* The hero photograph settles rather than sitting still — the slow scale every theme in the
   reference set opens with. Two rules it keeps, both the reveal's:

   - **The starting scale is behind `html.js-reveal`**, the class `reveal.js` adds itself. With
     no JavaScript the picture is simply at rest, rather than left permanently 6% too large and
     cropped — the same failure mode as a hidden state nothing ever un-hides.
   - **The settled rule repeats the whole selector.** A short `.js-reveal .is-revealed
     .pb-hero-image` would be (0,3,0) against (0,5,0) here and would never apply, which is
     exactly how the block reveal came to leave every home page invisible.

   It runs **once**, on arrival. Nothing loops: a background that never stops moving is the
   thing WCAG 2.2.2 is about, and the carousel already refuses to auto-advance for that reason. */
.js-reveal .builder-page .pb-hero[data-has-image] .pb-hero-image {
  transform: scale(var(--pb-hero-zoom, 1.06));
  transition: transform var(--pb-hero-settle, 1400ms) cubic-bezier(0.22, 0.61, 0.36, 1);
}
.js-reveal .builder-page .pb-hero[data-has-image].is-revealed .pb-hero-image { transform: none; }
@media (prefers-reduced-motion: reduce) {
  .js-reveal .builder-page .pb-hero[data-has-image] .pb-hero-image {
    transform: none;
    transition: none;
  }
}
.builder-page .pb-hero-heading { margin: 0 0 0.5rem; }
/* Every theme resets `p` to `margin: 0`, and nothing else spaced the hero's parts — so the
   standfirst's last line rested on the button beneath it (1–3px) on the About page every new
   shop ships, on all twelve themes. (0,3,0) outranks the reset without a theme needing a rule. */
.builder-page .pb-hero-body > .lead { margin-top: 0.75rem; }
.builder-page .pb-hero-body > .pb-actions { margin-top: 1.5rem; }

/* Prose ---------------------------------------------------------------------------------- */
.builder-page .pb-prose { color: var(--pb-muted); }
.builder-page .pb-prose > * + * { margin-top: 1rem; }
.builder-page .pb-prose h2, .builder-page .pb-prose h3 { color: var(--pb-ink); margin-top: 2rem; }
/* Underlined for the reason commerce.css underlines a link inside a sentence: a merchant's
   own words are exactly the case where nothing but the colour says it is a link, and a
   palette may give the accent and the ink around it the same weight. */
.builder-page .pb-prose a { color: var(--color-accent, currentColor); text-decoration: underline; text-underline-offset: 2px; }

/* Media ---------------------------------------------------------------------------------- */
.builder-page figure { margin: 0; }
.builder-page .pb-image img, .builder-page .pb-gallery img { width: 100%; border-radius: var(--pb-radius); }
.builder-page figcaption { margin-top: 0.6rem; font-size: 0.9rem; }
.builder-page .pb-video-frame { position: relative; aspect-ratio: 16 / 9; border-radius: var(--pb-radius); overflow: hidden; background: var(--pb-soft); }
.builder-page .pb-video-frame iframe { position: absolute; inset: 0; width: 100%; height: 100%; border: 0; }

/* Columns, quotes, FAQ -------------------------------------------------------------------- */
.builder-page .pb-column { display: grid; gap: 0.55rem; align-content: start; }
.builder-page .pb-quote { padding: 1.5rem; border: 1px solid var(--pb-line); border-radius: var(--pb-radius); background: var(--pb-soft); }
.builder-page .pb-quote blockquote { margin: 0 0 1rem; font-size: 1.05rem; }
.builder-page .pb-faq-list { display: grid; gap: 0.5rem; max-width: 48rem; }
.builder-page .pb-faq-item { border: 1px solid var(--pb-line); border-radius: var(--pb-radius); padding: 0.9rem 1.1rem; }
.builder-page .pb-faq-item summary { cursor: pointer; font-weight: 600; }
.builder-page .pb-faq-item p { margin-top: 0.6rem; }

/* Featured product & contact --------------------------------------------------------------- */
.builder-page .pb-featured-inner { display: grid; grid-template-columns: minmax(0, 1fr) minmax(0, 1fr); gap: clamp(1.5rem, 4vw, 3rem); align-items: center; }
.builder-page .pb-featured-media img { width: 100%; border-radius: var(--pb-radius); }
.builder-page .pb-featured-body { display: grid; gap: 0.75rem; align-content: start; }
.builder-page .pb-contact { display: grid; grid-template-columns: minmax(0, 1fr) minmax(0, 1fr); gap: clamp(1.5rem, 4vw, 3rem); align-items: start; }
.builder-page .pb-contact-details { list-style: none; margin: 1rem 0 0; padding: 0; display: grid; gap: 0.4rem; color: var(--pb-muted); }
.builder-page .pb-contact-form { display: grid; gap: 0.9rem; }
.builder-page .pb-contact-form textarea { width: 100%; }

@media (max-width: 820px) {
  .builder-page .pb-featured-inner, .builder-page .pb-contact { grid-template-columns: 1fr; }
}
@media (prefers-reduced-motion: reduce) {
  .builder-page *, .builder-page *::before, .builder-page *::after {
    animation-duration: 0.001ms !important; transition-duration: 0.001ms !important;
  }
}

/* The appearance group -----------------------------------------------------------------------
   `blocks.APPEARANCE_SETTINGS` — background tone, full bleed and per-side space — arrive as
   these classes alongside the layout ones. Every tone is a **pair**: a background role and the
   ink role that belongs on it, both from the theme's palette, never a colour a merchant typed.
   A free colour box is how a section ends up unreadable in dark mode; the palettes already
   guarantee their own pairs and the theme tests already measure them. */

/* Each tone restates the derived tokens rather than inheriting them. A custom property's
   `var()`s resolve against the element the property is *declared* on, so `--pb-soft` and
   `--pb-muted` computed up on `.builder-page` are mixes of the *page's* ink and background —
   which on an inverted section is the wrong pair in both halves. Same trap as `--c-accent-soft`
   being mixed at `:root` while every theme re-points the palette one element lower. */
/* `.pb-block` is on every one of these elements and is here for **specificity**, not selection.
   A tone at `.builder-page .pb-tone-accent` is (0,2,0), and a theme styling its own component —
   `body.theme-petal .newsletter { background: … }` — is (0,2,1). The newsletter block carries the
   bare `newsletter` class on purpose (that is how it borrows the theme's look), so the theme's
   background beat the tone's and an accent band rendered as the page ground: white ink on white,
   1.15:1, on the front page of every shop using that theme. Same collision as `.slot-day` meaning
   two things in one stylesheet. (0,3,0) settles it. */
.builder-page .pb-block.pb-tone-soft,
.builder-page .pb-block.pb-tone-raised,
.builder-page .pb-block.pb-tone-accent,
.builder-page .pb-block.pb-tone-inverted {
  background: var(--pb-bg);
  color: var(--pb-ink);
  border-radius: var(--pb-radius);
  padding-inline: clamp(1.25rem, 4vw, 2.5rem);
  --pb-line: color-mix(in srgb, var(--pb-ink) 18%, transparent);
  --pb-soft: color-mix(in srgb, var(--pb-ink) 8%, var(--pb-bg));
  /* Deliberately *not* a dimmed mix. On a toned section the palette has handed us one pair it
     stands behind; a 65% blend of it is a contrast figure nobody has measured, and pairing a
     dimmed signal colour with its own tint is exactly how `.pill-warn` came to measure 1.51:1
     on summit. Muted copy on a tone is the same ink, and the type scale does the work. */
  --pb-muted: var(--pb-ink);
}
.builder-page .pb-block.pb-tone-soft {
  --pb-bg: color-mix(in srgb, var(--color-ink, #0f172a) 6%, var(--color-surface, #fff));
  --pb-ink: var(--color-ink, #0f172a);
}
.builder-page .pb-block.pb-tone-raised {
  --pb-bg: var(--color-surface-raised, var(--color-surface, #fff));
  --pb-ink: var(--color-ink, #0f172a);
  border: 1px solid var(--color-border, #e2e8f0);
}
.builder-page .pb-block.pb-tone-accent {
  --pb-bg: var(--color-accent, #4f46e5);
  --pb-ink: var(--color-accent-ink, #fff);
}
.builder-page .pb-block.pb-tone-inverted {
  --pb-bg: var(--color-ink, #0f172a);
  --pb-ink: var(--color-surface, #fff);
}
/* A theme's `.btn` is painted from `--c-primary` on `--c-bg`, which on an accent or inverted
   ground is a button the same colour as the band it sits in. Inside a tone the button borrows
   the pair it is standing on, the one way round that is guaranteed to read. */
.builder-page .pb-block.pb-tone-accent .btn,
.builder-page .pb-block.pb-tone-inverted .btn {
  background: var(--pb-ink);
  color: var(--pb-bg);
  border-color: var(--pb-ink);
}
.builder-page .pb-tone-accent .btn-outline,
.builder-page .pb-tone-inverted .btn-outline {
  background: transparent;
  color: var(--pb-ink);
  border: 1px solid var(--pb-ink);
}
.builder-page .pb-tone-accent a:not(.btn),
.builder-page .pb-tone-inverted a:not(.btn) { color: var(--pb-ink); }

/* Full bleed: the colour runs to both edges while the words keep a readable measure, so the
   gutter becomes padding rather than margin. Written without `100vw`, which is wider than the
   page whenever a scrollbar is showing and buys a horizontal scroll for the whole document. */
.builder-page .pb-bleed { border-radius: 0; }
.builder-page .pb-bleed,
.builder-page .pb-bleed.pb-width-wide,
.builder-page .pb-bleed.pb-width-full,
.builder-page .pb-bleed.pb-width-narrow,
.builder-page .pb-bleed.pb-align-left.pb-width-narrow,
.builder-page .pb-bleed.pb-align-right.pb-width-narrow {
  width: 100%;
  max-width: none;
  margin-inline: 0;
  padding-inline: max(1.5rem, (100% - 72rem) / 2);
}
.builder-page .pb-bleed.pb-width-narrow,
.builder-page .pb-bleed.pb-align-left.pb-width-narrow,
.builder-page .pb-bleed.pb-align-right.pb-width-narrow {
  padding-inline: max(1.5rem, (100% - 48rem) / 2);
}

/* `space_top`/`space_bottom` override `space` on one side. Longhands, declared after
   `.pb-block`'s `padding-block` shorthand, so leaving one blank really does mean "as above". */
.builder-page .pb-top-none { padding-block-start: 0; }
.builder-page .pb-top-small { padding-block-start: clamp(0.75rem, 2vw, 1.5rem); }
.builder-page .pb-top-medium { padding-block-start: clamp(1.5rem, 4vw, 3rem); }
.builder-page .pb-top-large { padding-block-start: clamp(3rem, 7vw, 6rem); }
.builder-page .pb-bottom-none { padding-block-end: 0; }
.builder-page .pb-bottom-small { padding-block-end: clamp(0.75rem, 2vw, 1.5rem); }
.builder-page .pb-bottom-medium { padding-block-end: clamp(1.5rem, 4vw, 3rem); }
.builder-page .pb-bottom-large { padding-block-end: clamp(3rem, 7vw, 6rem); }

/* Image & text ------------------------------------------------------------------------------ */
.builder-page .pb-media { display: grid; grid-template-columns: minmax(0, 1fr) minmax(0, 1fr); gap: clamp(1.5rem, 4vw, 3rem); align-items: center; }
.builder-page .pb-media-figure { margin: 0; }
.builder-page .pb-media-figure img { width: 100%; border-radius: var(--pb-radius); }
.builder-page .pb-media-body { display: grid; gap: 0.9rem; align-content: start; }
/* `order` rather than a reversed column list, so the image stays first in the DOM: the reading
   order a screen reader follows must not depend on which side the picture is on. */
.builder-page .pb-media-right .pb-media-figure { order: 2; }

/* Divider ----------------------------------------------------------------------------------- */
.builder-page .pb-divider { display: block; }
.builder-page .pb-rule { border: 0; margin: 0; height: 1px; }
.builder-page .pb-rule-line { background: var(--pb-line); }
.builder-page .pb-rule-dots { height: 4px; background: radial-gradient(circle, var(--pb-line) 1.5px, transparent 1.6px) repeat-x center / 12px 4px; }
.builder-page .pb-rule-blank { background: none; }

/* Call to action ---------------------------------------------------------------------------- */
.builder-page .pb-banner-body { display: grid; gap: 0.75rem; max-width: 44rem; }
.builder-page .pb-align-center .pb-banner-body { margin-inline: auto; }
.builder-page .pb-align-right .pb-banner-body { margin-inline: auto 0; }

/* Buttons, logos, figures -------------------------------------------------------------------- */
.builder-page .pb-buttons .pb-actions { margin: 0; }
.builder-page .pb-logo-row { display: flex; flex-wrap: wrap; align-items: center; gap: clamp(1.5rem, 4vw, 3rem); }
.builder-page .pb-align-center .pb-logo-row { justify-content: center; }
.builder-page .pb-logo { display: inline-flex; }
.builder-page .pb-logo img { max-height: 3rem; width: auto; }
.builder-page .pb-stat { display: grid; gap: 0.25rem; }
.builder-page .pb-stat-value { font-family: var(--font-heading, inherit); font-size: clamp(1.9rem, 5vw, 2.75rem); line-height: 1.05; font-weight: 700; }
.builder-page .pb-stat-label { color: var(--pb-muted); font-size: 0.95rem; }

/* Listings — events, experiences, courses, services ------------------------------------------
   One card shape for all four (`blocks/_listing.html`), because they differ in exactly one
   line. Deliberately not the theme's own `.product-card`: that card has nowhere to put the
   date, the grade or the length, which is the only reason these blocks exist. */
/* A listing card carries a meta line and a price under the picture, so it needs more room
   than the three-up grid gives a collection card; the track is a token a theme can re-point. */
.builder-page .pb-listing { --pb-listing-min: 15rem; grid-template-columns: repeat(auto-fit, minmax(min(var(--pb-listing-min), 100%), 1fr)); }
.builder-page .pb-listing-card { display: grid; gap: 0.6rem; align-content: start; border: 1px solid var(--pb-line); border-radius: var(--pb-radius); overflow: hidden; background: var(--pb-soft); }
.builder-page .pb-listing-media { display: block; aspect-ratio: 4 / 3; background: var(--pb-soft); }
.builder-page .pb-listing-media img { width: 100%; height: 100%; object-fit: cover; display: block; }
.builder-page .pb-listing-body { display: grid; gap: 0.45rem; padding: 0 1rem 1.1rem; justify-items: start; }
.builder-page .pb-listing-meta { color: var(--pb-muted); font-size: 0.85rem; letter-spacing: 0.04em; text-transform: uppercase; margin: 0; }
.builder-page .pb-listing-title { margin: 0; font-size: 1.05rem; }
.builder-page .pb-listing-price { margin: 0; font-weight: 600; }

/* Reviews ----------------------------------------------------------------------------------- */
.builder-page .pb-review { margin: 0; padding: 1.25rem; border: 1px solid var(--pb-line); border-radius: var(--pb-radius); background: var(--pb-soft); display: grid; gap: 0.6rem; align-content: start; }
.builder-page .pb-review-stars { margin: 0; letter-spacing: 0.15em; }
.builder-page .pb-review-title { margin: 0; font-size: 1rem; }
.builder-page .pb-review-body { margin: 0; }
.builder-page .pb-review-by { font-size: 0.9rem; }

/* Where to find us --------------------------------------------------------------------------- */
.builder-page .pb-address { font-style: normal; line-height: 1.7; }
.builder-page .pb-map-frame { height: 320px; border-radius: var(--pb-radius); overflow: hidden; border: 1px solid var(--pb-line); }
.builder-page .pb-hint { font-size: 0.85rem; color: var(--pb-muted); }

@media (max-width: 820px) {
  .builder-page .pb-media { grid-template-columns: 1fr; }
  .builder-page .pb-media-right .pb-media-figure { order: 0; }
}

/* ---- Block classes that used to live in one theme ----------------------------------------
   `collection_list`, `hero` and `newsletter` are block templates every theme renders and none
   overrides, so `builder.css` owns them — these three were defined only in the old default
   theme's stylesheet, which is the pooled-sweep hole described in commerce.css. */
.builder-page .collection-grid { display: grid; gap: 1rem; }
.builder-page .collection-rail .collection-card {
  position: relative; display: grid; align-content: end; gap: 0.2rem;
  min-height: 11rem; padding: 1.1rem; overflow: hidden;
  border-radius: var(--pb-radius, 12px);
  background-color: var(--pb-soft); background-size: cover; background-position: center;
  color: var(--pb-ink);
}
/* A tile with a photograph behind it needs its own scrim: the label is the page's ink, and a
   merchant's collection image is whatever they uploaded. */
.builder-page .collection-rail .collection-card[style*="background-image"]::before {
  content: ""; position: absolute; inset: 0;
  background: linear-gradient(to top, color-mix(in srgb, #000 72%, transparent), transparent 68%);
}
.builder-page .collection-rail .collection-card[style*="background-image"] .collection-label,
.builder-page .collection-rail .collection-card[style*="background-image"] .collection-hint {
  position: relative; z-index: 2; color: #fff;
}
.builder-page .collection-label { font-weight: 700; font-size: 1.05rem; }
.builder-page .collection-hint { font-size: 0.85rem; color: var(--pb-muted); }
.builder-page .collection-rail .collection-card[style*="background-image"] .collection-hint {
  color: color-mix(in srgb, #fff 86%, transparent);
}

/* The eyebrow's leading mark, and the newsletter block's copy column. */
.builder-page .eyebrow .dot {
  display: inline-block; width: 0.4rem; height: 0.4rem; border-radius: 999px;
  margin-right: 0.5rem; vertical-align: middle; background: var(--pb-accent, currentColor);
}
.builder-page .newsletter-copy { display: grid; gap: 0.4rem; }


/* Muted copy inside a tone takes the tone's ink, and this has to out-specify the theme.
   Every theme sets `body.theme-<name> .lead { color: var(--color-ink-muted) }` at (0,2,1) — the
   *page's* muted ink — which on an inverted hero is dark grey on the dark band: 2.65:1 in light
   and 1.86:1 in dark, on the standfirst of the front page. The tone already computes
   `--pb-muted` as its own ink for exactly this reason; it just needed to win. (0,3,0). */
.builder-page .pb-block.pb-tone-soft .lead,
.builder-page .pb-block.pb-tone-raised .lead,
.builder-page .pb-block.pb-tone-accent .lead,
.builder-page .pb-block.pb-tone-inverted .lead,
.builder-page .pb-block.pb-tone-soft .muted,
.builder-page .pb-block.pb-tone-raised .muted,
.builder-page .pb-block.pb-tone-accent .muted,
.builder-page .pb-block.pb-tone-inverted .muted,
.builder-page .pb-block.pb-tone-soft .c-muted,
.builder-page .pb-block.pb-tone-raised .c-muted,
.builder-page .pb-block.pb-tone-accent .c-muted,
.builder-page .pb-block.pb-tone-inverted .c-muted {
  color: var(--pb-muted);
}
/* And the copy that carries no class at all. The newsletter block's body is a bare `<p>`, which
   a theme reaches with `body.theme-<name> .newsletter p` (0,2,2) — so the class list above,
   however long, could never have covered it. (0,3,1). */
.builder-page .pb-block.pb-tone-soft p,
.builder-page .pb-block.pb-tone-raised p,
.builder-page .pb-block.pb-tone-accent p,
.builder-page .pb-block.pb-tone-inverted p {
  color: var(--pb-muted);
}

/* ---- Carousel ---------------------------------------------------------------------------
   A scroll-snapping strip, not a stack of absolutely-positioned slides driven by a script: with
   no JavaScript this is still a usable, swipeable banner, and the dots are ordinary anchors to
   the slide ids. Nothing advances on its own — see the note in the template. */
/* `pb-bleed` makes a block full width by adding inset *padding*, which is right for a band of
   text and wrong for this: the slides are the picture, so they run edge to edge and it is
   `.pb-slide-body` that holds the reading width. Without this the banner sat in a 72rem box
   with the page showing either side of it. */
.builder-page .pb-carousel { position: relative; }
.builder-page .pb-carousel.pb-bleed { padding-inline: 0; }
.builder-page .pb-carousel-track {
  display: grid; grid-auto-flow: column; grid-auto-columns: 100%;
  overflow-x: auto; scroll-snap-type: x mandatory; scroll-behavior: smooth;
  scrollbar-width: none;
}
.builder-page .pb-carousel-track::-webkit-scrollbar { display: none; }
.builder-page .pb-carousel-track:focus-visible { outline: 2px solid var(--pb-accent, currentColor); outline-offset: -4px; }
/* The slide's own ground is the **ink**, not the soft tint. Two reasons, and the second is the
   one that bites: a merchant may not have uploaded a picture yet, and the words are `--pb-bg`,
   so on a near-white ground they are invisible; and the scrim above the picture is a *sibling*
   overlay rather than an ancestor, which means no contrast sweep that walks the ancestor chain
   can see it. Painting the floor dark makes the pair guaranteed whether or not there is an
   image and whether or not anything can measure the wash. */
.builder-page .pb-slide {
  position: relative; scroll-snap-align: start; display: grid; align-content: center;
  overflow: hidden; background: var(--pb-ink);
}
.builder-page .pb-carousel-band .pb-slide { min-height: clamp(14rem, 26vh, 20rem); }
.builder-page .pb-carousel-tall .pb-slide { min-height: clamp(22rem, 52vh, 34rem); }
.builder-page .pb-carousel-screen .pb-slide { min-height: min(88vh, 46rem); }
.builder-page .pb-slide-image { position: absolute; inset: 0; width: 100%; height: 100%; object-fit: cover; }
/* The wash under the words. A merchant's photograph has no contrast guarantee of its own, so
   the text sits on this rather than on the picture. */
.builder-page .pb-slide-scrim {
  position: absolute; inset: 0;
  background: linear-gradient(90deg,
    color-mix(in srgb, var(--pb-ink) 82%, transparent) 0%,
    color-mix(in srgb, var(--pb-ink) 62%, transparent) 52%,
    color-mix(in srgb, var(--pb-ink) 34%, transparent) 100%);
}
.builder-page .pb-slide-body {
  position: relative; z-index: 2; display: grid; gap: 0.75rem; justify-items: start;
  width: min(var(--pb-shell, 72rem), calc(100% - 3rem));
  margin-inline: auto; padding: 2rem 0;
}
/* The slide paints its own ground, so its ink is the *opposite* of the page's — the scrim is
   built from `--pb-ink`, so the words have to be `--pb-bg` or they are ink on ink. */
.builder-page .pb-slide-eyebrow,
.builder-page .pb-slide-heading,
.builder-page .pb-slide-text { color: var(--pb-bg); }
.builder-page .pb-slide-eyebrow {
  font-size: 0.75rem; letter-spacing: 0.2em; text-transform: uppercase; font-weight: 700; margin: 0;
}
.builder-page .pb-slide-heading { font-size: clamp(1.8rem, 4.5vw, 3.4rem); margin: 0; max-width: 18ch; }
.builder-page .pb-slide-text { margin: 0; max-width: 46ch; }
.builder-page .pb-slide .btn {
  background: var(--pb-bg); color: var(--pb-ink); border-color: var(--pb-bg);
}
/* The slide follows the block's own `align`, rather than the carousel deciding for it — the
   same rule the themes had to learn: overriding `align` is overriding the merchant. */
.builder-page .pb-carousel.pb-align-center .pb-slide-body {
  justify-items: center; text-align: center;
}
.builder-page .pb-carousel.pb-align-center .pb-slide-heading,
.builder-page .pb-carousel.pb-align-center .pb-slide-text { margin-inline: auto; }
.builder-page .pb-carousel-dots {
  position: absolute; left: 0; right: 0; bottom: 1rem; z-index: 3;
  display: flex; gap: 0.5rem; justify-content: center;
}
.builder-page .pb-carousel-dot {
  width: 2.25rem; height: 0.28rem; border-radius: 999px;
  background: color-mix(in srgb, var(--pb-bg) 55%, transparent);
}
.builder-page .pb-carousel-dot:hover,
.builder-page .pb-carousel-dot:focus-visible { background: var(--pb-bg); }
/* The slide the strip is actually on. Nothing marked it before autoplay, and a row of identical
   dots is a position indicator that indicates no position. */
.builder-page .pb-carousel-dot[aria-current="true"] { background: var(--pb-bg); }

/* The pause control, added by `autoplay.js` and by nothing else — there is no markup for it in
   any template, on purpose. It sits over the picture on a carousel and over the band on a
   marquee, so it paints the same pair the words above it do rather than the page's: `--pb-bg` on
   a wash of `--pb-ink` is guaranteed by the palette in both modes, where a translucent white
   would be whatever the merchant's photograph happens to be. 44px is the touch target, and it is
   the whole point of the control — hover and `:focus-within` already served everybody else. */
.builder-page .pb-autoplay-toggle {
  display: inline-flex; align-items: center; justify-content: center;
  width: 2.75rem; height: 2.75rem; padding: 0; border: 0; border-radius: 999px;
  background: color-mix(in srgb, var(--pb-ink) 55%, transparent);
  color: var(--pb-bg); cursor: pointer;
}
.builder-page .pb-autoplay-toggle:hover,
.builder-page .pb-autoplay-toggle:focus-visible {
  background: color-mix(in srgb, var(--pb-ink) 78%, transparent);
}
.builder-page .pb-autoplay-toggle svg { width: 1.1rem; height: 1.1rem; fill: currentColor; }
/* On the carousel it joins the dots row, which is centred over the bottom of the slide. */
.builder-page .pb-carousel-dots { align-items: center; }
/* A tab strip has no picture under it, so there the control is ink on the page. */
.builder-page .pb-tabs .pb-autoplay-toggle {
  width: 2rem; height: 2rem; margin-inline-start: auto; margin-bottom: 0.35rem;
  background: transparent; color: var(--pb-muted);
}
.builder-page .pb-tabs .pb-autoplay-toggle:hover,
.builder-page .pb-tabs .pb-autoplay-toggle:focus-visible {
  background: var(--pb-soft); color: var(--pb-ink);
}
.builder-page .pb-tabs .pb-autoplay-toggle svg { width: 0.9rem; height: 0.9rem; }

/* ============================================================================================
   The six sections the reference themes had and this one did not.

   All of them read the palette through `--pb-*` and none names a colour literal, so they follow
   `color_scheme` and a theme restyles them from `body.theme-<name>` — commerce.css and this file
   are both linked *after* the theme stylesheet, so a theme's tweak needs its own scope to win.
   ============================================================================================ */

/* Marquee -----------------------------------------------------------------------------------
   The track holds the phrase list twice and travels exactly -50% of itself, so the second copy
   lands where the first began and the loop has no seam. `width: max-content` is what makes that
   50% mean "half the duplicated run" rather than half the viewport.

   It pauses on hover and on focus-within, and `prefers-reduced-motion` stops it dead — but the
   viewport keeps `overflow-x: auto`, so somebody who has asked for no animation can still read
   the whole band by scrolling it. Stopping the animation without that leaves the tail of the
   list unreachable, which is worse than the motion. */
.builder-page .pb-marquee { overflow: hidden; position: relative; }
.builder-page .pb-marquee-viewport { overflow-x: auto; scrollbar-width: none; }
.builder-page .pb-marquee-viewport::-webkit-scrollbar { display: none; }
.builder-page .pb-marquee-viewport:focus-visible { outline: 2px solid var(--pb-ink); outline-offset: -3px; }
.builder-page .pb-marquee-track {
  display: flex; width: max-content;
  animation: pb-marquee-scroll var(--pb-marquee-duration, 32s) linear infinite;
}
.builder-page .pb-marquee-slow { --pb-marquee-duration: 52s; }
.builder-page .pb-marquee-medium { --pb-marquee-duration: 32s; }
.builder-page .pb-marquee-fast { --pb-marquee-duration: 18s; }
.builder-page .pb-marquee-viewport:hover .pb-marquee-track,
.builder-page .pb-marquee-viewport:focus-within .pb-marquee-track { animation-play-state: paused; }
/* The explicit stop. `autoplay.js` sets the class; the CSS hover rule above is the same
   behaviour for a pointer, which is why this is a second rule rather than a replacement — the
   band still holds still under a mouse on a page whose script never arrived. */
.builder-page .pb-marquee.is-paused .pb-marquee-track { animation-play-state: paused; }
/* Over the band's inline end, clear of the words and inside the clipped box. */
.builder-page .pb-marquee .pb-autoplay-toggle {
  position: absolute; inset-block: 0; inset-inline-end: 0.5rem; margin-block: auto;
  background: color-mix(in srgb, var(--pb-bg) 78%, transparent); color: var(--pb-ink);
}
.builder-page .pb-marquee .pb-autoplay-toggle:hover,
.builder-page .pb-marquee .pb-autoplay-toggle:focus-visible { background: var(--pb-bg); }
.builder-page .pb-marquee-run {
  display: flex; align-items: center; gap: var(--pb-marquee-gap, 2.5rem);
  list-style: none; margin: 0; padding: 0 var(--pb-marquee-gap, 2.5rem) 0 0;
}
.builder-page .pb-marquee-item { display: flex; align-items: center; gap: var(--pb-marquee-gap, 2.5rem); white-space: nowrap; }
.builder-page .pb-marquee-sep { opacity: 0.55; }
.builder-page .pb-marquee-small .pb-marquee-track { font-size: 0.8rem; letter-spacing: 0.18em; text-transform: uppercase; padding-block: 0.75rem; }
.builder-page .pb-marquee-medium .pb-marquee-track { font-size: clamp(1.1rem, 2.2vw, 1.6rem); padding-block: 1.1rem; }
.builder-page .pb-marquee-large .pb-marquee-track { font-size: clamp(1.6rem, 4.5vw, 3rem); padding-block: 1.4rem; }
@keyframes pb-marquee-scroll { from { transform: translateX(0); } to { transform: translateX(-50%); } }
@media (prefers-reduced-motion: reduce) {
  .builder-page .pb-marquee-track { animation: none; }
}

/* Lookbook ----------------------------------------------------------------------------------
   `grid-auto-flow: dense` so a wide tile that will not fit the remaining columns does not leave
   a hole behind it — a merchant reordering pictures should not have to think about packing. */
.builder-page .pb-lookbook-grid {
  display: grid; gap: var(--pb-look-gap, 0.9rem); grid-auto-flow: dense;
  grid-auto-rows: var(--pb-look-row, clamp(7rem, 14vw, 11rem));
}
.builder-page .pb-look { position: relative; margin: 0; overflow: hidden; border-radius: var(--pb-radius); background: var(--pb-soft); }
.builder-page .pb-look-wide { grid-column: span 2; }
.builder-page .pb-look-tall { grid-row: span 2; }
.builder-page .pb-look-large { grid-column: span 2; grid-row: span 2; }
.builder-page .pb-look-link { display: block; width: 100%; height: 100%; color: inherit; text-decoration: none; }
.builder-page .pb-look-image { width: 100%; height: 100%; object-fit: cover; display: block; }
.builder-page .pb-look-caption {
  position: absolute; inset-inline: 0; bottom: 0; margin: 0; padding: 1.4rem 1rem 0.8rem;
  font-size: 0.75rem; letter-spacing: 0.08em; color: var(--pb-bg);
  background: linear-gradient(to top, color-mix(in srgb, var(--pb-ink) 70%, transparent), transparent);
}
/* A tile with no picture keeps its caption, so it reads as a quiet cell rather than a fault. */
.builder-page .pb-look:not(:has(.pb-look-image)) .pb-look-caption {
  position: static; background: none; color: var(--pb-muted); padding: 1rem;
}
@media (max-width: 700px) {
  .builder-page .pb-lookbook-grid { grid-template-columns: repeat(2, minmax(0, 1fr)); }
  .builder-page .pb-look-wide, .builder-page .pb-look-large { grid-column: span 2; }
}

/* Category chips ----------------------------------------------------------------------------
   A rail, scrolled by the native gesture with scroll-snap — no script and no arrows, the
   carousel's trade. The label sits outside the picture so it is never ink-on-photograph. */
/* `margin-inline` and `margin-bottom`, never the `margin` shorthand: at (0,2,0) and later in the
   file, `margin: 0` quietly cancelled the block's own `> * + *` spacing, and "Where we go" sat
   directly on its row of pictures on every theme that seeds this block. */
.builder-page .pb-chip-rail {
  display: flex; gap: clamp(1.1rem, 2.5vw, 2.1rem); list-style: none; margin-inline: 0; margin-bottom: 0; padding: 0 0 0.5rem;
  overflow-x: auto; scroll-snap-type: x proximity; scrollbar-width: none;
}
.builder-page .pb-chip-rail::-webkit-scrollbar { display: none; }
.builder-page .pb-chip-rail > li { scroll-snap-align: start; flex: 0 0 auto; }
.builder-page .pb-chip {
  display: flex; flex-direction: column; align-items: center; gap: 0.7rem;
  text-decoration: none; color: var(--pb-ink); width: var(--pb-chip-size, 7rem);
}
.builder-page .pb-chip-media {
  display: block; width: var(--pb-chip-size, 7rem); height: var(--pb-chip-size, 7rem);
  overflow: hidden; background: var(--pb-soft);
}
.builder-page .pb-chips-round .pb-chip-media { border-radius: 999px; }
.builder-page .pb-chips-square .pb-chip-media { border-radius: var(--pb-radius); }
.builder-page .pb-chip-media img { width: 100%; height: 100%; object-fit: cover; display: block; transition: transform 620ms cubic-bezier(0.22, 0.61, 0.36, 1); }
@media (hover: hover) {
  .builder-page .pb-chip:hover .pb-chip-media img { transform: scale(var(--hover-zoom, 1.045)); }
}
@media (prefers-reduced-motion: reduce) {
  .builder-page .pb-chip-media img { transition: none; }
  .builder-page .pb-chip:hover .pb-chip-media img { transform: none; }
}
.builder-page .pb-chip-label { font-size: 0.75rem; letter-spacing: 0.12em; text-transform: uppercase; text-align: center; }
/* The text-only shape, which is also the fallback when no category has a picture. */
.builder-page .pb-chips-pill .pb-chip {
  width: auto; flex-direction: row; border: 1px solid var(--pb-line); border-radius: 999px;
  padding: 0.7rem 1.3rem; min-height: 2.75rem; justify-content: center;
}
.builder-page .pb-chips-pill .pb-chip-label { text-align: left; }

/* Collection tabs ---------------------------------------------------------------------------
   With no script every panel is visible and its own `<h3>` says which collection it is; the
   strip above is a row of in-page anchors. `tabs.js` then hides the inactive panels, hides the
   headings and adds the tablist ARIA — the markup promises no widget until it exists. */
.builder-page .pb-tab-strip { display: flex; gap: clamp(1rem, 3vw, 2rem); flex-wrap: wrap; border-bottom: 1px solid var(--pb-line); }
.builder-page .pb-tab {
  padding: 0 0 0.75rem; margin-bottom: -1px; text-decoration: none; color: var(--pb-muted);
  border-bottom: 2px solid transparent; font-size: 0.78rem; letter-spacing: 0.14em; text-transform: uppercase;
}
.builder-page .pb-tab[aria-selected="true"], .builder-page .pb-tab.is-current { color: var(--pb-ink); border-bottom-color: var(--pb-ink); }
.builder-page .pb-tab:hover { color: var(--pb-ink); }
.builder-page .pb-tab-panel[hidden] { display: none; }
.builder-page .pb-tab-title { margin: 1.5rem 0 1rem; }
.builder-page .pb-tabs[data-pb-tabs-ready] .pb-tab-title { display: none; }

/* Date search --------------------------------------------------------------------------------
   The words around the form; the form itself is `commerce.css`'s, because `/whats-on/` renders
   the same partial and two owners for one control is how the search box came to be drawn as a
   box inside a box. What this adds is the section's own rhythm and the tone-awareness the rest
   of the blocks have — inside an inverted band the panel takes that band's pair, not the
   page's, which is the `--pb-*` restating every tone does. */
.builder-page .pb-date-search-head { margin-bottom: 1rem; }
.builder-page .pb-date-search-head h2 { margin: 0 0 0.35rem; }
.builder-page .pb-date-search .date-search {
  background: var(--pb-bg); border-color: var(--pb-line);
}
.builder-page .pb-date-search .date-search-field label { color: var(--pb-muted); }
.builder-page .pb-date-search .date-search-field input {
  background: var(--pb-bg); color: var(--pb-ink); border-color: var(--pb-line);
}

/* Video with text ---------------------------------------------------------------------------
   The still is a first-party image and the play control is a real link; the frame is swapped in
   on the click, and only where consent allows an embed at all. */
.builder-page .pb-video-text-inner { display: grid; grid-template-columns: minmax(0, 1.3fr) minmax(0, 1fr); gap: clamp(1.5rem, 4vw, 3rem); align-items: center; }
.builder-page .pb-media-right .pb-video-text-inner { grid-template-columns: minmax(0, 1fr) minmax(0, 1.3fr); }
.builder-page .pb-media-right .pb-video-text-media { order: 2; }
.builder-page .pb-video-still { position: relative; aspect-ratio: 16 / 9; overflow: hidden; border-radius: var(--pb-radius); background: var(--pb-soft); }
.builder-page .pb-video-poster { width: 100%; height: 100%; object-fit: cover; display: block; }
.builder-page .pb-video-scrim {
  position: absolute; inset: 0; pointer-events: none;
  background: linear-gradient(to top, color-mix(in srgb, var(--pb-ink) 45%, transparent), color-mix(in srgb, var(--pb-ink) 8%, transparent));
}
.builder-page .pb-video-play {
  position: absolute; inset: 0; display: grid; place-items: center; text-decoration: none; color: var(--pb-bg);
}
.builder-page .pb-video-play-mark {
  width: 4.5rem; height: 4.5rem; border-radius: 999px; display: grid; place-items: center;
  border: 1px solid color-mix(in srgb, var(--pb-bg) 80%, transparent);
  background: color-mix(in srgb, var(--pb-ink) 35%, transparent);
  transition: transform 240ms cubic-bezier(0.22, 0.61, 0.36, 1);
}
.builder-page .pb-video-play-mark svg { width: 1.35rem; height: 1.35rem; }
@media (hover: hover) { .builder-page .pb-video-play:hover .pb-video-play-mark { transform: scale(1.08); } }
@media (prefers-reduced-motion: reduce) { .builder-page .pb-video-play-mark { transition: none; } }
.builder-page .pb-video-duration {
  position: absolute; left: 1rem; bottom: 0.9rem; margin: 0; color: var(--pb-bg);
  font-size: 0.7rem; letter-spacing: 0.16em; text-transform: uppercase;
}
.builder-page .pb-video-frame-live { position: absolute; inset: 0; width: 100%; height: 100%; border: 0; }
.builder-page .pb-video-text-body { display: grid; gap: 0.9rem; align-content: start; }

/* Split banner ------------------------------------------------------------------------------
   The whole panel is the link. A tone is a *pair* from the palette, never a typed colour. */
.builder-page .pb-split-row { display: grid; grid-template-columns: repeat(auto-fit, minmax(min(15rem, 100%), 1fr)); gap: var(--pb-split-gap, 0.9rem); }
.builder-page .pb-split-even .pb-split-row { grid-template-columns: repeat(2, minmax(0, 1fr)); }
.builder-page .pb-split-wide-left .pb-split-row { grid-template-columns: minmax(0, 1.6fr) minmax(0, 1fr); }
.builder-page .pb-split-wide-right .pb-split-row { grid-template-columns: minmax(0, 1fr) minmax(0, 1.6fr); }
.builder-page .pb-split-panel {
  position: relative; display: block; overflow: hidden; border-radius: var(--pb-radius);
  text-decoration: none; color: var(--pb-bg); background: var(--pb-soft);
}
.builder-page .pb-split-landscape .pb-split-panel { aspect-ratio: 4 / 3; }
.builder-page .pb-split-square .pb-split-panel { aspect-ratio: 1 / 1; }
.builder-page .pb-split-band .pb-split-panel { aspect-ratio: 21 / 9; }
.builder-page .pb-split-image { width: 100%; height: 100%; object-fit: cover; display: block; transition: transform 700ms cubic-bezier(0.22, 0.61, 0.36, 1); }
@media (hover: hover) { .builder-page .pb-split-panel:hover .pb-split-image { transform: scale(var(--hover-zoom, 1.045)); } }
@media (prefers-reduced-motion: reduce) { .builder-page .pb-split-image { transition: none; } }
.builder-page .pb-split-scrim {
  position: absolute; inset: 0; pointer-events: none;
  background: linear-gradient(to top, color-mix(in srgb, var(--pb-ink) 68%, transparent) 0%, color-mix(in srgb, var(--pb-ink) 12%, transparent) 62%, transparent 100%);
}
.builder-page .pb-split-body { position: absolute; inset-inline: clamp(1.1rem, 3vw, 1.9rem); bottom: clamp(1.1rem, 3vw, 1.7rem); display: grid; gap: 0.55rem; justify-items: start; }
/* No `opacity` here, and that is the point. It read 0.85, which on petal's accent panel
   composited white to 4.34:1 — under AA for text this small. A tone hands out a *pair* the
   palette guarantees; dimming one half of it produces a contrast figure nobody has measured,
   which is the same mistake as a 65% "muted" blend inside a toned section and how `--c-warn`
   reached 1.51:1. An eyebrow is already set apart by size, tracking and case. */
.builder-page .pb-split-eyebrow { margin: 0; font-size: 0.68rem; letter-spacing: 0.24em; text-transform: uppercase; }
.builder-page .pb-split-heading { margin: 0; }
.builder-page .pb-split-cta { font-size: 0.7rem; letter-spacing: 0.18em; text-transform: uppercase; border-bottom: 1px solid currentColor; padding-bottom: 2px; }
/* A panel with no picture: a ground and the ink that belongs on it, both from the palette. */
.builder-page .pb-split-tone-soft { background: var(--pb-soft); color: var(--pb-ink); }
.builder-page .pb-split-tone-accent { background: var(--color-accent); color: var(--color-accent-ink); }
.builder-page .pb-split-tone-inverted { background: var(--pb-ink); color: var(--pb-bg); }
.builder-page .pb-split-panel:not(:has(.pb-split-image)) .pb-split-body { position: static; padding: clamp(1.4rem, 4vw, 2.2rem); height: 100%; align-content: end; }

@media (max-width: 760px) {
  .builder-page .pb-video-text-inner,
  .builder-page .pb-media-right .pb-video-text-inner { grid-template-columns: 1fr; }
  .builder-page .pb-media-right .pb-video-text-media { order: 0; }
  .builder-page .pb-split-even .pb-split-row,
  .builder-page .pb-split-wide-left .pb-split-row,
  .builder-page .pb-split-wide-right .pb-split-row { grid-template-columns: 1fr; }
}
