
/* ────────── Universal box-sizing reset ──────────
   Without this, every element with width:100% + padding (and there are
   many — header, section roots, content wrappers) inflates past its
   container by 2×padding, producing horizontal overflow once CSS
   variables resolve. border-box makes width include padding so layouts
   stay inside the viewport. Standard practice; cheap. */
*, *::before, *::after { box-sizing: border-box; }

/* ────────── Button atom ──────────
   Base transitions for all ds-btn instances. Variant-specific hover bg below. */
.ds-btn {
  transition: transform 140ms ease, box-shadow 140ms ease, background-color 140ms ease, opacity 140ms ease;
  will-change: transform;
}
.ds-btn:hover:not(:disabled):not([aria-disabled="true"]) {
  transform: translateY(-1px);
}
.ds-btn:active:not(:disabled):not([aria-disabled="true"]) {
  transform: translateY(0);
  transition-duration: 60ms;
}
.ds-btn:focus-visible {
  outline: 2px solid var(--color-accent);
  outline-offset: 2px;
}

/* Variant-specific hover backgrounds — all token-driven. */
.ds-btn-primary:hover:not(:disabled):not([aria-disabled="true"]) {
  background-color: var(--color-primary-hover);
}
/* Outlined + text buttons take color:inherit, so on a dark band their text is
   light. Filling with the light --color-surface-hover there erased the text
   (illegible on dark heroes). A translucent wash of the button's OWN
   currentColor is band-agnostic: dark wash under dark text, light wash under
   light text — contrast is preserved on every band. */
.ds-btn-secondary:hover:not(:disabled):not([aria-disabled="true"]) {
  background-color: color-mix(in srgb, currentColor 10%, transparent);
}
.ds-btn-ghost:hover:not(:disabled):not([aria-disabled="true"]) {
  background-color: color-mix(in srgb, currentColor 10%, transparent);
}
.ds-btn-inverse:hover:not(:disabled):not([aria-disabled="true"]) {
  background-color: var(--color-surface);
}

/* ────────── Ghost family — edit-mode "add it back" affordances ──────────
   THE single source of the ghost look (Plan/23). Hidden or unconfigured
   optional content renders one of these in edit mode so its edit entry point
   never vanishes; published output renders nothing. .ds-ghost owns the state
   recipe: dashed + dimmed at rest, text/border near-full on hover with only a
   whisper of currentColor fill so the label never washes out. Geometry comes
   from the host: buttons compose ds-btn ds-btn-secondary ds-ghost (slot
   stays identical to a real button); chips add .ds-ghost-chip (via the
   shared GhostChip component). The doubled .ds-btn.ds-ghost selector is
   deliberate — it must beat the later base .ds-btn (opacity:1) and
   .ds-btn-secondary (solid border) rules at equal specificity. */
.ds-ghost {
  border-style: dashed;
  opacity: 0.5;
  cursor: pointer;
  transition: opacity 140ms ease, background-color 140ms ease;
}
/* Doubled re-assertions: later single-class rules (.ds-btn { opacity:1 },
   .ds-p { opacity:1 }, .ds-btn-secondary { border:solid }) tie the base
   .ds-ghost on specificity and win on order — these (0,2,0) forms restore
   the ghost state on any host that composes the family. */
.ds-btn.ds-ghost,
.ds-ghost.ds-ghost-chip {
  border-style: dashed;
  opacity: 0.5;
  cursor: pointer;
}
.ds-ghost:hover,
.ds-btn.ds-ghost:hover:not(:disabled):not([aria-disabled="true"]) {
  opacity: 0.92;
  background-color: color-mix(in srgb, currentColor 6%, transparent);
}
/* Chip geometry — the pill shape shared by all non-button ghosts.
   align-self keeps the pill hugging its content inside stretch flex columns
   (footer info columns, stacked section bodies). */
.ds-ghost-chip {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  align-self: flex-start;
  border: 1px dashed currentColor;
  border-radius: var(--radius-full);
  padding: 4px 12px;
  font-family: var(--font-body);
  font-size: var(--text-sm);
  color: currentColor;
  white-space: nowrap;
}

/* ────────── Atom: base structural rules ──────────
   These extend (not replace) the .ds-btn block above with the static
   layout / typography / variant fills that used to live as inline-style
   objects on every atom instance. Atom components emit composed
   classNames (e.g. 'ds-h ds-h-3 ds-text-center', 'ds-btn ds-btn-primary
   ds-btn-md') and skip the inline style attr unless a caller passes the
   style escape-hatch prop. */

/* Heading */
.ds-h {
  font-family: var(--font-heading);
  font-weight: var(--heading-weight);
  line-height: 1.15;
  margin: 0;
  color: inherit;
}
.ds-h-1 { font-size: var(--text-5xl); }
/* Section heading (H2): Sketch is ~34px, MEDIUM weight, relaxed leading — not
   the bundle's display weight. Display heads opt back up via .ds-h-display. */
.ds-h-2 { font-size: clamp(26px, 4vw, 34px); font-weight: 600; line-height: 1.2; }
.ds-h-3 { font-size: var(--text-3xl); }
.ds-h-4 { font-size: var(--text-2xl); }
.ds-h-5 { font-size: var(--text-xl); }
.ds-h-6 { font-size: var(--text-lg); }
/* Display heads (hero / CTA / form / booking lead): heavier than section heads,
   but token-driven (--display-weight: medium⇒700, bold⇒800) — the Sketch comps
   read ~700, not a hardcoded 800 (Plan/20 fidelity audit). Level-2 keeps the 40px. */
.ds-h-display { font-weight: var(--display-weight, 700); text-wrap: balance; }
.ds-h-display.ds-h-2 { font-size: var(--text-4xl); }

/* Paragraph */
.ds-p {
  font-family: var(--font-body);
  font-weight: var(--body-weight);
  line-height: 1.6;
  margin: 0;
  color: inherit;
  opacity: 1;
}
.ds-p-sm { font-size: var(--text-sm); }
.ds-p-base { font-size: var(--text-base); }
.ds-p-lg { font-size: var(--text-lg); }
.ds-p-muted { color: var(--color-text-muted); opacity: 1; }
/* On saturated / dark bands the warm muted token loses contrast, so muted text
   reverts to a dimmed light foreground there. */
.ds-cm-primary .ds-p-muted,
.ds-cm-secondary .ds-p-muted,
.ds-cm-dark .ds-p-muted { color: currentColor; opacity: 0.75; }
.ds-p-strong { font-weight: 600; }

/* Eyebrow */
.ds-eyebrow {
  font-family: var(--font-body);
  font-size: 13px;
  font-weight: 600;
  letter-spacing: 0.12em;
  text-transform: uppercase;
  margin: 0;
}
/* Sketch DNA: the eyebrow is accent-coloured (e.g. clay #c28866 on the garden
   identity), not muted grey — and carries no leading glyph. 'accent' is the
   default tone (set on the Eyebrow atom). It paints --color-accent-readable,
   not --color-accent: the token keeps the accent when it clears WCAG AA on the
   light grounds, else falls back to the brand primary / near-black so a
   low-contrast accent (gold on cream, cyan on white) never goes unreadable. */
.ds-eyebrow-accent { color: var(--color-accent-readable); opacity: 1; }
.ds-eyebrow-muted { color: currentColor; opacity: 0.65; }
.ds-eyebrow-primary { color: var(--color-primary); opacity: 1; }
.ds-eyebrow-plain { color: currentColor; opacity: 1; }
/* On mid-saturated bands the accent overline can clash, so it falls back to a
   light foreground there; default / surface keep the accent. */
.ds-cm-primary .ds-eyebrow-accent,
.ds-cm-secondary .ds-eyebrow-accent { color: currentColor; opacity: 0.9; }
/* On dark bands the readable-accent token is wrong (it is computed against the
   LIGHT grounds and may have fallen back to a near-black primary), so use the
   raw accent — mid-tone accents always clear AA on the dark surface. */
.ds-cm-dark .ds-eyebrow-accent { color: var(--color-accent); opacity: 1; }
/* …but a light card (default/surface) nested inside a saturated band restores
   the readable accent — nearest context wins (mirrors .ds-text-accent). */
.ds-cm-default .ds-eyebrow-accent,
.ds-cm-surface .ds-eyebrow-accent { color: var(--color-accent-readable); opacity: 1; }

/* Shared text alignment — used by Heading, Paragraph, Eyebrow. */
.ds-text-left { text-align: left; }
.ds-text-center { text-align: center; }
.ds-text-right { text-align: right; }

/* Emphasis text — semantic, colorMode-aware color classes. Variants must use
   these instead of hardcoding color:var(--color-primary/accent), which can
   collide with a same-coloured band and vanish (customers can't recolor text).
   On primary/secondary bands they fall back to the band's legible foreground. */
.ds-text-primary { color: var(--color-primary); }
.ds-text-accent  { color: var(--color-accent); }
/* On a saturated band the emphasis color can collide with the bg, so it falls
   back to the band's legible foreground. */
.ds-cm-primary .ds-text-primary,
.ds-cm-secondary .ds-text-primary,
.ds-cm-primary .ds-text-accent,
.ds-cm-secondary .ds-text-accent { color: currentColor; }
/* …but a light card nested inside a saturated band restores the emphasis color
   (nearest context wins via source order — same specificity, declared later). */
.ds-cm-default .ds-text-primary,
.ds-cm-surface .ds-text-primary { color: var(--color-primary); }
.ds-cm-default .ds-text-accent,
.ds-cm-surface .ds-text-accent { color: var(--color-accent); }

/* Button — extends the .ds-btn block above with static layout + variant
   fills. Hover/active/focus rules already declared higher up. */
.ds-btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: 8px;
  font-family: var(--font-body);
  font-weight: 600;
  line-height: 1;
  border-radius: var(--radius-md);
  text-decoration: none;
  cursor: pointer;
  width: auto;
  opacity: 1;
}
.ds-btn:disabled,
.ds-btn[aria-disabled="true"] {
  cursor: not-allowed;
  opacity: 0.6;
}
.ds-btn-sm { padding: 8px 16px; font-size: var(--text-sm); }
.ds-btn-md { padding: 12px 24px; font-size: var(--text-base); }
/* lg = hero / CTA lead buttons. Dialed to the Sketch comps' compact proportions
   (~44px tall, base type) — was 16px 32px / text-lg (18px), which read oversized
   next to the comps (Plan/20 fidelity audit). */
.ds-btn-lg { padding: 13px 28px; font-size: var(--text-base); }
.ds-btn-block { width: 100%; }
/* Buttons are flat (Plan/29): the elevation shadow on every primary button was
   part of the accent inflation — the fill color IS the button's emphasis. */
.ds-btn-primary {
  background-color: var(--color-primary);
  color: var(--color-primary-text);
  border: var(--border-width) solid transparent;
}
.ds-btn-secondary {
  background-color: transparent;
  color: inherit;
  border: var(--border-width) solid currentColor;
  box-shadow: none;
}
.ds-btn-ghost {
  background-color: transparent;
  color: inherit;
  border: var(--border-width) solid transparent;
  box-shadow: none;
}
.ds-btn-inverse {
  background-color: var(--color-background);
  color: var(--color-text);
  border: var(--border-width) solid transparent;
}

/* ButtonGroup — desktop base. Mobile collapse rule below at 640px. */
.ds-button-group {
  display: inline-flex;
  flex-wrap: wrap;
  gap: var(--gap);
  align-items: center;
  max-width: 100%;
}
.ds-button-group-start { justify-content: flex-start; align-self: flex-start; }
.ds-button-group-center { justify-content: center; align-self: center; }
.ds-button-group-end { justify-content: flex-end; align-self: flex-end; }

/* IconWrap */
.ds-icon-wrap {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  flex-shrink: 0;
}
.ds-icon-wrap-sm { width: 32px; height: 32px; }
.ds-icon-wrap-md { width: 48px; height: 48px; }
.ds-icon-wrap-lg { width: 64px; height: 64px; }
.ds-icon-wrap-circle { border-radius: var(--radius-full); }
.ds-icon-wrap-square { border-radius: var(--radius-md); }
.ds-icon-wrap-surface { background-color: var(--color-surface); color: var(--color-text); }
.ds-icon-wrap-primary { background-color: var(--color-primary); color: var(--color-primary-text); }
.ds-icon-wrap-accent { background-color: var(--color-accent); color: var(--color-accent-text); }
/* tint — quiet primary-tinted disc; band-agnostic (currentColor mix would wash
   on dark bands, primary mix stays legible on both). */
.ds-icon-wrap-tint { background-color: color-mix(in srgb, var(--color-primary) 12%, transparent); color: var(--color-primary); }
.ds-cm-primary .ds-icon-wrap-tint, .ds-cm-secondary .ds-icon-wrap-tint, .ds-cm-dark .ds-icon-wrap-tint {
  background-color: color-mix(in srgb, currentColor 14%, transparent);
  color: inherit;
}

/* MonogramBadge — initial letterform inside the IconWrap circle (Plan/29). */
.ds-monogram { font-family: var(--font-heading); font-weight: var(--heading-weight); }
.ds-monogram.ds-icon-wrap-sm { font-size: var(--text-sm); }
.ds-monogram.ds-icon-wrap-md { font-size: var(--text-lg); }
.ds-monogram.ds-icon-wrap-lg { font-size: var(--text-2xl); }

/* ImageFrame — wrapper + inner img sizing. Flat by default (Plan/29): the
   elevation shadow is budgeted, and the hero image is the one image that
   spends it — the page's single lifted photograph. */
.ds-image-frame {
  overflow: hidden;
  background-color: var(--color-surface);
}
.ds-hero-shell .ds-image-frame { box-shadow: var(--shadow); }
/* An image inside a card must not double up — the card already carries the
   elevation (visible as a stacked outline with 'offset'/'raised'). */
.ds-card .ds-image-frame {
  box-shadow: none;
}
.ds-image-frame > img {
  width: 100%;
  height: 100%;
  display: block;
}
.ds-image-frame-aspect-1-1  { aspect-ratio: 1 / 1; }
.ds-image-frame-aspect-4-3  { aspect-ratio: 4 / 3; }
.ds-image-frame-aspect-3-2  { aspect-ratio: 3 / 2; }
.ds-image-frame-aspect-16-9 { aspect-ratio: 16 / 9; }
.ds-image-frame-aspect-21-9 { aspect-ratio: 21 / 9; }
.ds-image-frame-radius-none { border-radius: 0; }
.ds-image-frame-radius-sm   { border-radius: var(--radius-sm); }
.ds-image-frame-radius-md   { border-radius: var(--radius-md); }
.ds-image-frame-radius-lg   { border-radius: var(--radius-lg); }
.ds-image-frame-radius-full { border-radius: var(--radius-full); }
/* Arched gallery — top-rounded (cathedral-arch) frames. A layout variant of
   the grid (variant-as-data): overrides the frame radius to a large block-start
   arch, square at the block-end. Tokenized via --radius-full. */
.ds-gallery-grid-arched .ds-image-frame {
  border-start-start-radius: var(--radius-full);
  border-start-end-radius: var(--radius-full);
  border-end-start-radius: 0;
  border-end-end-radius: 0;
}
.ds-image-frame-clickable   { cursor: pointer; }
.ds-image-frame-fit-cover > img   { object-fit: cover; }
.ds-image-frame-fit-contain > img { object-fit: contain; }

/* Badge */
.ds-badge {
  display: inline-flex;
  align-items: center;
  padding: 4px 12px;
  font-family: var(--font-body);
  font-size: var(--text-xs);
  font-weight: 600;
  line-height: 1;
  border-radius: var(--radius-full);
  letter-spacing: 0.02em;
}
.ds-badge-default { background-color: var(--color-surface); color: var(--color-text); }
.ds-badge-primary { background-color: var(--color-primary); color: var(--color-primary-text); }
.ds-badge-accent  { background-color: var(--color-accent);  color: var(--color-accent-text); }
.ds-badge-surface { background-color: var(--color-surface); color: var(--color-text-muted); }

/* Divider — currentColor at low opacity reads against any colorMode. */
.ds-divider {
  border: none;
  background-color: currentColor;
  opacity: 0.18;
  margin: 0;
}
.ds-divider-horizontal { width: 100%; height: var(--border-width); }
.ds-divider-vertical   { width: var(--border-width); height: 100%; }
.ds-divider-inset      { margin: var(--gap) 0; }

/* ────────── Service cards / list rows ──────────
   Subtle hover lift for clickable cards (data-service-card) and list rows. */
[data-service-card="true"] {
  transition: transform 140ms ease, box-shadow 140ms ease;
}
[data-service-card="true"]:hover {
  transform: translateY(-2px);
  box-shadow: var(--shadow-hover);
}

/* ────────── Form inputs ──────────
   Used by ContactForm. Border + placeholder colors derive from currentColor so
   inputs read against any colorMode (default, surface, primary, dark). */
.ds-input,
.ds-textarea {
  width: 100%;
  font-family: var(--font-body);
  font-size: var(--text-base);
  padding: 12px 14px;
  background-color: transparent;
  color: inherit;
  border: var(--border-width) solid currentColor;
  border-color: color-mix(in srgb, currentColor 28%, transparent);
  border-radius: var(--radius-sm);
  transition: border-color 140ms ease, box-shadow 140ms ease;
  box-sizing: border-box;
}
.ds-textarea {
  resize: vertical;
  min-height: 120px;
  font-family: var(--font-body);
}
.ds-input::placeholder,
.ds-textarea::placeholder {
  color: currentColor;
  opacity: 0.45;
}
.ds-input:focus-visible,
.ds-textarea:focus-visible {
  outline: none;
  border-color: var(--color-accent);
  box-shadow: 0 0 0 2px color-mix(in srgb, var(--color-accent) 32%, transparent);
}
/* Honeypot — kept in DOM for renderer-entry's bot-trap, hidden from users. */
.ds-honeypot {
  position: absolute;
  left: -9999px;
  width: 1px;
  height: 1px;
  opacity: 0;
  pointer-events: none;
}

/* ContactSimple — info bullets are desktop-hidden / mobile-shown.
   On desktop the centered form already collects what matters and the layout
   reads cleaner without redundancy; on mobile (≤768px) the bullets re-appear
   so visitors get tap-to-call/mail without scrolling past the form. */
.ds-contact-simple-info {
  display: none;
}

/* ────────── Testimonials carousel ──────────
   Hide native scrollbar in Chromium/Webkit so the carousel reads as a curated
   strip rather than a scroll-region. Firefox uses scrollbar-width. */
.ds-testimonials-carousel {
  scrollbar-width: none;
}
.ds-testimonials-carousel::-webkit-scrollbar {
  display: none;
}

/* ────────── FAQ accordion ──────────
   Native <details>/<summary> styled to match the design system. The chevron
   rotates 180° via the [open] attribute selector — no JS required. */
.ds-faq-item {
  border-top: var(--border-width) solid currentColor;
  border-color: color-mix(in srgb, currentColor 18%, transparent);
}
.ds-faq-item:last-child {
  border-bottom: var(--border-width) solid currentColor;
  border-color: color-mix(in srgb, currentColor 18%, transparent);
}
.ds-faq-summary {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--gap);
  padding: var(--gap) 0;
  cursor: pointer;
  list-style: none;
}
.ds-faq-summary::-webkit-details-marker {
  display: none;
}
.ds-faq-summary:focus-visible {
  outline: 2px solid var(--color-accent);
  outline-offset: 4px;
  border-radius: var(--radius-sm);
}
.ds-faq-chevron {
  flex-shrink: 0;
  transition: transform 200ms ease;
  opacity: 0.7;
}
.ds-faq-item[open] .ds-faq-chevron {
  transform: rotate(180deg);
}

/* ────────── Prose (blog body + Text long-form) ──────────
   Styles sanitized HTML — blog post body (.ds-blog-body) and the Text
   section's long-form variant (.ds-prose). Same allow-list, same look.
   All values token-driven so the body inherits the active bundle's identity. */
:is(.ds-blog-body, .ds-prose) {
  font-family: var(--font-body);
  font-size: var(--text-lg);
  line-height: 1.7;
}
:is(.ds-blog-body, .ds-prose) > * + * {
  margin-top: var(--gap);
}
:is(.ds-blog-body, .ds-prose) :is(h1, h2, h3) {
  font-family: var(--font-heading);
  font-weight: var(--heading-weight);
  line-height: 1.2;
  letter-spacing: -0.01em;
  margin-top: var(--gap-lg);
  margin-bottom: 0;
}
:is(.ds-blog-body, .ds-prose) h1 { font-size: var(--text-3xl); }
:is(.ds-blog-body, .ds-prose) h2 { font-size: var(--text-2xl); }
:is(.ds-blog-body, .ds-prose) h3 { font-size: var(--text-xl); }
:is(.ds-blog-body, .ds-prose) p {
  margin: 0;
}
:is(.ds-blog-body, .ds-prose) a {
  color: var(--color-primary);
  text-decoration: underline;
  text-underline-offset: 3px;
}
:is(.ds-blog-body, .ds-prose) strong { font-weight: 600; }
:is(.ds-blog-body, .ds-prose) em { font-style: italic; }
:is(.ds-blog-body, .ds-prose) :is(ul, ol) {
  padding-left: 1.5em;
  margin: 0;
}
:is(.ds-blog-body, .ds-prose) li + li { margin-top: 0.4em; }
:is(.ds-blog-body, .ds-prose) blockquote {
  margin: 0;
  padding-left: var(--gap);
  border-left: 3px solid color-mix(in srgb, currentColor 24%, transparent);
  font-style: italic;
  opacity: 0.85;
}
:is(.ds-blog-body, .ds-prose) code {
  font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
  font-size: 0.92em;
  padding: 0.15em 0.4em;
  background-color: color-mix(in srgb, currentColor 8%, transparent);
  border-radius: var(--radius-sm);
}
:is(.ds-blog-body, .ds-prose) pre {
  font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
  font-size: 0.9em;
  line-height: 1.55;
  padding: var(--gap);
  background-color: color-mix(in srgb, currentColor 6%, transparent);
  border-radius: var(--radius-md);
  overflow-x: auto;
}
:is(.ds-blog-body, .ds-prose) pre code {
  background: transparent;
  padding: 0;
}
:is(.ds-blog-body, .ds-prose) hr {
  border: none;
  border-top: 1px solid color-mix(in srgb, currentColor 16%, transparent);
  margin: var(--gap-lg) 0;
}

/* Blog index card — subtle hover lift on the cover image. */
.ds-blog-card img {
  transition: transform 200ms ease;
}
.ds-blog-card:hover img {
  transform: translateY(-2px);
}

/* Back-to-blog link on blog post pages. */
.ds-blog-back-link {
  font-family: var(--font-body);
  font-size: var(--text-base);
  color: inherit;
  text-decoration: none;
  opacity: 0.7;
}

/* Form label — ContactForm Field/FieldTextarea wrap their <label><span>
   with this. Reads slightly muted (0.85) so the input border is the
   primary visual anchor, not the label. */
.ds-form-label {
  font-family: var(--font-body);
  font-size: var(--text-sm);
  opacity: 0.85;
}

/* Section anchor target — a zero-height marker before a navigable section so
   header nav links (#services, …) jump to it. scroll-margin-top clears a
   sticky header (only the floating variant is sticky; the small offset reads
   as breathing room under the others). */
.ds-anchor {
  display: block;
  height: 0;
  scroll-margin-top: 84px;
}

/* Footer link — nav columns + contact column share this. */
.ds-footer-link {
  color: inherit;
  opacity: 0.75;
  text-decoration: none;
  font-family: var(--font-body);
  font-size: var(--text-base);
}

/* Footer layout: brand block anchors the left, the remaining columns (nav /
   contact) cluster together on the right. space-between opens the gap between
   the two groups; column content itself stays left-aligned. */
.ds-footer-standard {
  display: flex;
  justify-content: space-between;
  flex-wrap: wrap;
  gap: var(--gap-lg);
}
.ds-footer-columns {
  display: flex;
  gap: calc(var(--gap-lg) * 2);
  flex-wrap: wrap;
}

/* ────────── Card grid (governed row-of-cards primitive, Plan/22) ──────────
   The single mechanism for "a row of uniform cards/cells that collapses on
   mobile". Replaces ~15 bespoke inline grids + their scattered media rules.

   Two lanes, one class:
   • Content-flow (default) — variable/unknown item count (blog, events,
     gallery, menu, contact cards…). Auto-fit collapses natively; NO media
     rule needed. Tune the wrap point with --card-grid-min.
   • Author-controlled count — sections exposing a columns field
     (values, stats, team). Add .ds-card-grid--fixed and set
     --card-grid-cols inline; the shared media rules below own the collapse.

   Gap policy: default var(--gap-lg). Dense photo/menu mosaics opt down with
   --card-grid-gap: var(--gap) — a named, documented exception, never a
   silent per-section choice.

   The fixed lane carries ONLY the --card-grid-cols custom prop inline
   (never grid-template-columns itself), so the media rules win without
   !important. */
.ds-card-grid {
  display: grid;
  gap: var(--card-grid-gap, var(--gap-lg));
  grid-template-columns: repeat(auto-fit, minmax(min(100%, var(--card-grid-min, 260px)), 1fr));
}
.ds-card-grid--fixed {
  grid-template-columns: repeat(var(--card-grid-cols, 3), minmax(0, 1fr));
}
/* Content-flow, fill mode: cards keep their max size and pack from the start
   instead of stretching to fill empty tracks. Use where a short list (1-2
   items) should stay card-sized rather than balloon (blog / events index). */
.ds-card-grid--fill {
  grid-template-columns: repeat(auto-fill, minmax(min(100%, var(--card-grid-min, 260px)), 1fr));
}
/* Shared collapse for the fixed lane — co-located so 768→640 source order is
   self-contained (640 declared last wins at the narrower width). Nothing else
   targets .ds-card-grid--fixed. */
@media (max-width: 768px) {
  .ds-card-grid--fixed { grid-template-columns: repeat(2, minmax(0, 1fr)); }
}
@media (max-width: 640px) {
  .ds-card-grid--fixed { grid-template-columns: 1fr; }
}

/* ────────── Section helpers ──────────
   Repeated micro-patterns inside section variants, lifted out of inline
   styles so they don't multiply per-instance in published HTML. None of
   these are atoms — they're variant-internal helpers. */

/* Carousel chrome (Plan/29) — prev/next circles + "01 / 05" counter. Colors
   ride currentColor so the chrome works on any band. */
.ds-carousel-btn {
  width: 40px;
  height: 40px;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  border-radius: var(--radius-full);
  border: var(--border-width) solid color-mix(in srgb, currentColor 35%, transparent);
  background: transparent;
  color: inherit;
  cursor: pointer;
  transition: background-color 140ms ease;
}
.ds-carousel-btn:hover { background-color: color-mix(in srgb, currentColor 10%, transparent); }
.ds-carousel-counter {
  margin-left: auto;
  font-size: var(--text-xs);
  font-weight: 600;
  letter-spacing: 0.08em;
  opacity: 0.7;
}

/* Location pill — quiet tinted proof chip (testimonials with-location,
   service-area). Cornered, never centered (Plan/29). */
.ds-location-pill {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  padding: 4px 10px;
  border-radius: var(--radius-full);
  background-color: color-mix(in srgb, var(--color-primary) 9%, transparent);
  font-size: var(--text-xs);
  font-weight: 600;
  letter-spacing: 0.02em;
  color: var(--color-text);
}

/* Star rating — used by Testimonials. */
.ds-star-row { display: inline-flex; gap: 2px; }
.ds-star-filled { color: var(--color-accent); display: inline-flex; }
.ds-star-empty { color: currentColor; opacity: 0.3; display: inline-flex; }

/* Card surface (Plan/21 single source, re-governed by Plan/29): cards are FLAT
   hairline-bordered islands. The elevation shadow is a BUDGETED accent — with
   it on every card the signature stops meaning anything (the audit counted 3-6
   competing accents per viewport vs the comps' ~1). Only .ds-card-feature (a
   section's one focal object: quote form, featured plan, booking widget) and
   the hero image spend it.
   A card is always a light island (bg = --color-surface / --color-background), so its
   text must read against that, NOT against the section band. Cards that intentionally
   take a band colour carry their own .ds-cm-* class, which wins on source order. */
.ds-card { border: var(--card-border); color: var(--color-text); }
.ds-card-feature { box-shadow: var(--shadow); }
/* Pinned card foot (Plan/29 three-layer anatomy): hairline + bottom-pinned
   meta row (price, postcode, credit). margin-top:auto pins it inside the
   card's flex column, which also equalizes heights across a grid. */
.ds-card-foot {
  margin-top: auto;
  border-top: var(--border-width) solid var(--color-divider);
  padding-block: calc(var(--gap) * 0.6);
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--gap);
}

/* Testimonial card surface — bg/radius/shadow read against the section
   colorMode via inline backgroundColor (variants override with cardBg). */
.ds-testimonial-card {
  display: flex;
  flex-direction: column;
  gap: var(--gap);
  padding: var(--gap-lg);
  color: var(--color-text);
  border-radius: var(--radius-lg);
  height: 100%;
}
.ds-testimonial-quote-mark { font-style: italic; }

/* Services card surface — flex grid item with bg, radius, hover handled
   via the existing [data-service-card="true"] selector. The flex-basis
   stays inline because it depends on the column count chosen at render
   time. */
.ds-service-card {
  display: flex;
  flex-direction: column;
  border: 1px solid var(--color-surface-border);
  border-radius: var(--radius-lg);
  overflow: hidden;
  text-decoration: none;
  color: inherit;
  min-width: 260px;
}

/* Services price — token-driven typography for inline + prominent variants. */
.ds-service-price-inline {
  font-family: var(--font-body);
  font-size: var(--text-base);
  font-weight: var(--heading-weight);
  color: var(--color-primary);
  white-space: nowrap;
}
.ds-service-price-prefix {
  font-family: var(--font-body);
  font-size: var(--text-xs);
  font-weight: var(--body-weight);
  color: var(--color-text-muted);
  text-transform: uppercase;
  letter-spacing: 0.05em;
}
.ds-service-price-main {
  font-family: var(--font-heading);
  font-size: var(--text-xl);
  font-weight: var(--heading-weight);
  color: var(--color-primary);
  white-space: nowrap;
  line-height: 1.2;
}
.ds-service-price-main-text {
  font-family: var(--font-body);
  font-size: var(--text-sm);
}

/* Services — Icon columns. Cardless flex grid; each item leads with an
   IconWrap tile. flex-wrap + min-width handles the mobile collapse (no
   media query): items reflow to fewer columns, then one, on narrow widths.
   The flex-basis stays inline (depends on the column count at render). */
/* Icon-columns row uses the shared .ds-card-grid--fixed primitive (Plan/22);
   only the per-item styling lives here. */
.ds-service-icol-item {
  display: flex;
  flex-direction: column;
  align-items: flex-start;
  gap: calc(var(--gap) * 0.5);
  min-width: 220px;
  text-decoration: none;
  color: inherit;
}
.ds-service-icol-item .ds-icon-wrap {
  margin-bottom: calc(var(--gap) * 0.25);
}

/* Services — Featured. Accent card (lead service) beside a compact list.
   Stacks to a single column at the mobile breakpoint. */
.ds-service-featured {
  display: flex;
  gap: var(--gap-lg);
  align-items: stretch;
}
.ds-service-featured-card {
  flex: 1 1 0;
  min-width: 0;
  display: flex;
  flex-direction: column;
  justify-content: space-between;
  gap: var(--gap-lg);
  border-radius: var(--radius-lg);
  padding: var(--gap-lg);
  text-decoration: none;
}
.ds-service-featured-card-primary {
  background-color: var(--color-primary);
  color: var(--color-primary-text);
}
.ds-service-featured-card-surface {
  background-color: var(--color-surface);
  color: var(--color-text);
  border: 1px solid var(--color-surface-border);
}
/* On the primary card the brand-tinted price would sit primary-on-primary
   and disappear — flip price text to the inherited card text color. */
.ds-service-featured-card-primary .ds-service-price-main,
.ds-service-featured-card-primary .ds-service-price-prefix,
.ds-service-featured-card-primary .ds-service-price-inline {
  color: currentColor;
}
.ds-service-featured-card-body {
  display: flex;
  flex-direction: column;
  gap: calc(var(--gap) * 0.5);
}
.ds-service-featured-card-foot {
  display: flex;
  justify-content: space-between;
  align-items: flex-end;
  gap: var(--gap);
}
.ds-service-featured-list {
  flex: 1 1 0;
  min-width: 0;
  display: flex;
  flex-direction: column;
}
.ds-service-featured-row {
  display: flex;
  align-items: flex-start;
  gap: var(--gap);
  padding: var(--gap) 0;
  text-decoration: none;
  color: inherit;
  border-bottom: var(--border-width) solid var(--color-divider);
}
.ds-service-featured-row:first-child {
  padding-top: 0;
}
.ds-service-featured-row:last-child {
  border-bottom: none;
}
@media (max-width: 768px) {
  .ds-service-featured {
    flex-direction: column;
  }
}

/* ─── Restaurant pack: Menu Highlights + Menu Download ─── */

/* Shared price + course + tag typography (token-driven; flips to currentColor
   on saturated bands so it never sits primary-on-primary). */
.ds-menu-price {
  font-family: var(--font-heading);
  font-weight: 700;
  color: var(--color-primary);
  white-space: nowrap;
  flex-shrink: 0;
}
.ds-menu-price-lg {
  font-size: var(--text-2xl);
}
.ds-cm-primary .ds-menu-price,
.ds-cm-secondary .ds-menu-price,
.ds-cm-dark .ds-menu-price {
  color: currentColor;
}
.ds-menu-course {
  font-family: var(--font-body);
  font-size: var(--text-sm);
  font-weight: 700;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: var(--color-primary);
  margin: 0;
}
.ds-cm-primary .ds-menu-course,
.ds-cm-secondary .ds-menu-course,
.ds-cm-dark .ds-menu-course {
  color: currentColor;
  opacity: 0.75;
}
.ds-menu-tags {
  display: flex;
  flex-wrap: wrap;
  gap: calc(var(--gap) * 0.4);
}

/* Menu Highlights — Dish Cards. Grid is the shared .ds-card-grid primitive
   (dense: --card-grid-gap var(--gap), min 240px). See Plan/22. */
.ds-menu-dish-card {
  display: flex;
  flex-direction: column;
  overflow: hidden;
  padding: 0;
  border: 1px solid var(--color-surface-border);
  border-radius: var(--radius-lg);
  background-color: var(--color-surface);
}
/* On non-default bands the surface card would blend into the section — flip
   to the background tone so it always reads as a distinct card (mirrors
   ServicesCards' inline colorMode logic). */
.ds-cm-surface .ds-menu-dish-card,
.ds-cm-primary .ds-menu-dish-card,
.ds-cm-secondary .ds-menu-dish-card,
.ds-cm-dark .ds-menu-dish-card {
  background-color: var(--color-background);
}
.ds-menu-dish-media {
  aspect-ratio: 3 / 2;
  overflow: hidden;
  background-color: var(--color-surface-hover);
}
.ds-menu-dish-body {
  display: flex;
  flex-direction: column;
  gap: calc(var(--gap) * 0.5);
  padding: var(--gap);
}
.ds-menu-dish-titlerow {
  display: flex;
  justify-content: space-between;
  align-items: baseline;
  gap: var(--gap);
}

/* Menu Highlights — Classic List */
.ds-menu-classic {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(280px, 1fr));
  gap: var(--gap-lg);
}
.ds-menu-classic-group {
  display: flex;
  flex-direction: column;
  gap: calc(var(--gap) * 0.75);
}
.ds-menu-classic-rows {
  display: flex;
  flex-direction: column;
  gap: var(--gap);
}
.ds-menu-classic-row {
  display: flex;
  flex-direction: column;
  gap: calc(var(--gap) * 0.2);
}
.ds-menu-classic-lead {
  display: flex;
  align-items: baseline;
  gap: 8px;
}
.ds-menu-leader {
  flex: 1;
  align-self: center;
  height: 0;
  border-bottom: 1px dotted var(--color-divider);
  min-width: 12px;
}

/* Menu Highlights — Featured Dish (split → stack) */
.ds-menu-featured {
  display: flex;
  gap: var(--gap-lg);
  align-items: stretch;
}
.ds-menu-featured-media {
  flex: 1 1 0;
  min-width: 0;
  border-radius: var(--radius-lg);
  overflow: hidden;
  min-height: 280px;
  background-color: var(--color-surface-hover);
}
.ds-menu-featured-media-empty {
  width: 100%;
  height: 100%;
}
.ds-menu-featured-body {
  flex: 1 1 0;
  min-width: 0;
  display: flex;
  flex-direction: column;
  gap: calc(var(--gap) * 0.6);
  justify-content: center;
}
.ds-menu-featured-meta {
  display: flex;
  align-items: center;
  gap: var(--gap);
  flex-wrap: wrap;
}
.ds-menu-alsoloved {
  display: flex;
  flex-direction: column;
  gap: calc(var(--gap) * 0.5);
}
.ds-menu-alsoloved-row {
  display: flex;
  align-items: baseline;
  gap: 8px;
}
@media (max-width: 768px) {
  .ds-menu-featured {
    flex-direction: column;
  }
  .ds-menu-featured-media {
    min-height: 220px;
    max-height: 300px;
  }
}

/* Menu Download — Cards. Grid is the shared .ds-card-grid primitive
   (dense: --card-grid-gap var(--gap), min 260px). See Plan/22. */
.ds-menu-download-card {
  display: flex;
  flex-direction: column;
  gap: var(--gap);
  justify-content: space-between;
  padding: var(--gap-lg);
  border: 1px solid var(--color-surface-border);
  border-radius: var(--radius-lg);
  background-color: var(--color-surface);
}
.ds-cm-surface .ds-menu-download-card,
.ds-cm-primary .ds-menu-download-card,
.ds-cm-secondary .ds-menu-download-card,
.ds-cm-dark .ds-menu-download-card {
  background-color: var(--color-background);
}
.ds-menu-download-head {
  display: flex;
  align-items: center;
  gap: var(--gap);
}

/* Inline glyph + label inside a Button. */
.ds-btn-inline {
  display: inline-flex;
  align-items: center;
  gap: 8px;
}

/* Menu Download — Banner (inset primary card, row → column) */
.ds-menu-banner {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--gap-lg);
  width: 100%;
  max-width: var(--content-width);
  background-color: var(--color-primary);
  color: var(--color-primary-text);
  padding: var(--gap-lg);
  border-radius: var(--radius-lg);
}
.ds-menu-banner-copy {
  display: flex;
  flex-direction: column;
  gap: calc(var(--gap) * 0.4);
  min-width: 0;
}
.ds-menu-banner-action {
  display: flex;
  flex-direction: column;
  align-items: flex-end;
  gap: 6px;
  flex-shrink: 0;
}
.ds-menu-banner-note {
  font-size: var(--text-sm);
  opacity: 0.8;
}
@media (max-width: 768px) {
  .ds-menu-banner {
    flex-direction: column;
    align-items: flex-start;
  }
  .ds-menu-banner-action {
    align-items: flex-start;
    width: 100%;
  }
}

/* Menu Download — Preview (doc thumbnail + copy, split → stack) */
.ds-menu-preview {
  display: flex;
  gap: var(--gap-lg);
  align-items: center;
}
.ds-menu-preview-media {
  flex: 0 0 auto;
  width: 34%;
  display: flex;
  justify-content: center;
}
.ds-menu-preview-body {
  flex: 1 1 0;
  min-width: 0;
  display: flex;
  flex-direction: column;
  gap: calc(var(--gap) * 0.6);
}
.ds-menu-preview-actions {
  display: flex;
  flex-wrap: wrap;
  gap: var(--gap);
  margin-top: calc(var(--gap) * 0.4);
}
/* Stacked-paper document thumbnail — pure decoration. */
.ds-menu-doc {
  position: relative;
  width: 100%;
  max-width: 220px;
  aspect-ratio: 3 / 4;
}
.ds-menu-doc-back {
  position: absolute;
  inset: 0;
  transform: translate(14px, 14px) rotate(2deg);
  background-color: var(--color-surface);
  border: 1px solid var(--color-surface-border);
  border-radius: var(--radius-sm);
}
.ds-menu-doc-front {
  position: absolute;
  inset: 0;
  background-color: var(--color-background);
  border: 1px solid var(--color-surface-border);
  border-radius: var(--radius-sm);
  box-shadow: var(--shadow-md);
  padding: 16px 18px;
  display: flex;
  flex-direction: column;
  gap: 12px;
  justify-content: center;
}
.ds-menu-doc-row {
  display: flex;
  align-items: center;
  gap: 8px;
}
.ds-menu-doc-name {
  flex: 1;
  height: 5px;
  border-radius: 3px;
  background-color: var(--color-divider);
}
.ds-menu-doc-price {
  width: 22px;
  height: 5px;
  border-radius: 3px;
  background-color: var(--color-divider);
}
@media (max-width: 768px) {
  .ds-menu-preview {
    flex-direction: column;
    align-items: flex-start;
  }
  .ds-menu-preview-media {
    width: 100%;
    justify-content: flex-start;
  }
}

/* AboutHighlights — accent check-icon wrapper. */
.ds-about-check {
  display: inline-flex;
  color: var(--color-accent);
  margin-top: 4px;
  flex-shrink: 0;
}

/* Header — nav link typography. First/active link bumps weight + opacity.
   white-space:nowrap so nav links never wrap in a constrained header
   (notably HeaderFloating, where space is tight). */
.ds-header-nav-link {
  font-family: var(--font-body);
  font-size: var(--text-base);
  font-weight: var(--body-weight);
  color: inherit;
  opacity: 0.7;
  text-decoration: none;
  white-space: nowrap;
}
.ds-header-nav-link-active {
  font-weight: 500;
  opacity: 1;
}

/* HamburgerButton — hidden on desktop, surfaced via media query. */
.ds-hamburger {
  display: none;
  background: none;
  border: none;
  cursor: pointer;
  padding: 8px;
  align-items: center;
  justify-content: center;
}

/* LogoBrand text — token-driven defaults; LogoBrand emits inline-style
   overrides only when the customer has set brandFont/brandFontWeight/
   brandFontSize. */
.ds-logo-brand-text {
  font-family: var(--font-heading);
  font-size: var(--text-xl);
  font-weight: var(--heading-weight);
  color: inherit;
  letter-spacing: -0.02em;
  line-height: 1.2;
}

/* ────────── Section root ──────────
   Shared layout for the "stack" pattern: vertical flex column, centered
   children, full-width, section padding + gap-lg. Used by ~15 section
   variants. The colorMode bg/text colors come from .ds-cm-* below. */
.ds-section-stack {
  display: flex;
  flex-direction: column;
  align-items: center;
  padding: var(--section-py) var(--section-px);
  gap: var(--gap-lg);
  width: 100%;
}

/* ColorMode — bg + text-color slot per Plan/09. The dark variant is
   palette-derived (Plan/29): a warm ink tinted toward the brand primary with
   the palette's own light tone as text — the comps' darks are brown-black /
   deep green, never neutral #111 with cool slate text. Fallbacks keep v1
   configs (no resolveTokens emission) rendering as before. */
.ds-cm-default   { background-color: var(--color-background); color: var(--color-text); }
.ds-cm-surface   { background-color: var(--color-surface);    color: var(--color-text); }
.ds-cm-primary   { background-color: var(--color-primary);    color: var(--color-primary-text); }
.ds-cm-secondary { background-color: var(--color-secondary);  color: var(--color-secondary-text); }
.ds-cm-dark      { background-color: var(--color-dark-bg, #111111); color: var(--color-dark-text, #f1f5f9); }

/* FAQ outer list wrapper — content-width-capped flex column. */
.ds-faq-list {
  width: 100%;
  max-width: 760px;
  display: flex;
  flex-direction: column;
}

/* ────────── Hero shells ──────────
   Full-viewport section roots used by Hero variants. .ds-hero-shell is the
   base; .ds-hero-shell-centered + .ds-hero-shell-split modify alignment +
   padding for the two layout shapes. .ds-hero-shell-bleed adds 90vh +
   forced-dark inheritance for HeroFullBleed (bg image takes over). */
.ds-hero-shell {
  position: relative;
  display: flex;
  width: 100%;
  min-height: 80vh;
  overflow: hidden;
}
.ds-hero-shell-centered {
  align-items: center;
  justify-content: center;
  padding: var(--section-py) var(--section-px);
}
.ds-hero-shell-split {
  align-items: stretch;
}
.ds-hero-shell-bleed {
  align-items: center;
  justify-content: center;
  min-height: 90vh;
}

/* ────────── Botanical hero (Path B — Wildbloom) ──────────
   Content column beside a layered botanical SVG that bleeds off one edge,
   behind the content. Token-only + logical properties; the SVG's own fills /
   strokes are token vars (see BotanicalArt.tsx) so the art reskins with the
   bundle. The -right variant bleeds the art off the inline-end edge; -left mirrors it. */
.ds-botanical-hero {
  align-items: center;
  justify-content: flex-start;
  padding: var(--section-py) var(--section-px);
}
.ds-botanical-hero-content {
  position: relative;
  z-index: 2;
  display: flex;
  flex-direction: column;
  gap: var(--gap);
  inline-size: 100%;
  max-inline-size: min(36rem, var(--content-width));
}
.ds-botanical-hero-right .ds-botanical-hero-content {
  margin-inline-end: auto;
}
.ds-botanical-hero-left .ds-botanical-hero-content {
  margin-inline-start: auto;
  text-align: end;
  align-items: flex-end;
}
/* Decorative art — pinned to one edge, scaled to bleed past it, behind text. */
.ds-botanical-hero-art {
  position: absolute;
  z-index: 1;
  inset-block: 0;
  inline-size: 48%;
  min-inline-size: 22rem;
  display: flex;
  align-items: center;
  opacity: 0.92;
  pointer-events: none;
}
.ds-botanical-hero-right .ds-botanical-hero-art {
  inset-inline-end: 0;
  justify-content: flex-end;
}
.ds-botanical-hero-left .ds-botanical-hero-art {
  inset-inline-start: 0;
  justify-content: flex-start;
}
.ds-botanical-hero-art > svg {
  inline-size: auto;
  block-size: 92%;
  max-block-size: 32rem;
  /* Bleed past the section edge so the composition feels cropped, not boxed. */
  transform: translateX(8%) scale(1.08);
}
.ds-botanical-hero-left .ds-botanical-hero-art > svg {
  transform: translateX(-8%) scale(1.08) scaleX(-1);
}
.ds-botanical-hero-emphasis {
  font-style: italic;
}
.ds-botanical-hero-intro {
  max-inline-size: 32rem;
  line-height: 1.5;
}
.ds-botanical-hero-actions {
  margin-block-start: var(--gap);
}
@media (max-width: 768px) {
  /* On mobile the art steps OUT from behind the copy and becomes a bold,
     contained band above it — full strength, not a faint ghost — so the
     signature blooms still read on a phone instead of washing out behind
     text. The column stack keeps the headline fully legible (no overlap). */
  .ds-botanical-hero {
    flex-direction: column;
    align-items: stretch;
    justify-content: center;
    text-align: start;
    gap: var(--gap);
  }
  .ds-botanical-hero-art {
    position: relative;
    order: -1;
    inset: auto;
    inline-size: 100%;
    min-inline-size: 0;
    block-size: 36vh;
    min-block-size: 14rem;
    opacity: 1;
    justify-content: center;
  }
  .ds-botanical-hero-art > svg {
    block-size: 100%;
    max-block-size: none;
    transform: none;
  }
  .ds-botanical-hero-left .ds-botanical-hero-content,
  .ds-botanical-hero-right .ds-botanical-hero-content {
    margin-inline: 0;
    text-align: start;
    align-items: flex-start;
    max-inline-size: 100%;
  }
}

/* ────────── Header bar ──────────
   Fixed-height, bordered nav strip. .ds-header-bar-spread adds
   justify-content:space-between (used by HeaderStandard,
   HeaderRightAligned). HeaderCentered uses .ds-header-bar alone. */
.ds-header-bar {
  display: flex;
  align-items: center;
  width: 100%;
  height: 72px;
  padding: 0 var(--section-px);
  border-bottom: 1px solid var(--color-surface-border);
}
.ds-header-bar-spread { justify-content: space-between; }

/* HeaderFloating — sticky outer container + card-style inner header.
   The chassisStyle (surface bg for default, otherwise inherit colorMode)
   stays inline because the variant resolves it conditionally per
   colorMode; everything else moves to classes. */
.ds-header-floating-sticky {
  position: sticky;
  top: var(--gap);
  z-index: 100;
  padding: 0 var(--gap-lg);
  margin-top: var(--gap);
}
.ds-header-floating-card {
  display: flex;
  align-items: center;
  justify-content: space-between;
  width: 100%;
  max-width: var(--content-width);
  margin: 0 auto;
  padding: 12px 20px 12px 24px;
  border-radius: var(--radius-lg);
  box-shadow: var(--shadow-sm);
  border: var(--border-width) solid var(--color-divider);
}

/* ────────── Section row ──────────
   Flex-row variants (CTASplit, ContactSplit, AboutSplit). Modifiers
   layer center-justify (.ds-section-row-center) or stretch + gap
   (.ds-section-row-stretch). AboutSplit additionally sets
   flex-direction inline since its prop can flip row-reverse. */
.ds-section-row {
  display: flex;
  width: 100%;
  padding: var(--section-py) var(--section-px);
}
.ds-section-row-center { justify-content: center; }
.ds-section-row-stretch { align-items: stretch; gap: var(--gap-lg); }

/* ────────── Section stack — tight ──────────
   flex-column centered with section padding, NO gap-lg between children.
   Used by CTASimple + AboutCentered where children own their own spacing. */
.ds-section-stack-tight {
  display: flex;
  flex-direction: column;
  align-items: center;
  padding: var(--section-py) var(--section-px);
  width: 100%;
}

/* ────────── Phone CTA — sticky on mobile ──────────
   Trade pack conversion mechanic. The PhoneCta section's sticky-mobile
   variant adds .ds-phone-cta-sticky which pins the section to the
   bottom of the viewport on small screens (so the customer's phone
   button is always tappable while they scroll the page). On larger
   screens the section flows in-document like any other. */
.ds-phone-cta-sticky {
  position: sticky;
  bottom: 0;
  z-index: 40;
  background-color: var(--color-background);
  border-top: var(--border-width) solid var(--color-divider);
  box-shadow: 0 -4px 12px color-mix(in srgb, currentColor 8%, transparent);
}
@media (min-width: 769px) {
  .ds-phone-cta-sticky {
    position: relative;
    box-shadow: none;
    border-top: none;
  }
}

/* ────────── Phone CTA — banner ──────────
   Conversion band: copy left, tappable number + call button right. Wraps to a
   stacked column on narrow widths. The number rides currentColor so it stays
   legible on whatever band the bundle picks. */
.ds-phone-cta-banner {
  width: 100%;
  max-width: var(--content-width);
  margin-inline: auto;
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  justify-content: space-between;
  gap: var(--gap-lg);
}
.ds-phone-cta-banner-copy {
  display: flex;
  flex-direction: column;
  gap: calc(var(--gap) * 0.5);
  flex: 1 1 320px;
  min-width: 0;
}
.ds-phone-cta-banner-action {
  display: flex;
  flex-direction: column;
  align-items: flex-start;
  gap: var(--gap);
}
.ds-phone-cta-number {
  display: inline-flex;
  align-items: center;
  gap: calc(var(--gap) * 0.6);
  font-family: var(--font-display);
  font-weight: 800;
  font-size: clamp(26px, 3.4vw, 40px);
  line-height: 1;
  letter-spacing: -0.01em;
  color: currentColor;
  text-decoration: none;
}
.ds-phone-cta-number svg { opacity: 0.85; }

/* ────────── Form — info rail + elevated card ──────────
   Two-column: copy rail beside a white card holding the fields. Auto-fit so it
   collapses to one column on narrow widths; -solo centers a single card when
   there's no rail copy. Inputs read as recessed fields on the card. */
.ds-form-layout {
  width: 100%;
  max-width: var(--content-width);
  margin-inline: auto;
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(min(100%, 320px), 1fr));
  gap: var(--gap-lg);
  align-items: start;
}
.ds-form-layout-solo {
  grid-template-columns: minmax(0, 560px);
  justify-content: center;
}
.ds-form-rail {
  display: flex;
  flex-direction: column;
  gap: var(--gap-lg);
  padding-top: calc(var(--gap) * 0.5);
}
.ds-form-card {
  border-radius: var(--radius-lg);
  border: 1px solid var(--color-surface-border);
  padding: var(--gap-lg);
}
.ds-form-label {
  display: flex;
  flex-direction: column;
  gap: calc(var(--gap) * 0.3);
  font-size: var(--text-sm);
  font-weight: 600;
}
.ds-form-input {
  width: 100%;
  padding: 12px 14px;
  font-size: var(--text-base);
  font-family: var(--font-body);
  border: 1px solid var(--color-surface-border);
  border-radius: var(--radius-md);
  background-color: var(--color-surface);
  color: var(--color-text);
}
.ds-form-input::placeholder { color: var(--color-text-muted); opacity: 0.7; }
.ds-form-input:focus {
  outline: none;
  border-color: var(--color-primary);
  box-shadow: 0 0 0 3px color-mix(in srgb, var(--color-primary) 18%, transparent);
}

/* ────────── Service Area ──────────
   Framed map panel with a floating location pill (marker overlay), beside
   numbered city chips + a zip lookup. Defaults to a primary band. */
.ds-area-both {
  width: 100%;
  max-width: var(--content-width);
  margin-inline: auto;
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(min(100%, 320px), 1fr));
  gap: var(--gap-lg);
  align-items: center;
}
.ds-area-both-info {
  display: flex;
  flex-direction: column;
  gap: var(--gap-lg);
}
.ds-area-map {
  position: relative;
  width: 100%;
  min-height: 360px;
  border-radius: var(--radius-lg);
  overflow: hidden;
  border: 1px solid var(--color-surface-border);
}
.ds-area-map iframe {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  border: 0;
}
/* In-palette radius illustration (Plan/29) — dark panel; rings/routes draw in
   currentColor (the palette's dark-band text) so the object stays in-palette
   on every bundle. */
.ds-area-map-illustration {
  background-color: var(--color-dark-bg, #111111);
  color: var(--color-dark-text, #f1f5f9);
}
.ds-area-map-illustration > svg {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
}
.ds-area-map-pin {
  position: absolute;
  left: var(--gap);
  bottom: var(--gap);
  display: inline-flex;
  align-items: center;
  gap: 6px;
  padding: 8px 12px;
  border-radius: var(--radius-full);
  font-size: var(--text-sm);
  font-weight: 600;
  box-shadow: var(--shadow-sm);
}
.ds-area-cities {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(min(100%, 180px), 1fr));
  gap: calc(var(--gap) * 0.5) var(--gap);
  padding: 0;
  margin: 0;
  list-style: none;
}
.ds-area-city {
  display: flex;
  align-items: center;
  gap: calc(var(--gap) * 0.6);
  font-size: var(--text-base);
}
.ds-area-city-num {
  flex-shrink: 0;
  width: 28px;
  height: 28px;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  border-radius: var(--radius-full);
  font-family: var(--font-display);
  font-size: var(--text-sm);
  font-weight: 700;
  background-color: color-mix(in srgb, currentColor 14%, transparent);
  color: currentColor;
}
.ds-area-zip {
  display: flex;
  flex-wrap: wrap;
  gap: calc(var(--gap) * 0.5);
  align-items: center;
}
.ds-area-zip .ds-form-input { flex: 1 1 200px; }
.ds-area-zip-note {
  flex-basis: 100%;
  opacity: 0.7;
  font-size: var(--text-sm);
}

/* ────────── Hours — elevated card ──────────
   The day-by-day list lives in one light card so it reads on any band (hours
   defaults to a dark band). Rows divide with hairlines; the time column is
   muted + tabular for clean alignment. */
.ds-hours-card {
  width: 100%;
  max-width: 520px;
  margin-inline: auto;
  border-radius: var(--radius-lg);
  border: 1px solid var(--color-surface-border);
  padding: var(--gap-lg);
  display: flex;
  flex-direction: column;
  gap: var(--gap);
}
.ds-hours-card-head {
  display: flex;
  align-items: center;
  gap: var(--gap);
}
.ds-hours-list {
  list-style: none;
  margin: 0;
  padding: 0;
  display: flex;
  flex-direction: column;
}
.ds-hours-row {
  display: flex;
  justify-content: space-between;
  align-items: baseline;
  gap: var(--gap);
  padding: calc(var(--gap) * 0.6) 0;
  border-bottom: 1px solid var(--color-surface-border);
}
.ds-hours-row:last-child { border-bottom: none; }
.ds-hours-day { font-weight: 600; }
.ds-hours-time {
  color: var(--color-text-muted);
  font-variant-numeric: tabular-nums;
}

/* ────────── Map — full-width framed panel + floating address card ──────────
   Mirrors the service-area map: a rounded, shadowed frame with the iframe
   absolutely filling it, and a white overlay card carrying the address +
   directions link (the "marker overlay"). */
.ds-map-panel {
  position: relative;
  width: 100%;
  max-width: var(--content-width);
  margin-inline: auto;
  min-height: 460px;
  border-radius: var(--radius-lg);
  overflow: hidden;
  border: 1px solid var(--color-surface-border);
}
.ds-map-panel iframe {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  border: 0;
}
.ds-map-overlay {
  position: absolute;
  top: var(--gap-lg);
  left: var(--gap-lg);
  max-width: 320px;
  display: flex;
  flex-direction: column;
  gap: calc(var(--gap) * 0.5);
  padding: var(--gap-lg);
  border-radius: var(--radius-lg);
  box-shadow: var(--shadow-sm);
}
.ds-map-overlay-addr {
  display: flex;
  align-items: flex-start;
  gap: 8px;
  font-size: var(--text-base);
}
.ds-map-overlay-addr svg { flex-shrink: 0; margin-top: 2px; }
.ds-map-directions {
  font-size: var(--text-sm);
  font-weight: 600;
  text-decoration: none;
}
.ds-map-directions:hover { text-decoration: underline; }

/* ────────── Pricing — plan cards ──────────
   Membership/package tiers as side-by-side cards. Each card owns a colorMode
   (featured = primary fill, else white) so its text + CTA pair with the card,
   not the section band. Auto-fit collapses to one column on narrow widths. */
.ds-plan-cards {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(min(100%, 260px), 1fr));
  gap: var(--gap-lg);
  align-items: stretch;
}
.ds-plan-card {
  position: relative;
  display: flex;
  flex-direction: column;
  gap: var(--gap);
  padding: var(--gap-lg);
  border-radius: var(--radius-lg);
  border: 1px solid var(--color-surface-border);
}
.ds-plan-card-featured { border-color: transparent; }
@media (min-width: 769px) {
  .ds-plan-card-featured { transform: scale(1.04); z-index: 1; }
}
.ds-plan-card-badge {
  position: absolute;
  top: calc(var(--gap) * -0.55);
  right: var(--gap-lg);
  padding: 4px 12px;
  border-radius: var(--radius-full);
  font-size: var(--text-xs);
  font-weight: 700;
  letter-spacing: 0.04em;
  text-transform: uppercase;
  background-color: var(--color-accent);
  color: var(--color-accent-text);
  box-shadow: var(--shadow-sm);
}
.ds-plan-card-head {
  display: flex;
  flex-direction: column;
  gap: calc(var(--gap) * 0.25);
}
.ds-plan-card-name {
  font-family: var(--font-display);
  font-size: var(--text-xl);
  font-weight: 700;
}
.ds-plan-card-desc {
  font-size: var(--text-sm);
  margin: 0;
}
.ds-plan-card-price {
  display: flex;
  align-items: baseline;
  gap: 6px;
  flex-wrap: wrap;
}
.ds-plan-card-amount {
  font-family: var(--font-display);
  font-size: var(--text-4xl);
  font-weight: 800;
  line-height: 1;
}
.ds-plan-card-period { font-size: var(--text-sm); }
.ds-plan-card-features {
  list-style: none;
  margin: 0;
  padding: 0;
  display: flex;
  flex-direction: column;
  gap: calc(var(--gap) * 0.5);
  flex: 1;
}
.ds-plan-card-features li {
  display: flex;
  align-items: flex-start;
  gap: 8px;
  font-size: var(--text-base);
}
.ds-plan-card-features svg { flex-shrink: 0; margin-top: 3px; }

/* ────────── Booking widget (native) ──────────
   Interactive calendar + slot picker. Cards adopt ds-cm-default so text always
   pairs with the card. Selected day/slot use a primary fill; available days a
   subtle tint; non-bookable days are dimmed. */
.ds-booking-card {
  width: 100%;
  border-radius: var(--radius-lg);
  border: 1px solid var(--color-surface-border);
  padding: var(--gap-lg);
  display: flex;
  flex-direction: column;
  gap: var(--gap);
}
.ds-booking-service {
  display: flex;
  align-items: center;
  gap: var(--gap);
  padding-bottom: var(--gap);
  border-bottom: 1px solid var(--color-surface-border);
}
.ds-booking-service-icon {
  flex-shrink: 0;
  width: 44px;
  height: 44px;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  font-size: 22px;
  border-radius: var(--radius-full);
  background-color: color-mix(in srgb, var(--color-primary) 12%, transparent);
}
.ds-booking-service-name { font-family: var(--font-display); font-weight: 700; font-size: var(--text-lg); }
.ds-booking-service-meta { font-size: var(--text-sm); }

.ds-booking-cal-head {
  display: flex;
  align-items: center;
  justify-content: space-between;
}
.ds-booking-month { font-family: var(--font-display); font-weight: 700; letter-spacing: 0.06em; font-size: var(--text-sm); }
.ds-booking-cal-nav-group { display: flex; gap: 6px; }
.ds-booking-cal-nav {
  width: 32px;
  height: 32px;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  border-radius: var(--radius-full);
  border: 1px solid var(--color-surface-border);
  background: transparent;
  color: inherit;
  cursor: pointer;
}
.ds-booking-cal-nav:hover { background-color: color-mix(in srgb, currentColor 6%, transparent); }

.ds-booking-dow,
.ds-booking-grid {
  display: grid;
  grid-template-columns: repeat(7, 1fr);
  gap: 4px;
}
.ds-booking-dow {
  font-size: var(--text-xs);
  font-weight: 700;
  letter-spacing: 0.04em;
  color: var(--color-text-muted);
  text-align: center;
}
.ds-booking-day {
  aspect-ratio: 1;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  border: none;
  border-radius: var(--radius-md);
  background: transparent;
  color: inherit;
  font-family: var(--font-body);
  font-size: var(--text-sm);
  cursor: default;
}
.ds-booking-day-available {
  cursor: pointer;
  font-weight: 600;
  background-color: color-mix(in srgb, var(--color-primary) 9%, transparent);
}
.ds-booking-day-available:hover { background-color: color-mix(in srgb, var(--color-primary) 18%, transparent); }
.ds-booking-day-empty { color: color-mix(in srgb, currentColor 32%, transparent); }
.ds-booking-day-selected,
.ds-booking-day-selected:hover {
  background-color: var(--color-primary);
  color: var(--color-primary-text);
  font-weight: 700;
}

.ds-booking-pick { display: flex; flex-direction: column; gap: calc(var(--gap) * 0.6); }
.ds-booking-dayhead {
  font-size: var(--text-xs);
  font-weight: 700;
  letter-spacing: 0.06em;
  color: var(--color-text-muted);
}
.ds-booking-slots { display: flex; flex-wrap: wrap; gap: calc(var(--gap) * 0.5); }
.ds-booking-slot {
  padding: 9px 16px;
  border-radius: var(--radius-full);
  border: 1px solid var(--color-surface-border);
  background: transparent;
  color: inherit;
  font-family: var(--font-body);
  font-size: var(--text-sm);
  font-weight: 600;
  cursor: pointer;
}
.ds-booking-slot:hover { border-color: var(--color-primary); }
.ds-booking-slot-selected,
.ds-booking-slot-selected:hover {
  background-color: var(--color-primary);
  color: var(--color-primary-text);
  border-color: var(--color-primary);
}
.ds-booking-slot:disabled { opacity: 0.4; text-decoration: line-through; cursor: not-allowed; }
.ds-booking-reassure { font-size: var(--text-sm); text-align: center; margin: 0; }

/* Booking — request contact fields (name + email + phone), shown once a slot
   is picked. Email/phone share a row that stacks on narrow cards. */
.ds-booking-fields {
  display: flex;
  flex-direction: column;
  gap: var(--gap);
}
.ds-booking-fields-row {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(min(100%, 180px), 1fr));
  gap: var(--gap);
}
.ds-booking-req { color: var(--color-primary); }

/* SideBySide */
.ds-booking-split {
  width: 100%;
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(min(100%, 300px), 1fr));
  gap: var(--gap-lg);
  align-items: start;
}
.ds-booking-hours-card {
  border-radius: var(--radius-lg);
  border: 1px solid var(--color-surface-border);
  padding: var(--gap-lg);
  display: flex;
  flex-direction: column;
  gap: calc(var(--gap) * 0.5);
}
.ds-booking-hours-label {
  font-size: var(--text-xs);
  font-weight: 700;
  letter-spacing: 0.06em;
  color: var(--color-text-muted);
  margin-bottom: calc(var(--gap) * 0.25);
}
.ds-booking-hours-list { list-style: none; margin: 0; padding: 0; display: flex; flex-direction: column; }
.ds-booking-hours-row {
  display: flex;
  justify-content: space-between;
  gap: var(--gap);
  padding: calc(var(--gap) * 0.45) 0;
  border-bottom: 1px solid var(--color-surface-border);
  font-size: var(--text-sm);
}
.ds-booking-hours-row:last-child { border-bottom: none; }
.ds-booking-status {
  display: flex;
  align-items: center;
  gap: 8px;
  margin-top: calc(var(--gap) * 0.5);
  font-size: var(--text-sm);
  font-weight: 600;
}
/* Semantic "open" indicator — a fixed green (no success token in the palette). */
.ds-booking-status-dot {
  width: 9px;
  height: 9px;
  border-radius: var(--radius-full);
  background-color: #3aa564;
  box-shadow: 0 0 0 3px color-mix(in srgb, #3aa564 25%, transparent);
}
.ds-booking-week {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(54px, 1fr));
  gap: calc(var(--gap) * 0.5);
}
.ds-booking-weekchip {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 2px;
  padding: 10px 6px;
  border-radius: var(--radius-md);
  border: 1px solid var(--color-surface-border);
  background: transparent;
  color: inherit;
  cursor: pointer;
}
.ds-booking-weekchip:hover { border-color: var(--color-primary); }
.ds-booking-weekchip-selected,
.ds-booking-weekchip-selected:hover {
  background-color: var(--color-primary);
  color: var(--color-primary-text);
  border-color: var(--color-primary);
}
.ds-booking-weekchip-full { opacity: 0.45; cursor: not-allowed; }
.ds-booking-weekchip-dow { font-size: var(--text-xs); font-weight: 700; letter-spacing: 0.04em; }
.ds-booking-weekchip-day { font-family: var(--font-display); font-weight: 800; font-size: var(--text-xl); line-height: 1; }
.ds-booking-weekchip-slots { font-size: var(--text-xs); opacity: 0.8; }

/* Confirmed state */
.ds-booking-confirmed {
  display: flex;
  flex-direction: column;
  align-items: center;
  text-align: center;
  gap: calc(var(--gap) * 0.5);
  padding: var(--gap-lg) 0;
}
.ds-booking-confirmed-icon {
  width: 56px;
  height: 56px;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  border-radius: var(--radius-full);
  background-color: #3aa564;
  color: #ffffff;
  margin-bottom: calc(var(--gap) * 0.25);
}
.ds-booking-confirmed-title { font-family: var(--font-display); font-weight: 800; font-size: var(--text-2xl); }
.ds-booking-confirmed-sub { font-size: var(--text-base); margin: 0; max-width: 360px; }
.ds-booking-reset {
  margin-top: calc(var(--gap) * 0.5);
  background: none;
  border: none;
  font-family: var(--font-body);
  font-size: var(--text-sm);
  font-weight: 600;
  cursor: pointer;
}
.ds-booking-reset:hover { text-decoration: underline; }

/* ────────── Section stack — edge ──────────
   flex-column with vertical-only padding (no horizontal section-px).
   Used by TestimonialsCarousel so the carousel can scroll edge-to-edge
   while the section header reapplies its own horizontal padding. */
.ds-section-stack-edge {
  display: flex;
  flex-direction: column;
  align-items: center;
  padding: var(--section-py) 0;
  gap: var(--gap-lg);
  width: 100%;
}

/* ────────── Section pad ──────────
   Just section padding + width — for variants that handle their own
   internal layout (BlogIndexList centers via inner div + margin auto). */
.ds-section-pad {
  padding: var(--section-py) var(--section-px);
  width: 100%;
}

/* ────────── Footer — minimal ──────────
   Slim footer bar with reduced vertical padding (gap-lg vs section-py). */
.ds-footer-minimal-shell {
  display: flex;
  justify-content: center;
  padding: var(--gap-lg) var(--section-px);
  width: 100%;
}

/* SectionHeader — centered/left layout used by Services, Testimonials,
   FAQ, Team, Stats, Values, etc. Shared shell at /sections/_shared/. */
.ds-section-header {
  display: flex;
  flex-direction: column;
  gap: calc(var(--gap) * 0.5);
}
.ds-section-header-center { align-items: center; max-width: 600px; }
.ds-section-header-left   { align-items: flex-start; }
/* Split header — the comps' asymmetric shape: eyebrow + heading left, the
   supporting paragraph in a right column, bottom-aligned. Wraps on mobile. */
.ds-section-header-split {
  flex-direction: row;
  flex-wrap: wrap;
  align-items: flex-end;
  justify-content: space-between;
  gap: var(--gap-lg);
  width: 100%;
  max-width: var(--content-width);
}
.ds-section-header-split-main {
  flex: 1 1 340px;
  min-width: 0;
  display: flex;
  flex-direction: column;
  gap: calc(var(--gap) * 0.5);
  align-items: flex-start;
}
.ds-section-header-split-aside {
  flex: 1 1 280px;
  min-width: 0;
  max-width: 460px;
}
/* The subheading paragraph caps width inside a centered header. */
.ds-section-header-subheading { max-width: 540px; }
/* Governed signature rule (Plan/20, rationed by Plan/29): rendered by
   SectionHeader always, but shown only when the identity sets headerMark='rule'
   AND only under the hero headline — the page's title mark. Repeated under
   every section heading it became the third competing ornament. */
.ds-section-header-rule { display: none; }
[data-header-mark="rule"] .ds-hero-shell .ds-section-header-rule { display: block; }

/* FAQ answer — fixed bottom padding inside <details> body. */
.ds-faq-answer { padding: 0 0 var(--gap); }

/* ────────── Header navigation ──────────
   Each header variant emits the same set of class names — ds-nav-links for
   the desktop nav row, ds-nav-cta for the inline CTA button, ds-hamburger
   for the mobile menu toggle. Earlier each header inlined its own <style>
   block with these rules; consolidating here keeps the CSS in one place
   and removes a body-level <style> tag from the published HTML. */
@media (max-width: 768px) {
  .ds-nav-links {
    display: none !important;
  }
  .ds-nav-cta {
    display: none !important;
  }
  .ds-hamburger {
    display: flex !important;
  }
  /* HeaderCentered specifics — the centered layout's hamburger sits inside a
     wrapper that also holds the centered logo on desktop; on mobile the
     wrapper needs to push to the right edge so the toggle isn't cramped
     next to the logo. The centered-logo wrapper drops its horizontal
     padding to keep the logo flush left, matching the other variants. */
  .ds-hamburger-wrap {
    display: flex !important;
    margin-left: auto !important;
  }
  .ds-header-centered-logo {
    padding: 0 !important;
  }
}

/* ────────── Mobile responsiveness ──────────
   Two breakpoints:
     - 768px: layout collapse (split → stack, multi-column → single, bento → vertical)
     - 640px: button groups stack to full-width vertical
   See Plan/09-design-system.md governance rule 7. */

@media (max-width: 640px) {
  /* Stacked buttons stay content-width — alignment follows the variant's
     align prop so buttons line up with whatever text alignment the section
     uses. align-items handles horizontal positioning of buttons within the
     column-flex group; align-self positions the group itself within its
     flex parent.

     The !important on align-items is required: ButtonGroup's inline style
     sets align-items: center for vertical centering of buttons in a desktop
     row. On mobile we flip to flex-direction: column, where align-items
     becomes the horizontal cross-axis. Without !important, the inline
     center wins and buttons end up center-aligned regardless of the variant's
     align prop. */
  .ds-button-group {
    display: flex !important;
    flex-direction: column;
  }
  .ds-button-group-start {
    align-items: flex-start !important;
    align-self: flex-start;
  }
  .ds-button-group-center {
    align-items: center !important;
    align-self: center;
  }
  .ds-button-group-end {
    align-items: flex-end !important;
    align-self: flex-end;
  }
}

@media (max-width: 768px) {
  /* Hero — split / edge-to-edge layouts collapse to vertical stack with image below text. */
  .ds-hero-split,
  .ds-hero-edge {
    flex-direction: column;
    min-height: auto;
  }
  .ds-hero-split-text,
  .ds-hero-edge-text {
    flex: 1 1 auto;
    padding: var(--section-py) var(--section-px);
  }
  .ds-hero-split-image,
  .ds-hero-edge-image {
    flex: 0 0 auto;
    width: 100%;
    height: 56vw;
    min-height: 240px;
    max-height: 380px;
    padding: 0;
  }

  /* Bento services — all rows collapse to vertical stack on mobile. */
  .ds-bento-row {
    flex-direction: column;
    min-height: auto;
  }

  /* About — split collapses to single column, image stacks above text.
     Image-first ordering keeps the visual anchor at the top of the section
     regardless of which side the desktop layout placed the image on. */
  .ds-about-split {
    flex-direction: column !important;
  }
  .ds-about-split-image {
    order: 0;
  }
  .ds-about-split-text {
    order: 1;
  }

  /* About — banner: the story|stats row collapses to a single column; the
     vertical rule between them becomes a top rule on the stacked stats. */
  .ds-about-banner-cols {
    flex-direction: column !important;
    align-items: stretch !important;
  }
  .ds-about-banner-stats {
    border-left: none !important;
    padding-left: 0 !important;
    border-top: 1px solid color-mix(in srgb, currentColor 22%, transparent);
    padding-top: var(--gap-lg);
  }

  /* About — stacked: footer stat pairs lose their vertical rules when stacked. */
  .ds-about-stacked-stat {
    border-left: none !important;
    padding-left: 0 !important;
  }

  /* Contact — split collapses to single column, info above form. Both columns
     take the full row width so the form's input fields fill the screen edge-to-edge. */
  .ds-contact-split {
    flex-direction: column;
  }
  .ds-contact-split-info,
  .ds-contact-split-form {
    width: 100%;
  }

  /* Contact Simple — info bullets re-appear on mobile (hidden by default above). */
  .ds-contact-simple-info {
    display: flex;
    justify-content: center;
    width: 100%;
  }

  /* Footer — brand + column group stack vertically; the column group itself
     also breaks into a single stacked list. justify-content resets so the
     space-between doesn't blow a gap between the stacked groups. */
  .ds-footer-standard {
    flex-direction: column;
    justify-content: flex-start;
  }
  /* flex-basis is a HEIGHT once the container is a column — reset it so the
     brand block doesn't reserve 340px of vertical space (a big gap). */
  .ds-footer-brand {
    flex-basis: auto !important;
  }
  .ds-footer-columns {
    flex-direction: column;
    gap: var(--gap-lg);
  }

  /* Testimonials grid — collapse to single column on mobile. */
  /* FAQ two-column — collapse to single column on mobile. */
  .ds-faq-two-column {
    grid-template-columns: 1fr !important;
  }

  /* CTA split — collapses to single column on mobile. Image lands above
     text+buttons (matches AboutSplit pattern). */
  .ds-cta-split {
    flex-direction: column !important;
  }
  .ds-cta-split-image {
    order: 0;
  }
  .ds-cta-split-content {
    order: 1;
  }

  /* Team — featured stacks image above text; remaining-members row collapses
     to a single column. (Grid collapse now handled by .ds-card-grid--fixed.) */
  .ds-team-featured {
    flex-direction: column !important;
  }
  .ds-team-featured-image {
    order: 0;
  }
  .ds-team-featured-text {
    order: 1;
  }
  .ds-team-featured-rest {
    grid-template-columns: 1fr !important;
  }

  /* Gallery — masonry drops to 2 columns. (Grid collapse now handled by
     .ds-card-grid--fixed.) */
  .ds-gallery-masonry {
    column-count: 2 !important;
  }

  /* Stats — inline collapses to a 2-column grid (vertical dividers visually
     read as cell separators); grid collapses to 2 columns too. */
  .ds-stats-inline {
    display: grid !important;
    grid-template-columns: repeat(2, minmax(0, 1fr)) !important;
    gap: var(--gap-lg);
  }
  .ds-stats-inline > div hr {
    display: none;
  }


  /* Trust badges — stacked in-band cells swap the vertical rules for
     horizontal hairlines between rows (mobile comp; cells live inside
     display:contents wrappers, so rows are selected via the wrappers), and
     with-labels cards flip to a row: logo left, text right. */
  .ds-trust-inband {
    flex-direction: column !important;
    gap: 0 !important;
  }
  .ds-trust-inband .ds-divider-vertical {
    display: none;
  }
  .ds-trust-inband-cell {
    width: 100%;
    justify-content: flex-start !important;
    padding-block: calc(var(--gap) * 0.85);
  }
  .ds-trust-inband > div:first-child .ds-trust-inband-cell { padding-top: 0; }
  .ds-trust-inband > div:last-child .ds-trust-inband-cell { padding-bottom: 0; }
  .ds-trust-inband > div:not(:first-child) .ds-trust-inband-cell {
    border-top: 1px solid color-mix(in srgb, currentColor 22%, transparent);
  }
  .ds-trust-labels-card {
    flex-direction: row !important;
    align-items: center !important;
  }

  /* Image+Text — side-by-side variants stack image over copy; image-top's
     body + pull-quote columns stack too. */
  .ds-image-text-row,
  .ds-image-text-cols {
    grid-template-columns: 1fr !important;
  }

  /* Text — two-columns flows back to a single column on phones. */
  .ds-text-columns {
    column-count: 1 !important;
  }

  /* Quote — the author-photo layout stacks portrait over copy. */
  .ds-quote-photo-row {
    grid-template-columns: 1fr !important;
  }

  /* Blog index — collapse the card grid to a single column on phones. */
  .ds-blog-grid {
    grid-template-columns: 1fr !important;
  }
}

/* ─────────────────────────────────────────────────────────────
   v2 decoration axes — Tactics/03-bundles-and-onboarding.md.

   Bundles set data-image-treatment / data-bg-decoration / data-header-mark
   on <html> via applyDecorationAttrs() at runtime, or via the htmlDocument()
   helper at publish time. Rules below are no-ops for the default 'none'
   values; v1 bundles (which leave these undefined) render exactly as before.
   (Frame styles were removed 2026-07-02 — image shadow is governed by the
   Elevation token, see .ds-image-frame rule near the top of this file.)

   Why attribute selectors on :root (not CSS variables): we need to switch
   *which CSS rules apply*, not just swap a value. CSS variables can't
   drive selector matching; data attributes can.
   ───────────────────────────────────────────────────────────── */

/* ── Image treatments (filter on the img only) ───────────── */

[data-image-treatment="duotone-warm"] .ds-image-frame > img {
  filter: sepia(0.4) saturate(1.25) hue-rotate(-10deg) contrast(1.02);
}
[data-image-treatment="duotone-cool"] .ds-image-frame > img {
  filter: saturate(0.55) hue-rotate(180deg) brightness(0.97);
}
[data-image-treatment="dark-tint"] .ds-image-frame > img {
  filter: brightness(0.55) contrast(1.05);
}
[data-image-treatment="grayscale"] .ds-image-frame > img {
  filter: grayscale(1) contrast(1.05);
}
[data-image-treatment="saturate"] .ds-image-frame > img {
  filter: saturate(1.45) contrast(1.05);
}

/* ── Bg decorations (ambient pattern behind sections) ────── */

/* Apply only on light section backgrounds (default + surface) — dark/primary
   sections cover any background-image with their solid color anyway, but
   limiting reduces specificity tangles when atoms ride those colorModes. */

/* grain — fine paper-texture noise (feTurbulence baked into a tiled SVG).
   Charcoal speckle at ~5% alpha reads as texture on any light palette. */
[data-bg-decoration="grain"] .ds-cm-default,
[data-bg-decoration="grain"] .ds-cm-surface {
  background-image: url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' width='140' height='140'><filter id='g'><feTurbulence type='fractalNoise' baseFrequency='0.9' numOctaves='2' stitchTiles='stitch'/><feColorMatrix type='matrix' values='0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0.05 0'/></filter><rect width='140' height='140' filter='url(%23g)'/></svg>");
}

/* dots — quiet dot lattice; one repeating radial-gradient, text-color tinted. */
[data-bg-decoration="dots"] .ds-cm-default,
[data-bg-decoration="dots"] .ds-cm-surface {
  background-image: radial-gradient(color-mix(in srgb, var(--color-text) 8%, transparent) 1px, transparent 1.5px);
  background-size: 22px 22px;
}

/* hatch — very fine 45° pinstripes, text-color tinted. */
[data-bg-decoration="hatch"] .ds-cm-default,
[data-bg-decoration="hatch"] .ds-cm-surface {
  background-image: repeating-linear-gradient(
    45deg,
    color-mix(in srgb, var(--color-text) 5%, transparent) 0 1px,
    transparent 1px 12px
  );
}

/* grid — subtle 32px grid using two linear-gradients. */
[data-bg-decoration="grid"] .ds-cm-default,
[data-bg-decoration="grid"] .ds-cm-surface {
  background-image:
    linear-gradient(to right, color-mix(in srgb, var(--color-text) 5%, transparent) 1px, transparent 1px),
    linear-gradient(to bottom, color-mix(in srgb, var(--color-text) 5%, transparent) 1px, transparent 1px);
  background-size: 32px 32px;
}

/* mesh-gradient — soft radial blobs in opposite corners. */
[data-bg-decoration="mesh-gradient"] .ds-cm-default {
  background-image:
    radial-gradient(circle at 0% 0%, color-mix(in srgb, var(--color-primary) 14%, transparent) 0%, transparent 45%),
    radial-gradient(circle at 100% 100%, color-mix(in srgb, var(--color-secondary) 14%, transparent) 0%, transparent 45%);
}
[data-bg-decoration="mesh-gradient"] .ds-cm-surface {
  background-image:
    radial-gradient(circle at 100% 0%, color-mix(in srgb, var(--color-accent) 12%, transparent) 0%, transparent 45%),
    radial-gradient(circle at 0% 100%, color-mix(in srgb, var(--color-primary) 10%, transparent) 0%, transparent 45%);
}

/* Corner line-art (botanical-corner, contours) — inline SVGs used as MASKS on
   a ::before overlay so the ink derives from the palette (data-URL SVGs can't
   read CSS variables; a mask + background-color can). The pseudo paints above
   the band color and below all children — same layering as a background-image.
   Sections carry no other ::before, so the slot is free.
   RATIONED (Plan/29): corner motifs are authored placements, not ambient — on
   every light band (and every light card, which also carries a .ds-cm-* class)
   they read as noise. Only the hero shell and explicit .ds-deco-host opt-ins
   paint them; ambient textures (grain/dots/hatch/grid) stay page-wide. */
[data-bg-decoration="botanical-corner"] :is(.ds-cm-default, .ds-cm-surface):is(.ds-hero-shell, .ds-deco-host),
[data-bg-decoration="contours"] :is(.ds-cm-default, .ds-cm-surface):is(.ds-hero-shell, .ds-deco-host) {
  position: relative;
}
[data-bg-decoration="botanical-corner"] :is(.ds-cm-default, .ds-cm-surface):is(.ds-hero-shell, .ds-deco-host)::before,
[data-bg-decoration="contours"] :is(.ds-cm-default, .ds-cm-surface):is(.ds-hero-shell, .ds-deco-host)::before {
  content: '';
  position: absolute;
  inset: 0;
  pointer-events: none;
  background-color: color-mix(in srgb, var(--color-primary) 32%, transparent);
  -webkit-mask-repeat: no-repeat, no-repeat;
  mask-repeat: no-repeat, no-repeat;
}

/* botanical-corner — small leafy sprigs, top-right + bottom-left. */
[data-bg-decoration="botanical-corner"] :is(.ds-cm-default, .ds-cm-surface):is(.ds-hero-shell, .ds-deco-host)::before {
  -webkit-mask-image:
    url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 80 80' fill='none' stroke='%23000' stroke-width='1.2' stroke-linecap='round'><path d='M8 8 C 24 18, 38 22, 54 36'/><path d='M16 8 Q 22 14, 24 22'/><path d='M22 14 Q 30 16, 34 24'/><path d='M30 22 Q 38 24, 42 32'/></svg>"),
    url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 80 80' fill='none' stroke='%23000' stroke-width='1.2' stroke-linecap='round'><path d='M72 72 C 56 62, 42 58, 26 44'/><path d='M64 72 Q 58 66, 56 58'/><path d='M58 66 Q 50 64, 46 56'/><path d='M50 58 Q 42 56, 38 48'/></svg>");
  mask-image:
    url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 80 80' fill='none' stroke='%23000' stroke-width='1.2' stroke-linecap='round'><path d='M8 8 C 24 18, 38 22, 54 36'/><path d='M16 8 Q 22 14, 24 22'/><path d='M22 14 Q 30 16, 34 24'/><path d='M30 22 Q 38 24, 42 32'/></svg>"),
    url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 80 80' fill='none' stroke='%23000' stroke-width='1.2' stroke-linecap='round'><path d='M72 72 C 56 62, 42 58, 26 44'/><path d='M64 72 Q 58 66, 56 58'/><path d='M58 66 Q 50 64, 46 56'/><path d='M50 58 Q 42 56, 38 48'/></svg>");
  -webkit-mask-position: top 16px right 16px, bottom 16px left 16px;
  mask-position: top 16px right 16px, bottom 16px left 16px;
  -webkit-mask-size: 80px 80px, 80px 80px;
  mask-size: 80px 80px, 80px 80px;
}

/* contours — topographic lines sweeping out of the top-right + bottom-left
   corners; nested wavy quarter-rings, like elevation lines on a trail map. */
[data-bg-decoration="contours"] :is(.ds-cm-default, .ds-cm-surface):is(.ds-hero-shell, .ds-deco-host)::before {
  -webkit-mask-image:
    url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 120 120' fill='none' stroke='%23000' stroke-width='1.2' stroke-linecap='round'><path d='M120 12 C 96 14, 82 30, 80 54 C 78 80, 62 94, 38 100 C 26 103, 16 110, 12 120'/><path d='M120 32 C 102 34, 92 46, 90 62 C 88 82, 74 92, 58 96 C 46 99, 38 106, 35 120'/><path d='M120 52 C 108 54, 101 62, 99 72 C 97 86, 87 92, 76 95 C 66 98, 60 108, 58 120'/><path d='M120 72 C 113 74, 109 79, 107 86 C 105 94, 98 98, 90 101 C 84 103, 80 110, 79 120'/></svg>"),
    url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 120 120' fill='none' stroke='%23000' stroke-width='1.2' stroke-linecap='round'><path d='M0 108 C 24 106, 38 90, 40 66 C 42 40, 58 26, 82 20 C 94 17, 104 10, 108 0'/><path d='M0 88 C 18 86, 28 74, 30 58 C 32 38, 46 28, 62 24 C 74 21, 82 14, 85 0'/><path d='M0 68 C 12 66, 19 58, 21 48 C 23 34, 33 28, 44 25 C 54 22, 60 12, 62 0'/><path d='M0 48 C 7 46, 11 41, 13 34 C 15 26, 22 22, 30 19 C 36 17, 40 10, 41 0'/></svg>");
  mask-image:
    url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 120 120' fill='none' stroke='%23000' stroke-width='1.2' stroke-linecap='round'><path d='M120 12 C 96 14, 82 30, 80 54 C 78 80, 62 94, 38 100 C 26 103, 16 110, 12 120'/><path d='M120 32 C 102 34, 92 46, 90 62 C 88 82, 74 92, 58 96 C 46 99, 38 106, 35 120'/><path d='M120 52 C 108 54, 101 62, 99 72 C 97 86, 87 92, 76 95 C 66 98, 60 108, 58 120'/><path d='M120 72 C 113 74, 109 79, 107 86 C 105 94, 98 98, 90 101 C 84 103, 80 110, 79 120'/></svg>"),
    url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 120 120' fill='none' stroke='%23000' stroke-width='1.2' stroke-linecap='round'><path d='M0 108 C 24 106, 38 90, 40 66 C 42 40, 58 26, 82 20 C 94 17, 104 10, 108 0'/><path d='M0 88 C 18 86, 28 74, 30 58 C 32 38, 46 28, 62 24 C 74 21, 82 14, 85 0'/><path d='M0 68 C 12 66, 19 58, 21 48 C 23 34, 33 28, 44 25 C 54 22, 60 12, 62 0'/><path d='M0 48 C 7 46, 11 41, 13 34 C 15 26, 22 22, 30 19 C 36 17, 40 10, 41 0'/></svg>");
  -webkit-mask-position: top 0 right 0, bottom 0 left 0;
  mask-position: top 0 right 0, bottom 0 left 0;
  -webkit-mask-size: 140px 140px, 140px 140px;
  mask-size: 140px 140px, 140px 140px;
}

/* ── Motifs ──────────────────────────────────────────────────
   The Sketch comps carry NO leading glyph on the eyebrow — the eyebrow is
   simply accent-coloured, letter-spaced, uppercase text. The earlier
   scribble / botanical / geometric ::before/::after glyphs were a code
   invention that read as "a strange little icon," so they're removed to match
   Sketch. The motif axis is parked (inert) until it's repurposed to the real
   Sketch signature — a short accent rule under section headings. */

/* ─────────────────────────────────────────────────────────────
   v2 expressive variants — Tactics/03.

   Variant-internal helpers for HeroAsymmetric and ServicesBentoShifted.
   Most variant styling stays inline (per existing conventions); these are
   the bits that need media queries or descendant selectors and don't fit
   inline-style.
   ───────────────────────────────────────────────────────────── */

/* HeroAsymmetric — collapses to a clean vertical stack on mobile. The
   inline grid above stops being useful below 768px because the headline
   needs to land on top, the CTA card directly below, and the image
   below that. */
@media (max-width: 768px) {
  .ds-hero-asymmetric {
    display: flex !important;
    flex-direction: column !important;
    min-height: auto !important;
    gap: var(--gap-lg) !important;
  }
  .ds-hero-asymmetric-headline,
  .ds-hero-asymmetric-cta,
  .ds-hero-asymmetric-image {
    grid-column: auto !important;
    grid-row: auto !important;
    margin-right: 0 !important;
    margin-left: 0 !important;
    max-width: none !important;
    min-height: auto !important;
  }
  .ds-hero-asymmetric-image {
    height: 56vw;
    min-height: 240px;
    max-height: 380px;
  }
  /* Drop the asymmetric overflow on mobile — it would push past the screen. */
  .ds-hero-asymmetric .ds-h-1 {
    margin-right: 0 !important;
  }
}

/* ServicesBentoShifted — alternate cards translate + rotate slightly so the
   bento reads as deliberately off-kilter. We target the [data-service-index]
   attribute and pair indices into even/odd via attribute-suffix selectors so
   one rule covers cards 0/2/…/10 and another covers 1/3/…/11. */
.ds-bento-shifted-wrap [data-service-index] {
  transition: transform 200ms ease;
}
.ds-bento-shifted-wrap [data-service-index$="0"],
.ds-bento-shifted-wrap [data-service-index$="2"],
.ds-bento-shifted-wrap [data-service-index$="4"],
.ds-bento-shifted-wrap [data-service-index$="6"],
.ds-bento-shifted-wrap [data-service-index$="8"] {
  transform: rotate(-1.2deg) translateY(-6px);
}
.ds-bento-shifted-wrap [data-service-index$="1"],
.ds-bento-shifted-wrap [data-service-index$="3"],
.ds-bento-shifted-wrap [data-service-index$="5"],
.ds-bento-shifted-wrap [data-service-index$="7"],
.ds-bento-shifted-wrap [data-service-index$="9"] {
  transform: rotate(1.2deg) translateY(6px);
}
/* Hover lifts the rotation off briefly so cards stay readable on focus. */
.ds-bento-shifted-wrap [data-service-index]:hover {
  transform: translateY(-2px) !important;
}
/* On mobile the shift compresses since cards are stacked single-column
   and rotation looks awkward at full bleed. */
@media (max-width: 768px) {
  .ds-bento-shifted-wrap [data-service-index] {
    transform: none !important;
  }
}

/* TestimonialsLargeQuote — the rest grid collapses to single column on mobile. */
@media (max-width: 768px) {
  .ds-testimonials-large-quote-rest {
    grid-template-columns: 1fr !important;
  }
}
