/* ============================================================
   Velia component styles.
   Consumes only the tokens from snippets/v-tokens.liquid.
   Reskin path: change token values, never these rules.
   Dawn sets html font-size to 62.5%, so 1rem = 10px throughout.
   Mobile first: the buyer is a salon owner on a phone.
   ============================================================ */

@media (min-width: 750px) {
  :root {
    --v-page-pad: 4rem;
  }
}

/* Section shell.

   Vertical rhythm comes from --v-section-pad, which velia.css raises here
   rather than in the token file because the value is breakpoint-dependent
   and tokens carry no media queries. The mockup runs 72/110px; 110 is off
   the 4px scale, so the desktop step lands on 112.

   The hairline stays. The v3 mockup separates sections by changing ground
   instead, and on the landing that is what happens at every boundary, where
   hair against sand or pearl measures about 1.02:1 and is invisible. But the
   inner pages still run paper against paper (catalogo, come-funziona and
   professionisti all do), and there the hairline is the only thing dividing
   two sections. Removing it globally to match a page that never needs it
   would flatten three pages that do. Where the mockup draws its dashed
   die-cut rule instead, v-die-rule suppresses this border explicitly. */
.v-section {
  background: var(--v-paper);
  color: var(--v-ink);
  padding-block: var(--v-section-pad);
  border-top: 1px solid var(--v-line);
  /* Both are for the ornament: the register marks position against the
     section, and the ghost archetype is bled off the left edge and has to be
     clipped there rather than widening the page. */
  position: relative;
  overflow: hidden;
}
@media (min-width: 750px) {
  .v-section {
    --v-section-pad: 11.2rem;
  }
}
/* Tight rhythm: the V5 density for collapsed and data-led sections. Half
   the vertical padding of a full section, so a page of accordions reads as
   one document rather than eleven rooms. Auto-applied by the sections whose
   V5 collapse flag is on, opt-in elsewhere. */
.v-section--tight {
  --v-section-pad: 4.8rem;
}
@media (min-width: 750px) {
  .v-section--tight {
    --v-section-pad: 4.8rem;
  }
}
.v-section--hero {
  border-top: 0;
  padding-block: var(--v-space-7) var(--v-space-8);
}
@media (min-width: 750px) {
  .v-section--hero {
    /* V5 density: space-8 rather than space-9, so the numbers strip starts
       inside the first viewport at 800px, which is where the 5-second test
       is decided. */
    padding-block: var(--v-space-8);
  }
}

/* Section grounds: the editorial rhythm the token merge could not provide.
   Every section carries a "ground" setting (paper by default) so Giacomo can
   re-order the rhythm in the customizer without touching code. Tinted and
   ink grounds swap --v-ink-soft for their contrast-safe variant, because the
   grey that clears AA on paper fails on pearl: reasoning and the measured
   ratios in v-tokens.liquid. The swap cascades into the ruled-grid cells,
   where the darker grey lands on paper and only gains contrast. */
.v-section--wash {
  background: var(--v-wash);
  --v-ink-soft: var(--v-ink-soft-tinted);
  --v-focus: var(--v-focus-tinted);
}
.v-section--sand {
  background: var(--v-sand);
  --v-ink-soft: var(--v-ink-soft-tinted);
  --v-focus: var(--v-focus-tinted);
}
/* The ghost's wash hover-fill matches a tinted ground exactly, so hovering
   read as nothing (17/08 review finding). Paper is a visible state on both. */
.v-section--wash .v-btn--ghost:hover,
.v-section--sand .v-btn--ghost:hover {
  background: var(--v-paper);
}
.v-section--ink {
  background: var(--v-ink);
  color: var(--v-paper);
  border-top: 0;
  --v-ink-soft: var(--v-ink-soft-inverse);
}
.v-section--ink .v-heading {
  color: var(--v-paper);
}
/* Buttons invert on the ink band. Hover keeps the sage fill with ink text,
   5.31:1, same reasoning as the base hover below. The ghost keeps a visible
   paper border because a borderless ghost disappears against ink. */
.v-section--ink .v-btn {
  background: var(--v-paper);
  color: var(--v-ink);
  border-color: var(--v-paper);
}
.v-section--ink .v-btn--ghost {
  background: transparent;
  color: var(--v-paper);
  border-color: var(--v-paper);
}
.v-section--ink .v-btn:hover,
.v-section--ink .v-btn--ghost:hover {
  background: var(--v-accent);
  border-color: var(--v-accent);
  color: var(--v-ink);
}
.v-wrap {
  max-width: var(--v-page-max);
  margin-inline: auto;
  padding-inline: var(--v-page-pad);
}

/* ============================================================
   Page furniture and motion, shared by the v3 landing sections.
   ============================================================ */

/* Die-cut rule: the dashed mark the mockup sets between the hero and the
   numbers, and between the categories and production. It is its own section
   so it can be re-ordered in the customizer, exactly as the mockup has it as
   a sibling <hr> rather than a section border.

   Purely ornamental, aria-hidden, and at 32% ink it measures 2.07:1, which
   1.4.11 exempts as decoration. It carries no information: the ground change
   underneath it is what actually separates the two sections. */
.v-die-rule {
  border: 0;
  border-top: 1.5px dashed color-mix(in srgb, var(--v-ink) 32%, transparent);
  max-width: var(--v-page-max);
  margin-inline: auto;
  width: calc(100% - var(--v-page-pad) * 2);
}
/* Where the dashed rule lands, the following section's hairline would sit
   1px under it and read as a printing error. The section wrapper carries
   .v-die-wrap from the schema's "class" key, so the two are siblings in the
   DOM and this reaches across. */
.v-die-rule + .v-section {
  border-top: 0;
}

/* ============================================================
   Nav. Sticky, paper at 94% so the page shows through faintly, with the
   hairline appearing only once the page has moved. Anchors collapse into a
   disclosure below 900px, which is the mockup's breakpoint rather than
   Dawn's 750/990: the four labels plus the CTA are what stops fitting, and
   they stop fitting there.
   ============================================================ */
.v-nav {
  position: sticky;
  top: 0;
  z-index: 50;
  background: color-mix(in srgb, var(--v-paper) 94%, transparent);
  border-bottom: 1px solid transparent;
  transition: border-color 0.3s var(--v-ease);
}
.v-nav.is-scrolled,
.v-nav.is-open {
  border-bottom-color: var(--v-line);
}
.v-nav__bar {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--v-space-4);
  height: var(--v-nav-h);
}
.v-nav__brand {
  color: var(--v-ink);
  text-decoration: none;
}
.v-nav__links {
  display: flex;
  align-items: center;
  gap: var(--v-space-6);
}
.v-nav__link {
  display: none;
  font-family: var(--v-font-system);
  font-size: 1.45rem;
  font-weight: 500;
  color: var(--v-ink);
  text-decoration: none;
  padding-block: var(--v-space-1);
  border-bottom: 2px solid transparent;
  transition: border-color 0.3s var(--v-ease);
}
.v-nav__link:hover {
  /* Sage on paper is 3.41:1, over the 3:1 a non-text indicator needs. The
     underline is not the only signal either: these are links in a nav. */
  border-bottom-color: var(--v-accent);
}
/* Scoped through .v-nav deliberately, do not "simplify" to a bare class.
   .v-btn also sets display, it is declared 400 lines further down this file,
   and at equal specificity source order wins: a bare .v-nav__cta could not
   hide the button and the CTA stayed in the bar at every width. Same trap,
   and the same fix, as the Dawn header-icon rule at the bottom of this file. */
.v-nav .v-nav__cta {
  display: none;
  padding: 1.2rem 2.2rem;
  font-size: 1.4rem;
  white-space: nowrap;
}
/* The CTA outlives the anchors on the way down: it is the conversion path,
   so it stays in the bar until the bar itself runs out of room. Below that
   the disclosure carries it. */
@media (min-width: 431px) {
  .v-nav .v-nav__cta {
    display: inline-flex;
  }
}
@media (min-width: 900px) {
  .v-nav__link {
    display: inline-block;
  }
  .v-nav__toggle {
    display: none;
  }
}

.v-nav__toggle {
  position: relative;
  width: 4.4rem;
  height: 4.4rem;
  flex: 0 0 auto;
  appearance: none;
  background: none;
  border: 1.5px solid var(--v-ink);
  border-radius: var(--v-radius);
  cursor: pointer;
}
.v-nav__toggle:focus-visible {
  outline: 2px solid var(--v-focus);
  outline-offset: 2px;
}
/* Two bars that cross into a close glyph. Drawn on one span plus its
   ::before so the element itself is not required to carry a background. */
.v-nav__bars::before,
.v-nav__bars::after {
  content: '';
  position: absolute;
  left: 1.2rem;
  right: 1.2rem;
  height: 2px;
  background: var(--v-ink);
  transition: transform 0.3s var(--v-ease), top 0.3s var(--v-ease);
}
.v-nav__bars::before {
  top: 1.6rem;
}
.v-nav__bars::after {
  top: 2.4rem;
}
.v-nav.is-open .v-nav__bars::before {
  top: 2rem;
  transform: rotate(45deg);
}
.v-nav.is-open .v-nav__bars::after {
  top: 2rem;
  transform: rotate(-45deg);
}

/* display:none rather than a visual hide, so the collapsed links leave the
   accessibility tree and the tab order together. */
.v-nav__menu {
  display: none;
  border-top: 1px solid var(--v-line);
  background: var(--v-paper);
  padding-block: var(--v-space-2) var(--v-space-5);
}
.v-nav.is-open .v-nav__menu {
  display: block;
}
.v-nav__menu a {
  display: block;
  padding-block: var(--v-space-3);
  font-size: var(--v-text-base);
  font-weight: 500;
  color: var(--v-ink);
  text-decoration: none;
  border-bottom: 1px solid var(--v-line);
}
.v-nav__menu a.v-btn {
  display: inline-flex;
  margin-top: var(--v-space-3);
  color: var(--v-paper);
  border-bottom: 1px solid var(--v-ink);
}
.v-nav__menu a.v-btn:hover {
  color: var(--v-ink);
  border-bottom-color: var(--v-accent);
}

/* Anchor targets clear the sticky bar. The mockup does this with a blanket
   [id] rule; scoped to the Velia sections here so it does not reach into
   Dawn's markup. */
.v-section[id] {
  scroll-margin-top: calc(var(--v-nav-h) + var(--v-space-4));
}

/* Label lockup, shared by the nav and the footer. The construction is the
   brand book's and matches what Dawn's header carried before this branch:
   a tracked wordmark in a rounded frame, with a peeled corner clipped out of
   the bottom right. Colour comes from the context by inheritance and the
   frame follows via currentColor, so it survives an ink ground and any
   colour scheme, which is why the header rule was moved off --v-ink on main.

   A wordmark must never wrap: it broke as VELI/A on first render (Day 5). */
/* The lockup. V6 retires the CSS construction that used to live here, a
   bordered box plus a tracked span plus a clip-path corner, and renders the
   supplied artwork instead: the pack's first fixed rule is that only the
   logo-*.svg files may stand for the mark and that it is never rebuilt in
   CSS. What is left is sizing.

   Height rather than width, because the mark has to sit on the same optical
   line as the nav's text and the footer's suffix whatever the artboard is.
   The intrinsic 680x300 is on the img element, so the box is reserved before
   the file arrives and the bar does not jump. */
.v-lockup {
  display: block;
  width: auto;
}
.v-lockup--nav {
  height: 3.4rem;
}
.v-lockup--small {
  height: 2.8rem;
}

/* Crop marks: the four register marks in the hero and the closing band.
   Decorative, aria-hidden, and deliberately faint (28% ink on paper, 34%
   paper on ink). Same 1.4.11 exemption as the die rule. */
.v-marks > i {
  position: absolute;
  width: 1.6rem;
  height: 1.6rem;
  pointer-events: none;
}
.v-marks > i::before,
.v-marks > i::after {
  content: '';
  position: absolute;
  top: 0;
  left: 0;
  background: currentColor;
}
.v-marks > i::before {
  width: 1.6rem;
  height: 1px;
}
.v-marks > i::after {
  width: 1px;
  height: 1.6rem;
}
.v-marks .v-mark--tl { top: 2.2rem; left: 2.2rem; }
.v-marks .v-mark--tr { top: 2.2rem; right: 2.2rem; transform: scaleX(-1); }
.v-marks .v-mark--bl { bottom: 2.2rem; left: 2.2rem; transform: scaleY(-1); }
.v-marks .v-mark--br { bottom: 2.2rem; right: 2.2rem; transform: scale(-1); }
/* The tone is set on the marks, NOT on the section.

   The mockup puts it on the section, where the marks read currentColor off
   their parent. That also hands the faded colour to every descendant that
   does not declare its own, and on both sections that carry marks the only
   such descendant is the claim. Measured in the deployed mockup: the hero
   claim renders rgba(22,22,22,.28), which is 1.86:1, and the closing claim
   renders rgba(255,255,255,.34), which is 3.10:1. Those are the two largest
   and most important lines on the page, and both fail AA text contrast.

   Confining the colour to the marks fixes both and changes nothing else:
   the marks look identical, and the claims inherit ink and paper from the
   section as every other section's text already does. */
.v-marks--ink > i { color: color-mix(in srgb, var(--v-ink) 28%, transparent); }
.v-marks--paper > i { color: color-mix(in srgb, var(--v-paper) 34%, transparent); }

/* Ghost archetype: one oversized packaging outline bled off the left edge,
   at 3.5% opacity. One per page, per the mockup's own note. Decorative. */
.v-ghost {
  position: absolute;
  pointer-events: none;
  opacity: 0.035;
  width: min(52rem, 52vw);
  left: -14%;
  top: 50%;
  transform: translateY(-50%);
}
.v-ghost svg {
  width: 100%;
  height: auto;
  stroke: var(--v-ink);
  fill: none;
  stroke-linejoin: round;
  stroke-linecap: round;
}

/* Intro peel overlay. NOT .v-intro: that class already exists in this file
   as the section intro paragraph, and giving the overlay the same name
   turned every <p class="v-intro"> on the site into a fixed, full-viewport
   pearl sheet covering the page. Above the sticky nav's z-index 50, because it covers
   the whole page including the bar. It lifts upward and fades, which is the
   sheet being drawn off rather than dissolving. */
.v-intro-peel {
  position: fixed;
  inset: 0;
  z-index: 100;
  display: flex;
  align-items: center;
  justify-content: center;
  background: var(--v-wash);
  transition: transform 0.8s var(--v-ease) 0.1s, opacity 0.6s ease 0.2s;
}
.v-intro-peel.is-done {
  transform: translateY(-100%);
  opacity: 0;
  pointer-events: none;
}
#v-intro-canvas {
  width: min(34rem, 78vw);
  height: auto;
}
@media (prefers-reduced-motion: reduce) {
  .v-intro-peel {
    transition: opacity 0.3s ease;
  }
  .v-intro-peel.is-done {
    transform: none;
  }
}

/* ============================================================
   Accordion. The V5 expansion level: title plus one preview line on the
   surface, the rest one click down. Native <details>, no JavaScript.

   interpolate-size is what makes block-size:auto animatable at all; it is an
   inherited opt-in, so declaring it on the accordion enables it for this
   subtree only rather than for the whole document.

   Why ::details-content and not the grid-template-rows trick: reasoning and
   the measurement are in snippets/v-accordion.liquid. Short version, the
   grid trick keeps collapsed content in the accessibility tree.
   ============================================================ */
.v-acc {
  interpolate-size: allow-keywords;
  border-top: 1px solid var(--v-ink);
}
.v-acc__summary {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: var(--v-space-5);
  padding-block: var(--v-space-5);
  cursor: pointer;
  /* Both are needed to drop the UA disclosure triangle across engines. */
  list-style: none;
}
.v-acc__summary::-webkit-details-marker {
  display: none;
}
.v-acc__summary:focus-visible {
  outline: 2px solid var(--v-focus);
  outline-offset: 3px;
}
.v-acc__head {
  display: flex;
  flex-wrap: wrap;
  align-items: baseline;
  gap: var(--v-space-2) var(--v-space-4);
  min-width: 0;
}
.v-acc__title {
  font-family: var(--v-font-system);
  font-weight: 600;
  font-size: var(--v-text-l);
  line-height: 1.2;
  margin: 0;
}
.v-acc__preview {
  font-size: var(--v-text-s);
  line-height: 1.5;
  color: var(--v-ink-soft);
}

/* The sign is drawn rather than typed so it can morph. Thin strokes to sit
   in the same family as the weight-200 plus on the "su richiesta" tile,
   which is where this mark already exists in the language. */
.v-acc__sign {
  position: relative;
  flex: 0 0 auto;
  width: 1.8rem;
  height: 1.8rem;
  align-self: center;
}
.v-acc__sign::before,
.v-acc__sign::after {
  content: '';
  position: absolute;
  background: var(--v-ink);
  transition: transform 0.35s var(--v-ease);
}
.v-acc__sign::before {
  top: 50%;
  left: 0;
  right: 0;
  height: 1.5px;
  transform: translateY(-50%);
}
.v-acc__sign::after {
  left: 50%;
  top: 0;
  bottom: 0;
  width: 1.5px;
  transform: translateX(-50%);
}
.v-acc[open] .v-acc__sign::after {
  transform: translateX(-50%) scaleY(0);
}

.v-acc__body {
  padding-bottom: var(--v-space-6);
}
.v-acc__body > * + * {
  margin-top: var(--v-space-4);
}
/* The measure sits on the prose, not the container, so a ruled grid or a
   product strip inside an accordion can span the full width. */
.v-acc__body p {
  font-size: var(--v-text-base);
  line-height: 1.65;
  color: var(--v-ink-soft);
  max-width: 68ch;
}
.v-acc__body b,
.v-acc__body strong {
  color: var(--v-ink);
  font-weight: 600;
}

@supports selector(::details-content) {
  .v-acc::details-content {
    block-size: 0;
    overflow: hidden;
    transition: block-size 0.35s var(--v-ease), content-visibility 0.35s allow-discrete;
  }
  .v-acc[open]::details-content {
    block-size: auto;
  }
}

@media (prefers-reduced-motion: reduce) {
  .v-acc::details-content,
  .v-acc__sign::before,
  .v-acc__sign::after {
    transition: none;
  }
}

/* ============================================================
   Glossary term and its popover. The V5 glossary level.

   The trigger is a real <button> because it is a real control. Dashed
   underline because the dash is already this layer's mark for "this is
   customisable / this opens", the same language as the die-cut rule and the
   "su richiesta" panel. No pill, no coloured circle.

   The panel uses the native Popover API, so Esc, outside click, focus return
   and top-layer painting come from the platform. velia.js only positions it.
   ============================================================ */
.v-term {
  white-space: nowrap;
}
.v-term__btn {
  appearance: none;
  background: none;
  border: 0;
  padding: 0;
  margin: 0;
  font: inherit;
  color: inherit;
  cursor: pointer;
  white-space: normal;
}
.v-term__word {
  border-bottom: 1px dashed currentColor;
  padding-bottom: 0.1em;
}
/* The question mark is a small mono square, not an icon. Sized in em so it
   tracks whatever type size the term is sitting in. */
.v-term__mark {
  display: inline-block;
  margin-left: 0.35em;
  width: 1.35em;
  height: 1.35em;
  line-height: 1.3em;
  text-align: center;
  vertical-align: 0.05em;
  font-family: var(--v-font-mono);
  font-size: 0.72em;
  border: 1px solid currentColor;
  border-radius: var(--v-radius);
  opacity: 0.75;
}
.v-term__btn:hover .v-term__mark,
.v-term__btn:focus-visible .v-term__mark {
  opacity: 1;
}
.v-term__btn:focus-visible {
  outline: 2px solid var(--v-focus);
  outline-offset: 3px;
}

.v-term__pop {
  /* Reset the UA popover box, then rebuild it in the layer's own language. */
  position: fixed;
  inset: auto;
  margin: 0;
  max-width: 46ch;
  width: max-content;
  padding: var(--v-space-4) var(--v-space-5);
  background: var(--v-paper);
  color: var(--v-ink);
  border: 1px solid var(--v-ink);
  border-radius: var(--v-radius);
  box-shadow: 0 1.2rem 3.2rem color-mix(in srgb, var(--v-ink) 12%, transparent);
  white-space: normal;
}
.v-term__pop:not(:popover-open) {
  display: none;
}
.v-term__title {
  display: block;
  font-family: var(--v-font-mono);
  font-size: var(--v-text-xs);
  letter-spacing: 0.1em;
  /* No text-transform: CPNP and PIF are authored uppercase already, and
     "Persona Responsabile" is a phrase, not a code. */
  color: var(--v-ink-soft);
  margin-bottom: var(--v-space-2);
}
.v-term__text {
  display: block;
  font-size: var(--v-text-s);
  line-height: 1.6;
}
.v-term__who {
  display: block;
  margin-top: var(--v-space-3);
  padding-top: var(--v-space-3);
  border-top: 1px solid var(--v-line);
  font-size: var(--v-text-s);
  font-weight: 600;
}

/* Mobile: the brief asks for an inline expansion under the line rather than
   a floating panel. It stays a popover, so the behaviour is identical, but
   it spans the viewport under the term instead of pointing at it. */
@media (max-width: 749px) {
  .v-term__pop {
    left: var(--v-page-pad);
    right: var(--v-page-pad);
    width: auto;
    max-width: none;
  }
}

/* Scroll reveal. velia.js adds .is-in as each element enters the viewport.
   The hidden state is only safe because theme.liquid carries a <noscript>
   override and velia.js fails open: if the script never runs but scripting
   IS enabled, the 5s timer in it is what un-hides the page. */
.v-rv {
  opacity: 0;
  transform: translateY(var(--v-dy));
  transition: opacity var(--v-t-in) var(--v-ease), transform var(--v-t-in) var(--v-ease);
  transition-delay: var(--v-delay, 0ms);
}
.v-rv.is-in {
  opacity: 1;
  transform: none;
}
@media (prefers-reduced-motion: reduce) {
  .v-rv {
    opacity: 1;
    transform: none;
    transition: none;
  }
}

/* Typography */
/* The kicker is a label, so it is set in the system face. The V6 pack's
   rule reserves mono for data (numbers, sigle, lotto, URL), never for
   kickers, labels or titles, and the prototype prints every section label
   as a quiet grey line in the body face (its .grey at 14px). The mono
   uppercase treatment below dated from V5 and contradicted both. */
.v-kicker {
  font-family: var(--v-font-system);
  font-size: var(--v-text-s);
  color: var(--v-ink-soft);
  margin: 0 0 var(--v-space-2);
}
.v-heading {
  font-family: var(--v-font-system);
  font-weight: 600;
  font-size: var(--v-text-2xl);
  line-height: 1.12;
  letter-spacing: -0.01em;
  color: var(--v-ink);
  margin: 0 0 var(--v-space-4);
  text-wrap: balance;
}
.v-intro {
  font-family: var(--v-font-system);
  font-size: var(--v-text-l);
  line-height: 1.5;
  color: var(--v-ink-soft);
  max-width: 62ch;
  margin: 0 0 var(--v-space-6);
}
.v-footnote {
  /* System, not mono: a footnote is a sentence, and the V5 rule confines
     mono to data. The audit caught this shipping the compliance DISCLOSURE
     in code voice, which read as decoration exactly where it must read as
     a statement. */
  font-family: var(--v-font-system);
  font-size: var(--v-text-s);
  color: var(--v-ink-soft);
  max-width: 68ch;
  margin: var(--v-space-5) 0 0;
}

/* Editorial heading: the brand book's Cormorant, confined to claims and
   closing moments per Giacomo's own schema note on v_font_serif. The library
   cut is cormorant_i6, italic 600, so both properties are declared to match
   the loaded file rather than synthesize. */
.v-heading--editorial {
  font-family: var(--v-font-serif);
  font-style: italic;
  font-weight: 600;
  font-size: clamp(3rem, 5vw, 4.6rem);
  letter-spacing: 0;
  line-height: 1.15;
  max-width: 24ch;
}

/* Hero */
.v-hero__title {
  font-family: var(--v-font-system);
  font-weight: 650;
  font-size: var(--v-text-hero);
  line-height: 1.04;
  letter-spacing: -0.02em;
  margin: 0;
  max-width: 16ch;
  text-wrap: balance;
}
/* The claim variant: Cormorant italic per the approved landing direction.
   Slightly larger than the system hero because the serif runs smaller on
   the same em box. Tracking resets to zero: italics do not letterspace. */
/* No measure limit: the claim's break is authored in the setting and
   rendered through newline_to_br, so a ch-based max-width can only fight it.
   The mockup sets none either. */
.v-hero__title--editorial {
  max-width: none;
  font-family: var(--v-font-serif);
  font-style: italic;
  font-weight: 600;
  /* v3 sizing, 52 to 86px. The first slice shipped 42 to 78px, which was a
     guess made before the mockup was the reference. */
  font-size: clamp(5.2rem, 7vw, 8.6rem);
  letter-spacing: 0;
  line-height: 1.02;
}
.v-hero__grid {
  display: grid;
  gap: var(--v-space-7);
  align-items: center;
}
@media (min-width: 900px) {
  .v-hero__grid {
    grid-template-columns: 1.08fr 0.92fr;
    gap: var(--v-space-8);
  }
}
.v-hero__sub {
  font-size: var(--v-text-l);
  line-height: 1.55;
  color: var(--v-ink-soft);
  max-width: 50ch;
  margin: var(--v-space-5) 0 0;
}
/* The opening clause carries the promise, so it steps up to ink. Weight
   alone would not be enough separation against the grey. */
.v-hero__sub b {
  color: var(--v-ink);
  font-weight: 600;
}
.v-hero__proof {
  font-size: var(--v-text-s);
  color: var(--v-ink-soft);
  margin: var(--v-space-5) 0 0;
}
.v-hero__panel {
  border-radius: var(--v-radius-panel);
  overflow: hidden;
}
/* height:auto is load-bearing, not tidiness. v-figure emits width and height
   attributes so the box is reserved before the image arrives, and those
   attributes map to presentational hints: the height hint pins the element
   at its intrinsic pixel height and aspect-ratio never gets a say. Without
   this line every panel on the landing rendered at full 1125px height. */
.v-hero__panel img {
  width: 100%;
  height: auto;
  object-fit: cover;
}
@media (min-width: 900px) {
  .v-hero__panel img {
    aspect-ratio: 4 / 4.6;
  }
}
@media (max-width: 899px) {
  .v-hero__panel img {
    max-height: 60vh;
  }
}
.v-hero__actions {
  display: flex;
  flex-wrap: wrap;
  gap: var(--v-space-3);
  margin-top: var(--v-space-7);
}

/* Buttons */
.v-btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: 0.8rem;
  padding: 1.4rem 2.4rem;
  background: var(--v-ink);
  color: var(--v-paper);
  border: 1px solid var(--v-ink);
  border-radius: var(--v-radius);
  font-family: var(--v-font-system);
  font-size: var(--v-text-base);
  font-weight: 600;
  line-height: 1;
  text-decoration: none;
  cursor: pointer;
}
/* Hover fills the button with sage, so the label is text on sage and needs
   4.5:1, not the 3:1 an outline gets. Paper on sage is 3.41:1 and fails; ink
   on sage is 5.31:1 and passes. Using ink keeps sage exactly as the brand
   book specifies, rather than darkening a brand colour to fix contrast.
   Reasoning in v-tokens.liquid. */
.v-btn:hover {
  background: var(--v-accent);
  border-color: var(--v-accent);
  color: var(--v-ink);
}
.v-btn--ghost {
  background: transparent;
  color: var(--v-ink);
}
.v-btn--ghost:hover {
  background: var(--v-wash);
  color: var(--v-ink);
}
/* Unclassed inline links, 17/08. Neither velia.css nor Dawn's base.css declares a colour
   for a bare <a>: Dawn only styles .link, .underlined-link and .inline-richtext a, none of
   which this layer uses. .v-section sets colour by INHERITANCE, and the UA's
   a:-webkit-any-link is a DECLARED value, so the UA wins and these render #0000EE. That
   passes contrast comfortably but is a saturated browser blue in a palette whose whole
   premise is ink, paper and sage confined to states. It is live today on the privacy
   consent link, which sits on the only conversion path, and applies to the mailto
   links in the contact details. currentColor also survives the
   ink ground, where #0000EE would be 1.93:1 and genuinely fail. */
.v-section a:not([class]) {
  color: currentColor;
  text-underline-offset: 0.2em;
}
.v-btn:focus-visible,
.v-form a:focus-visible {
  outline: 2px solid var(--v-focus, var(--v-accent));
  outline-offset: 2px;
}

/* Stats strip */
.v-stats__grid {
  display: grid;
  gap: var(--v-space-6);
  margin: 0;
}
@media (min-width: 750px) {
  .v-stats__grid {
    grid-template-columns: repeat(auto-fit, minmax(24rem, 1fr));
  }
}
.v-stat {
  border-left: 2px solid var(--v-ink);
  padding-left: var(--v-space-4);
}
.v-stat dt {
  font-family: var(--v-font-mono);
  font-size: var(--v-text-xs);
  letter-spacing: 0.1em;
  text-transform: uppercase;
  color: var(--v-ink-soft);
  margin-bottom: var(--v-space-2);
}
.v-stat__value {
  font-family: var(--v-font-mono);
  font-size: var(--v-text-xl);
  line-height: 1.15;
  margin: 0;
}
.v-stat__note {
  font-size: var(--v-text-s);
  color: var(--v-ink-soft);
  margin: var(--v-space-2) 0 0;
}

/* Display numbers: the v3 landing treatment of the same data the ruled
   strip carries. The figure leads at 40 to 60px and the sentence follows.
   The label is in the DOM but visually hidden, see the section comment. */
.v-nums {
  display: grid;
  gap: var(--v-space-6);
  margin: 0;
}
@media (min-width: 700px) {
  .v-nums {
    grid-template-columns: 1fr 1fr;
    gap: var(--v-space-8);
  }
}
.v-num__value {
  margin: 0;
  font-family: var(--v-font-system);
  font-weight: 800;
  font-size: var(--v-text-num);
  letter-spacing: -0.02em;
  line-height: 1.05;
}
.v-num__note {
  margin: var(--v-space-2) 0 0;
  font-size: var(--v-text-base);
  color: var(--v-ink-soft);
  max-width: 36ch;
}

/* Ruled grid of cells: the pharmaceutical-grid look.
   1px gaps over the line color read as printed rules. */
.v-grid {
  display: grid;
  gap: 1px;
  background: var(--v-line);
  border: 1px solid var(--v-line);
  list-style: none;
  margin: 0;
  padding: 0;
}
@media (min-width: 750px) {
  .v-grid--2 { grid-template-columns: repeat(2, 1fr); }
  .v-grid--3 { grid-template-columns: repeat(3, 1fr); }
}
@media (min-width: 990px) {
  .v-grid--4 { grid-template-columns: repeat(4, 1fr); }
}
.v-cell {
  background: var(--v-paper);
  padding: var(--v-space-5);
}
.v-cell__code {
  font-family: var(--v-font-mono);
  font-size: var(--v-text-l);
  margin: 0 0 var(--v-space-3);
}
.v-cell__title {
  font-size: var(--v-text-base);
  font-weight: 600;
  margin: 0 0 var(--v-space-2);
}
.v-cell__text {
  font-size: var(--v-text-s);
  line-height: 1.55;
  color: var(--v-ink-soft);
  margin: 0;
}

/* Scope block on a credential: which families it covers and, until the
   client confirms, its verification status. Never optional, see the comment
   in sections/v-credentials.liquid. A div since 17/08 so scope and status
   stack inside one bordered footer instead of doubling the rule. */
.v-cell__scope {
  font-family: var(--v-font-mono);
  font-size: var(--v-text-xs);
  letter-spacing: 0.04em;
  color: var(--v-ink-soft);
  margin: var(--v-space-3) 0 0;
  padding-top: var(--v-space-3);
  border-top: 1px solid var(--v-line);
}
.v-cell__scope p {
  margin: 0;
}
.v-cell__scope p + p {
  margin-top: var(--v-space-1);
}

/* Craft: the single-archetype audience block. Text left, a main plate with
   a smaller inset stepped under it on the right. */
.v-craft {
  display: grid;
  gap: var(--v-space-7);
  align-items: center;
}
@media (min-width: 860px) {
  .v-craft {
    grid-template-columns: 5fr 6fr;
    gap: var(--v-space-8);
  }
}
.v-craft__body p {
  font-size: var(--v-text-m);
  line-height: 1.65;
  color: var(--v-ink-soft);
  margin: var(--v-space-4) 0 0;
  max-width: 46ch;
}
.v-craft__body b {
  color: var(--v-ink);
  font-weight: 600;
}
.v-craft__quote {
  font-family: var(--v-font-serif);
  font-style: italic;
  font-weight: 600;
  font-size: clamp(2.2rem, 2.6vw, 3rem);
  line-height: 1.3;
  color: var(--v-ink);
  margin: var(--v-space-6) 0 0;
}
.v-craft__others {
  margin-top: var(--v-space-7);
  padding-top: var(--v-space-5);
  border-top: 1px solid var(--v-ink);
  max-width: 46ch;
}
.v-craft__others p {
  margin: 0;
  font-size: var(--v-text-s);
}
/* The link keeps ink for its text and takes sage only on the underline.
   --v-focus is sage corrected for the ground: the token is named for the
   focus ring but its value is exactly the sage this needs, and the ground
   modifiers already swap in the darkened cut on pearl and sand. The
   underline is not the only signal anyway, the weight steps up with it. */
.v-craft__others a {
  font-weight: 600;
  text-decoration: underline;
  text-decoration-color: var(--v-focus);
  text-underline-offset: 0.3em;
}
.v-craft__media {
  display: grid;
  gap: var(--v-space-4);
}
.v-craft__figure {
  margin: 0;
  border-radius: var(--v-radius-panel);
  overflow: hidden;
}
.v-craft__figure img {
  width: 100%;
  height: auto;
  object-fit: cover;
}
.v-craft__figure--main img {
  aspect-ratio: 4 / 3;
}
.v-craft__figure--side {
  width: 70%;
  justify-self: end;
}
.v-craft__figure--side img {
  aspect-ratio: 5 / 3.4;
}
@media (min-width: 860px) {
  .v-craft__figure--side {
    width: 62%;
  }
}

/* Catalogue cards */
.v-card__media {
  aspect-ratio: 4 / 3;
  background: var(--v-wash);
  display: flex;
  align-items: center;
  justify-content: center;
  overflow: hidden;
  margin-bottom: var(--v-space-4);
}
.v-card__media img {
  width: 100%;
  height: 100%;
  object-fit: cover;
}
/* Empty catalogue tile, redesigned 17/08. Two grey letters on wash read as
   a broken image, five times in a row. The placeholder is now a deliberate
   specimen plate: sand ground (the token's first consumer), a corner index
   in the pharma-grid manner, and a tracked monogram. Decorative throughout,
   both spans are aria-hidden. Replaced automatically when the family gets a
   real image in the metaobject. */
.v-card__media--ph {
  background: var(--v-sand);
  position: relative;
  border: 1px solid var(--v-line);
}
.v-card__idx {
  position: absolute;
  top: var(--v-space-3);
  left: var(--v-space-3);
  font-family: var(--v-font-mono);
  font-size: var(--v-text-xs);
  letter-spacing: 0.1em;
  color: var(--v-ink-soft-tinted);
}
.v-card__ph {
  font-family: var(--v-font-mono);
  font-size: var(--v-text-2xl);
  letter-spacing: 0.18em;
  /* Optical centre: letter-spacing trails the last glyph */
  margin-right: -0.18em;
  color: var(--v-ink-soft-tinted);
}
.v-chips {
  display: flex;
  flex-wrap: wrap;
  gap: 0.8rem;
  list-style: none;
  margin: var(--v-space-3) 0 0;
  padding: 0;
}
.v-chip {
  font-family: var(--v-font-mono);
  font-size: var(--v-text-xs);
  border: 1px solid var(--v-line);
  padding: 0.3rem 0.8rem;
}
.v-card__cta {
  display: inline-block;
  font-size: var(--v-text-s);
  font-weight: 600;
  color: var(--v-ink);
  text-decoration: underline;
  text-underline-offset: 0.3em;
  margin-top: var(--v-space-4);
}

/* Create: the display categories as an editorial list rather than cards.
   Cards would say "five equivalent products"; a ruled list says "these are
   the shapes the offer takes", which is what these are. */
.v-create {
  display: grid;
  gap: var(--v-space-7);
  align-items: start;
}
@media (min-width: 860px) {
  .v-create {
    grid-template-columns: 7fr 5fr;
    gap: var(--v-space-8);
  }
}
.v-cats {
  list-style: none;
  margin: var(--v-space-2) 0 0;
  padding: 0;
}
.v-cat {
  display: flex;
  align-items: center;
  gap: var(--v-space-5);
  /* V5 density: the light grid the brief asks for. */
  padding-block: var(--v-space-3);
  border-bottom: 1px solid var(--v-line);
}
.v-cat:first-child {
  border-top: 1px solid var(--v-line);
}
.v-cat__name {
  flex: 0 0 46%;
  font-size: clamp(1.9rem, 2vw, 2.3rem);
  font-weight: 700;
  letter-spacing: -0.01em;
}
.v-cat__desc {
  flex: 1;
  font-size: var(--v-text-s);
  line-height: 1.5;
  color: var(--v-ink-soft);
}
.v-cat__arch {
  flex: 0 0 auto;
  width: 4.4rem;
  height: auto;
  stroke: var(--v-ink);
  fill: none;
  stroke-linejoin: round;
  stroke-linecap: round;
}
/* The archetype is the first thing to go when the row runs out of width:
   it says nothing the name does not. */
@media (max-width: 519px) {
  .v-cat__arch {
    display: none;
  }
}
.v-create__figure {
  margin: 0;
  border-radius: var(--v-radius-panel);
  overflow: hidden;
}
.v-create__figure img {
  width: 100%;
  height: auto;
  object-fit: cover;
  aspect-ratio: 4 / 3;
}
@media (min-width: 860px) {
  .v-create__figure img {
    aspect-ratio: 4 / 4.8;
  }
}

/* Compact create: the ruled list without the photo column. */
.v-create--compact .v-cats {
  margin-top: var(--v-space-2);
}

/* "La tua prima linea": three products joined by the layer's plus sign. The
   names are offer, not data, so Archivo; the plus is drawn by pseudo with
   empty alt so it is never announced. */
.v-firstline {
  margin-top: var(--v-space-6);
  padding: var(--v-space-5) var(--v-space-6);
  border: 1px solid var(--v-ink);
  border-radius: var(--v-radius-frame);
}
.v-firstline__title {
  font-size: var(--v-text-base);
  font-weight: 600;
  margin: 0 0 var(--v-space-4);
}
.v-firstline__row {
  display: flex;
  flex-wrap: wrap;
  align-items: baseline;
  gap: var(--v-space-3) var(--v-space-4);
  margin: 0;
  padding: 0;
  list-style: none;
}
.v-firstline__item {
  font-weight: 700;
  font-size: clamp(1.9rem, 2.2vw, 2.4rem);
  letter-spacing: -0.01em;
}
.v-firstline__item + .v-firstline__item::before {
  content: '+' / '';
  font-weight: 200;
  font-size: 1.2em;
  color: var(--v-ink-soft);
  margin-right: var(--v-space-4);
}
.v-firstline__note {
  font-size: var(--v-text-s);
  color: var(--v-ink-soft);
  margin: var(--v-space-4) 0 0;
}

/* "Su richiesta": the catch-all panel. Dashed at full ink, 18.1:1, because
   unlike the die rule this border is the whole shape of the component. */
.v-ask {
  margin-top: var(--v-space-7);
  padding: var(--v-space-6);
  border: 1.5px dashed var(--v-ink);
  border-radius: var(--v-radius-frame);
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  justify-content: space-between;
  gap: var(--v-space-5);
}
.v-ask__plus {
  flex: 0 0 auto;
  font-weight: 200;
  font-size: 4.4rem;
  line-height: 1;
}
.v-ask__text {
  flex: 1 1 32rem;
}
.v-ask__text h2,
.v-ask__text h3 {
  font-size: var(--v-text-l);
  font-weight: 700;
  margin: 0 0 var(--v-space-1);
}
.v-ask__text p {
  font-size: var(--v-text-s);
  color: var(--v-ink-soft);
  margin: 0;
}

/* Production: the labelling plate leads, the text answers. */
.v-prod {
  display: grid;
  gap: var(--v-space-7);
  align-items: center;
}
@media (min-width: 860px) {
  .v-prod {
    grid-template-columns: 6fr 5fr;
    gap: var(--v-space-8);
  }
}
.v-prod__figure {
  margin: 0;
  border-radius: var(--v-radius-panel);
  overflow: hidden;
}
.v-prod__figure img {
  width: 100%;
  height: auto;
  object-fit: cover;
  aspect-ratio: 4 / 3.2;
  /* V5 density: the plate stays the strongest image on the page but stops
     claiming half a viewport. Cover keeps the crop centred on the machine. */
  max-height: 36rem;
}
.v-prod__body p {
  font-size: var(--v-text-m);
  line-height: 1.65;
  color: var(--v-ink-soft);
  margin: var(--v-space-4) 0 0;
  max-width: 44ch;
}
.v-prod__body b {
  color: var(--v-ink);
  font-weight: 600;
}

/* Steps */
/* Progress filet. Ornament: see the reasoning in v-steps.liquid. It is
   hidden below 900px, where the steps stack and a horizontal rail would be
   measuring a row that no longer exists. */
.v-steps__rail {
  display: none;
  position: relative;
  height: 2px;
  background: var(--v-line);
  margin-block: var(--v-space-7) var(--v-space-6);
}
@media (min-width: 900px) {
  .v-steps__rail {
    display: block;
  }
}
.v-steps__fill {
  position: absolute;
  inset: 0 auto 0 0;
  width: 0;
  background: var(--v-accent);
}
.v-steps {
  display: grid;
  gap: var(--v-space-6);
  list-style: none;
  margin: 0;
  padding: 0;
}
@media (min-width: 750px) {
  /* 22rem rather than 26: at the 1120px wrap, 26rem only fits three columns
     and the v3 layout is four across. auto-fit keeps the inner pages honest
     if a section is ever given three steps instead. */
  .v-steps {
    grid-template-columns: repeat(auto-fit, minmax(22rem, 1fr));
  }
}
/* v3 sets the numerals in the system face at display weight rather than in
   grey monospace. They are the loudest thing in the section on purpose. */
.v-step__num {
  display: block;
  font-family: var(--v-font-system);
  font-weight: 800;
  font-size: 3.4rem;
  letter-spacing: -0.02em;
  line-height: 1;
  color: var(--v-ink);
  margin-bottom: var(--v-space-3);
}
.v-step__title {
  font-size: var(--v-text-base);
  font-weight: 600;
  margin: 0 0 var(--v-space-2);
}
.v-step__text {
  font-size: var(--v-text-s);
  line-height: 1.55;
  color: var(--v-ink-soft);
  margin: 0;
}

/* Step address inside the collapsed detail: the numeral is data, so mono. */
.v-step__ref {
  font-family: var(--v-font-mono);
  font-size: var(--v-text-xs);
  letter-spacing: 0.1em;
  color: var(--v-ink);
  margin-right: 0.6rem;
}

/* "Come guadagni": three beats and an arrow. The beats are claims, not data,
   so they are Archivo; the arrow is drawn by a pseudo with empty alt text so
   no screen reader announces punctuation. Stacked with a downward arrow
   under 750px. */
.v-earn__flow {
  display: flex;
  flex-direction: column;
  gap: var(--v-space-3);
  margin: var(--v-space-6) 0 0;
  padding: 0;
  list-style: none;
}
@media (min-width: 750px) {
  .v-earn__flow {
    flex-direction: row;
    align-items: baseline;
    gap: var(--v-space-4);
  }
}
.v-earn__beat {
  font-weight: 700;
  font-size: var(--v-text-xl);
  letter-spacing: -0.01em;
  line-height: 1.2;
}
.v-earn__beat + .v-earn__beat::before {
  content: '\2193' / '';
  display: block;
  font-weight: 400;
  color: var(--v-ink-soft);
  margin-bottom: var(--v-space-3);
}
@media (min-width: 750px) {
  .v-earn__beat + .v-earn__beat::before {
    content: '\2192' / '';
    display: inline-block;
    margin: 0 var(--v-space-4) 0 0;
  }
}
.v-earn__message {
  font-size: var(--v-text-m);
  line-height: 1.6;
  color: var(--v-ink);
  max-width: 52ch;
  margin: var(--v-space-6) 0 var(--v-space-5);
}

/* Ready-products list inside the aziende accordion: name and data on one
   ruled row, the data in mono because it IS data. */
.v-ready__list {
  margin: 0;
  padding: 0;
  list-style: none;
}
.v-ready__row {
  display: flex;
  flex-wrap: wrap;
  align-items: baseline;
  justify-content: space-between;
  gap: var(--v-space-2) var(--v-space-5);
  padding-block: var(--v-space-3);
  border-bottom: 1px solid var(--v-line);
}
.v-ready__row:first-child {
  border-top: 1px solid var(--v-line);
}
.v-ready__name {
  font-weight: 600;
  font-size: var(--v-text-base);
}
.v-ready__data {
  font-family: var(--v-font-mono);
  font-size: var(--v-text-s);
  color: var(--v-ink-soft);
}

/* Lead form */
.v-form {
  display: grid;
  gap: var(--v-space-5);
  max-width: 68rem;
}
.v-field {
  display: grid;
  gap: var(--v-space-2);
}
.v-field > label,
.v-field legend {
  font-family: var(--v-font-mono);
  font-size: var(--v-text-s);
  letter-spacing: 0.08em;
  text-transform: uppercase;
}
.v-field input[type='text'],
.v-field input[type='email'],
.v-field input[type='tel'],
.v-field select,
.v-field textarea {
  font-family: var(--v-font-system);
  font-size: var(--v-text-base);
  padding: 1.2rem 1.4rem;
  width: 100%;
  background: var(--v-paper);
  color: var(--v-ink);
  border: 1px solid var(--v-ink);
  border-radius: var(--v-radius);
}
.v-field input:focus-visible,
.v-field select:focus-visible,
.v-field textarea:focus-visible {
  outline: 2px solid var(--v-focus, var(--v-accent));
  outline-offset: 1px;
}
.v-field--radio fieldset {
  border: 0;
  margin: 0;
  padding: 0;
  display: grid;
  gap: var(--v-space-2);
}
.v-radio {
  display: flex;
  align-items: center;
  gap: var(--v-space-2);
  font-size: var(--v-text-base);
}
.v-field--consent {
  display: flex;
  align-items: flex-start;
  gap: var(--v-space-3);
  font-size: var(--v-text-s);
  line-height: 1.55;
}
.v-field--consent input {
  margin-top: 0.3rem;
}
/* Required marking, 17/08. The asterisk is decorative and aria-hidden: the
   `required` attribute carries the semantics. Sage would be the obvious
   accent here but it is 3.41:1 on paper, below the 4.5:1 this text needs,
   so the marker inherits ink and leans on weight instead of colour. Colour
   is never the only signal anyway (WCAG 1.4.1): the note above the form
   explains what the mark means. */
.v-req {
  font-weight: 700;
  margin-left: 0.2em;
}
.v-form__req {
  font-family: var(--v-font-mono);
  font-size: var(--v-text-xs);
  color: var(--v-ink-soft);
  margin: 0;
}
/* Field-level server error. Same red as the summary block, which measures
   6.10:1 on paper and clears AA comfortably. */
.v-field__error {
  font-size: var(--v-text-s);
  color: #b3261e;
  margin: 0;
}
.v-form__hp {
  position: absolute !important;
  left: -9999px !important;
  top: auto;
}
.v-form__sla {
  font-size: var(--v-text-s);
  color: var(--v-ink-soft);
  margin: 0;
}
/* The short privacy clause under the form. Same quiet register as the SLA
   line, pulled up under the presa visione checkbox it belongs to rather than
   sitting in the form's own gap, so the two read as one statement. */
.v-form__privacy {
  font-size: var(--v-text-s);
  color: var(--v-ink-soft);
  margin: calc(var(--v-space-2) * -1) 0 0;
  max-width: 58rem;
}
.v-form__success {
  border: 1px solid var(--v-ink);
  padding: var(--v-space-6);
  max-width: 68rem;
}
.v-form__errors {
  border: 1px solid #b3261e;
  color: #b3261e;
  padding: var(--v-space-4);
  font-size: var(--v-text-s);
}

/* Credentials, prose layout. A single measure: the section is one claim and
   one data line, and a wide one of either would read as a paragraph of terms
   and conditions. */
.v-cred {
  max-width: 62ch;
}
.v-cred__lead {
  font-size: var(--v-text-m);
  line-height: 1.7;
  margin: 0;
}
/* The summary line is technical data, so it is set as technical data:
   monospace, tracked, ruled off above. Ink rather than grey because it is
   the section's payload, not a footnote. */
.v-cred__data {
  font-family: var(--v-font-mono);
  font-size: 1.3rem;
  letter-spacing: 0.04em;
  color: var(--v-ink);
  margin: var(--v-space-5) 0 0;
  padding-top: var(--v-space-4);
  border-top: 1px solid var(--v-ink);
}
/* Social proof: real people, no stars. The card is a photo, one specific
   sentence, and the person's own coordinates. Ships disabled: the section
   renders only when a testimonial passes the consent gate. */
.v-proof__grid {
  display: grid;
  gap: var(--v-space-6);
  margin: var(--v-space-6) 0 0;
  padding: 0;
  list-style: none;
}
@media (min-width: 750px) {
  .v-proof__grid {
    grid-template-columns: repeat(auto-fit, minmax(28rem, 1fr));
  }
}
.v-proof__photo {
  margin: 0 0 var(--v-space-4);
  border-radius: var(--v-radius-panel);
  overflow: hidden;
}
.v-proof__photo img {
  width: 100%;
  height: auto;
  aspect-ratio: 4 / 3;
  object-fit: cover;
}
.v-proof__quote {
  margin: 0 0 var(--v-space-3);
}
.v-proof__quote p {
  font-size: var(--v-text-base);
  line-height: 1.6;
}
.v-proof__who {
  font-size: var(--v-text-s);
  color: var(--v-ink-soft);
  margin: 0;
}
.v-proof__name {
  font-weight: 600;
  color: var(--v-ink);
}
/* The product tried is data, so mono. */
.v-proof__product {
  font-family: var(--v-font-mono);
  font-size: var(--v-text-xs);
  letter-spacing: 0.04em;
  color: var(--v-ink-soft);
  margin: var(--v-space-2) 0 0;
}

/* Case study: media beside story, the client's name leading. Same consent
   gate as the proof cards; nothing renders without it. */
.v-case {
  display: grid;
  gap: var(--v-space-6);
  margin-top: var(--v-space-6);
}
@media (min-width: 860px) {
  .v-case {
    grid-template-columns: 6fr 5fr;
    gap: var(--v-space-8);
    align-items: start;
  }
}
.v-case__photo {
  margin: 0 0 var(--v-space-4);
  border-radius: var(--v-radius-panel);
  overflow: hidden;
}
.v-case__photo img {
  width: 100%;
  height: auto;
  object-fit: cover;
}
.v-case__name {
  font-size: var(--v-text-xl);
  font-weight: 700;
  margin: 0 0 var(--v-space-3);
}
.v-case__goal,
.v-case__story p {
  font-size: var(--v-text-base);
  line-height: 1.65;
  color: var(--v-ink-soft);
}
.v-case__products {
  font-family: var(--v-font-mono);
  font-size: var(--v-text-s);
  color: var(--v-ink);
  margin: var(--v-space-3) 0;
}
.v-case__quote {
  margin: var(--v-space-4) 0 0;
  padding-left: var(--v-space-4);
  border-left: 2px solid var(--v-ink);
}
.v-case__quote p {
  font-size: var(--v-text-base);
  line-height: 1.6;
}

/* Key-value list, contact details */
.v-kv {
  display: grid;
  gap: 1px;
  background: var(--v-line);
  border: 1px solid var(--v-line);
  margin: 0;
}
.v-kv > div {
  background: var(--v-paper);
  display: grid;
  gap: var(--v-space-1);
  padding: var(--v-space-4) var(--v-space-5);
  /* Grid items default to min-width:auto, so a dd cannot shrink below its
     longest unbreakable word. Needed alongside the dd overflow-wrap below:
     overflow-wrap alone does not lower the automatic minimum size in every
     engine, and the PEC address is 28 characters with no break opportunity,
     which lands right on the limit of a 320px viewport. */
  min-width: 0;
}
@media (min-width: 750px) {
  .v-kv > div {
    grid-template-columns: 22rem 1fr;
    align-items: baseline;
  }
}
.v-kv dt {
  font-family: var(--v-font-mono);
  font-size: var(--v-text-xs);
  letter-spacing: 0.1em;
  text-transform: uppercase;
  color: var(--v-ink-soft);
}
.v-kv dd {
  margin: 0;
  font-size: var(--v-text-base);
  /* Browsers do not break at @ or . so email and PEC addresses have no wrap
     opportunity at all. The public contact address is still unchosen and will
     likely be longer than the PEC. */
  overflow-wrap: anywhere;
}

/* CTA band. v3 centres it and gives it its own vertical measure, deeper
   than a content section: it is the end of the page, not another row. */
.v-cta-band {
  text-align: center;
}
.v-cta-band .v-wrap > * {
  margin-inline: auto;
}
@media (min-width: 750px) {
  .v-cta-band {
    padding-block: 10rem;
  }
}
/* Hair on ink is 14.51:1. The lead is a heading by structure and a quiet
   line by design: the claim under it is what carries the weight. */
.v-cta__lead {
  font-family: var(--v-font-system);
  font-size: var(--v-text-l);
  font-weight: 400;
  letter-spacing: 0;
  color: var(--v-line);
  margin: 0 0 var(--v-space-5);
}
.v-section--ink .v-cta__lead {
  color: var(--v-line);
}
.v-cta__claim {
  font-family: var(--v-font-serif);
  font-style: italic;
  font-weight: 600;
  font-size: var(--v-text-display);
  line-height: 1.05;
  margin: 0 0 var(--v-space-7);
}
.v-cta-band__actions {
  display: flex;
  flex-wrap: wrap;
  gap: var(--v-space-3);
  margin-top: var(--v-space-5);
}
.v-cta-band .v-cta-band__actions {
  justify-content: center;
  margin-top: 0;
}
/* On ink this is --v-ink-soft-inverse, 7.17:1. */
.v-cta__note {
  font-size: 1.3rem;
  color: var(--v-ink-soft);
  margin: var(--v-space-5) 0 0;
}

/* Footer. Two tiers on the wrap's grid, left-aligned like every section
   above it. Top: the signature and the section links. Bottom, under a
   hairline: the company line and the three policy links. Below 750px both
   tiers stack, still flush left with the wrap. */
.v-footer {
  background: var(--v-paper);
  color: var(--v-ink);
  border-top: 1px solid var(--v-line);
  padding-block: var(--v-space-7) var(--v-space-6);
}
.v-footer__inner {
  display: flex;
  flex-direction: column;
  gap: var(--v-space-5);
}
.v-footer__top,
.v-footer__bottom {
  display: flex;
  flex-direction: column;
  align-items: flex-start;
  gap: var(--v-space-3);
}
.v-footer__bottom {
  border-top: 1px solid var(--v-line);
  padding-top: var(--v-space-4);
}
.v-footer__sig {
  display: inline-flex;
  align-items: center;
  gap: var(--v-space-2);
}
.v-footer__suffix {
  font-family: var(--v-font-serif);
  font-style: italic;
  font-weight: 600;
  font-size: 1.8rem;
  line-height: 1;
}
.v-footer__nav {
  display: flex;
  flex-wrap: wrap;
  gap: var(--v-space-1) var(--v-space-4);
  /* The links carry their own inline padding for the hit target; pull the
     row back by the same amount so the first word sits on the wrap edge. */
  margin-inline: calc(-1 * var(--v-space-1));
}
.v-footer__nav a {
  font-size: 1.4rem;
  color: var(--v-ink-soft);
  text-decoration: none;
  transition: color 160ms ease;
}
.v-footer__nav a:hover {
  color: var(--v-ink);
}
.v-footer__legal {
  font-size: 1.3rem;
  line-height: 1.6;
  color: var(--v-ink-soft);
  margin: 0;
}
@media (min-width: 750px) {
  .v-footer__top,
  .v-footer__bottom {
    flex-direction: row;
    align-items: center;
    justify-content: space-between;
    gap: var(--v-space-3) var(--v-space-6);
  }
  .v-footer__nav {
    justify-content: flex-end;
  }
}

/* Keep focus clear of the sticky nav (WCAG 2.4.11 Focus Not Obscured).
   Both upward paths, tabbing backwards with Shift+Tab and the browser's own
   scroll-into-view, align the target flush with the viewport top, which is
   exactly where the pinned nav sits: the focused control ends up completely
   hidden behind it.

   This used to read Dawn's --header-height, published from setHeaderHeight()
   in its header JS. v-nav replaced that header in the header group, so the
   variable is no longer set by anything and the rule was silently falling
   back to 0px. Our own nav has a declared height, so the token is both the
   correct source and a fixed one. Scoped to the Velia layer deliberately: a
   :root scroll-padding-top would fix Dawn's own pages too but reaches
   outside our fence. */
.v-section :is(a, button, input, select, textarea, [tabindex]) {
  scroll-margin-top: calc(var(--v-nav-h, 0rem) + var(--v-space-4));
}

/* ============================================================
   Motion & material, v4 proposal enhancement layer (18 Aug).
   The flat canvas read as static rather than serene. This layer
   adds depth, tactility and slow scroll-linked life WITHOUT
   breaking the restraint: no colour is introduced, only warm
   shadow, weight and machined motion. Everything that MOVES is
   gated by the reduced-motion block at the foot of the layer;
   the depth (shadow) stays, because a shadow is not motion.
   Appended last on purpose: source order lets these win at
   equal specificity over the base component rules above.
   ============================================================ */

:root {
  /* Warm, ink-based shadows. The palette is warm, so a neutral-grey
     shadow reads cold: these are mixed from ink (#161616). */
  --v-shadow-soft: 0 1px 2px rgba(22, 22, 22, 0.05), 0 10px 30px -14px rgba(22, 22, 22, 0.18);
  --v-shadow-lift: 0 2px 6px rgba(22, 22, 22, 0.06), 0 22px 48px -20px rgba(22, 22, 22, 0.26);
  --v-ease-out: cubic-bezier(0.16, 1, 0.3, 1);
}

/* Depth: lift the figures off the single flat plane. */
.v-hero__panel,
.v-craft__figure,
.v-prod__figure,
.v-create__figure {
  box-shadow: var(--v-shadow-soft);
}

/* The image carries a scale hook that velia.js drives on scroll: a very
   slow push so the stills breathe instead of sitting dead. The panels
   already clip (overflow:hidden), so the zoom stays inside its frame.
   No transition here on purpose, the scroll position IS the timeline. */
.v-hero__panel img,
.v-craft__figure img,
.v-prod__figure img,
.v-create__figure img {
  transform: scale(var(--v-img-scale, 1));
  transform-origin: center;
  will-change: transform;
}

/* Tactility: buttons gain weight on hover. The sage fill and its reasoned
   contrast are untouched; this only adds lift and a warm shadow. */
.v-btn {
  transition: transform 0.4s var(--v-ease-out), box-shadow 0.4s var(--v-ease-out),
    background-color 0.35s var(--v-ease), border-color 0.35s var(--v-ease), color 0.35s var(--v-ease);
}
.v-btn:hover {
  transform: translateY(-2px);
  box-shadow: var(--v-shadow-lift);
}
.v-btn:active {
  transform: translateY(0);
  transition-duration: 0.08s;
}

/* Cells warm their hairline under the cursor: a passive highlight, no
   motion. The earlier draft lifted these with a shadow, which the review
   rightly called false affordance: ten of the eleven cells are not links,
   and a lift promises a click. Lift and shadow now belong only to elements
   that actually go somewhere (buttons, the ask cell). */
.v-cell,
.v-cat {
  transition: border-color 0.35s var(--v-ease);
}
.v-cell:hover,
.v-cat:hover {
  border-color: color-mix(in srgb, var(--v-ink) 22%, var(--v-line));
}

/* The nav earns a soft shadow and a whisper of blur once it leaves the
   top, so it reads as a layer floating above the page rather than a line
   drawn on it. Only shows while it is stuck; harmless if it is not. */
.v-nav.is-scrolled {
  box-shadow: 0 1px 0 var(--v-line), 0 16px 32px -26px rgba(22, 22, 22, 0.55);
  backdrop-filter: saturate(1.08) blur(8px);
}

/* Reduced motion: strip every MOVEMENT this layer introduced. Depth and
   shadow stay (not motion); transforms and the scroll push reset so the
   page is calm and still for a user who asked for it. */
@media (prefers-reduced-motion: reduce) {
  .v-hero__panel img,
  .v-craft__figure img,
  .v-prod__figure img,
  .v-create__figure img {
    transform: none;
  }
  .v-btn {
    transition: background-color 0.35s var(--v-ease), border-color 0.35s var(--v-ease),
      color 0.35s var(--v-ease);
  }
  .v-btn:hover {
    transform: none;
    box-shadow: none;
  }
}

/* ============================================================
   Taste pass, round 2 of the v4 enhancement (18 Aug, Fable).
   Three moves: material (grain, one image grade, ink vignette),
   scale (the hero a step bolder, the serif given one monumental
   moment), and the quiet section that breaks the metronome.
   Still no colour: the life comes from paper, weight and light.
   ============================================================ */

:root {
  /* Paper grain. A 140px tile of desaturated fractal noise at 5%,
     inline SVG so it costs no request. On the warm grounds it reads
     as stock rather than pixels; on ink it is invisible, which is
     why ink gets a vignette instead (below). */
  --v-grain: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='140' height='140'%3E%3Cfilter id='vg'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='0.8' numOctaves='2' stitchTiles='stitch'/%3E%3CfeColorMatrix type='saturate' values='0'/%3E%3C/filter%3E%3Crect width='140' height='140' filter='url(%23vg)' opacity='0.05'/%3E%3C/svg%3E");
}

/* The grounds get the grain. The base rules set background with the
   shorthand, which resets the image longhand, so these later rules win
   the longhand back at equal specificity by source order. */
.v-section {
  background-image: var(--v-grain);
}
/* Ink swaps grain for light: a soft top vignette so the closing band
   reads as a lit plate rather than a flat rectangle. */
.v-section--ink {
  background-image: radial-gradient(130% 100% at 50% 0%, rgba(255, 255, 255, 0.06), transparent 55%);
}

/* One grade across every figure so five placeholder renders stop being
   five colour temperatures. Slightly desaturated, a breath of warmth,
   a touch of contrast: art-directed rather than assembled. Static, so
   it needs no reduced-motion gate, and it composes with the scroll
   scale transform already on these images. */
.v-hero__panel img,
.v-craft__figure img,
.v-prod__figure img,
.v-create__figure img {
  filter: saturate(0.85) sepia(0.08) contrast(1.03);
}

/* The hero, a step bolder. The text column widens a little and the claim
   takes the room: Cormorant italic runs narrow, and the authored break
   already holds the two lines. 92px cap measured against the widened
   column at the 1200px wrap, so the second line does not rewrap. */
@media (min-width: 900px) {
  .v-hero__grid {
    grid-template-columns: 1.16fr 0.84fr;
  }
}
/* Floor stays at v3's 5.2rem: the raised cap gives the desktop drama, and
   the lower floor is what keeps the authored two-line break safe on a
   360px phone, which is the primary device. Only the cap grew. */
.v-hero__title--editorial {
  font-size: clamp(5.2rem, 7.6vw, 9.2rem);
}

/* The quiet moment. One line, monumental, centered, and more sky than
   text: the padding is the point. This is where the scale drama lives
   now, so the page's ladder runs whisper to monument instead of
   crowding the middle. */
.v-section--quiet {
  padding-block: clamp(13rem, 24vh, 22rem);
}
.v-quiet__text {
  font-family: var(--v-font-serif);
  font-style: italic;
  font-weight: 600;
  font-size: clamp(3.4rem, 6vw, 7.2rem);
  line-height: 1.12;
  letter-spacing: 0;
  color: var(--v-ink);
  text-align: center;
  text-wrap: balance;
  max-width: 20ch;
  margin-inline: auto;
}

/* Stats display, v4 corrections. The microlabel is the spec-sheet line
   above each figure: three figures without visible labels stopped
   explaining themselves once the v4 notes no longer carried the label
   meaning. And three figures want three columns, not a 2+1 orphan. */
.v-num__label {
  font-family: var(--v-font-mono);
  font-size: var(--v-text-xs);
  letter-spacing: 0.14em;
  text-transform: uppercase;
  color: var(--v-ink-soft);
  margin: 0 0 var(--v-space-2);
}
@media (min-width: 900px) {
  .v-nums--3 {
    grid-template-columns: repeat(3, 1fr);
    gap: var(--v-space-7);
  }
}

/* The ask cell: the audience grid's sixth slot. Five personas in a ruled
   grid leave a void that renders as a slab of the gap colour, which reads
   as a mistake. The sixth cell completes the grid and earns the slot: the
   escape hatch for the buyer whose sector is not listed, pointing at the
   form. Whole cell is the link. */
.v-cell--ask {
  padding: 0;
  background: var(--v-wash);
  /* The cell is its own tinted ground inside a paper section, so it swaps in
     the tinted grey exactly as the wash and sand section grounds do: #767676
     is 4.24:1 on pearl and fails AA, #6b6b6b clears it on pearl and on the
     sand hover fill both. Same finding, same fix, as the 14/08 pass. */
  --v-ink-soft: var(--v-ink-soft-tinted);
}
.v-cell--ask > a {
  display: flex;
  flex-direction: column;
  height: 100%;
  padding: var(--v-space-5);
  color: var(--v-ink);
  text-decoration: none;
  transition: background-color 0.35s var(--v-ease);
}
.v-cell--ask > a:hover {
  background: var(--v-sand);
}
.v-cell--ask > a:focus-visible {
  outline: 2px solid var(--v-focus);
  outline-offset: -2px;
}
.v-cell__go {
  margin-top: auto;
  padding-top: var(--v-space-4);
  font-family: var(--v-font-mono);
  font-size: var(--v-text-l);
  transition: transform 0.4s var(--v-ease-out);
}
.v-cell--ask > a:hover .v-cell__go {
  transform: translateX(6px);
}
@media (prefers-reduced-motion: reduce) {
  .v-cell--ask > a:hover .v-cell__go {
    transform: none;
  }
}

/* Anchor offset for wrap-level ids. The lead form's #modulo anchor sits on
   the inner .v-wrap (the section id is taken by the post-submit target), and
   the offset rule above this layer only matches section-level ids. Harmless
   while the sticky nav was broken; the moment the wrapper fix made the nav
   actually stick, every CTA pointing at #modulo (hero, nav, ask cell,
   closing band) landed the form heading under the bar. */
.v-section .v-wrap[id] {
  scroll-margin-top: calc(var(--v-nav-h, 0rem) + var(--v-space-4));
}

/* Grain differential and the toothless footer, from the re-review. The
   sand and pearl grounds sit closer to the speckle tone than paper does,
   so the same 5% reads dirtier there: tinted grounds drop to 3%. And the
   footer was the one surface with no tooth at all, which made it read as
   a different stock stapled onto the page. */
:root {
  --v-grain-soft: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='140' height='140'%3E%3Cfilter id='vgs'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='0.8' numOctaves='2' stitchTiles='stitch'/%3E%3CfeColorMatrix type='saturate' values='0'/%3E%3C/filter%3E%3Crect width='140' height='140' filter='url(%23vgs)' opacity='0.03'/%3E%3C/svg%3E");
}
.v-section--wash,
.v-section--sand {
  background-image: var(--v-grain-soft);
}
.v-footer {
  background-image: var(--v-grain);
}

/* The form's post-submit fragment. Shopify redirects to /contact#ContactForm,
   and the form element is neither a section id, a wrap id nor a focusable,
   so the success or error state scrolled in under the sticky bar before the
   focus script corrected it. Same bug class as the #modulo gap, closed the
   same way. */
.v-section form[id] {
  scroll-margin-top: calc(var(--v-nav-h, 0rem) + var(--v-space-4));
}

/* Routes. Two cards on the ruled grid, each closing on a mono spec line
   pinned to the foot so the two timings line up across the pair and read
   as a comparison rather than two paragraphs. Reuses .v-cell, so the only
   new rules are the column and the spec line itself. */
.v-route {
  display: flex;
  flex-direction: column;
}
.v-route__spec {
  margin: var(--v-space-5) 0 0;
  padding-top: var(--v-space-3);
  border-top: 1px solid var(--v-line);
  font-family: var(--v-font-mono);
  font-size: var(--v-text-xs);
  letter-spacing: 0.14em;
  text-transform: uppercase;
  color: var(--v-ink);
}
.v-route .v-cell__text {
  margin-bottom: auto;
}

/* Footer legal row: the three policy links, one step quieter than the
   section nav so it reads as fine print rather than a second navigation. */
.v-footer__legal-nav {
  display: flex;
  flex-wrap: wrap;
  align-items: baseline;
  gap: var(--v-space-1) var(--v-space-3);
  margin-inline: calc(-1 * var(--v-space-1));
}
.v-footer__legal-nav a {
  border-bottom: 1px solid transparent;
  padding: 0;
  margin: 0;
  font: inherit;
  font-size: 1.3rem;
  color: var(--v-ink-soft);
  text-decoration: none;
  transition: color 160ms ease, border-color 160ms ease;
}
.v-footer__legal-nav a:hover {
  color: var(--v-ink);
  border-bottom-color: var(--v-line);
}
@media (min-width: 750px) {
  .v-footer__legal-nav {
    justify-content: flex-end;
  }
}

/* ============================================================
   V6 layer. The simplified landing: ten blocks, everything below the
   surface behind one click, cut-out product photography floating on the
   grounds rather than framed inside panels.

   Everything here is additive. No V5 rule is edited, because the inner
   pages still render through them and this branch is a proposal for the
   home page, not a reskin of the site.
   ============================================================ */

/* Inline data. The V6 rule is stricter than the V5 one it replaces: mono
   marks a NUMBER, not a line that happens to contain one. So this is an
   inline span sitting inside ordinary body copy, sized down a step because
   IBM Plex Mono runs optically larger than Archivo at the same size. */
.v-num {
  font-family: var(--v-font-mono);
  font-weight: 500;
  font-size: 0.92em;
  color: var(--v-ink);
}

/* Cut-outs. The pack ships five PNGs with real alpha and asks for them to
   float on the ground rather than sit in a card: no frame, no radius, no
   background of their own, and free to cross a section boundary.

   The shadow is a drop-shadow filter rather than a box-shadow on purpose.
   box-shadow would draw the rectangle of the img element, which is exactly
   the frame the brief is removing; drop-shadow follows the alpha channel, so
   the shadow has the shape of the bottle. Kept soft and low, per the pack. */
.v-float img {
  display: block;
  width: 100%;
  height: auto;
  filter: drop-shadow(0 1.4rem 2.2rem rgb(22 22 22 / 9%));
}

/* Sections a cut-out is allowed to hang out of.

   .v-section sets overflow:hidden for the bled ghost archetype, which would
   clip the image at the section edge. This does not simply undo that:
   overflow:visible on a full-width section is how a page gets a horizontal
   scrollbar on a phone, and nothing about "let the bottle cross the seam"
   asks for one. overflow:clip with a clip margin keeps a bounded escape
   hatch, generous vertically and still bounded horizontally.

   Where overflow:clip is unsupported the declaration is dropped and the
   inherited hidden applies, so the image is clipped at the boundary and
   nothing breaks. */
@supports (overflow: clip) {
  .v-section--bleed {
    overflow: clip;
    overflow-clip-margin: 14rem;
  }
}

/* Packaging archetypes, server-rendered by v-arch. */
.v-arch {
  display: block;
  width: 100%;
  height: auto;
  color: var(--v-ink);
}

/* ------------------------------------------------------------
   Hero additions
   ------------------------------------------------------------ */

/* The target line answers "is this for me?" before the buttons are read, so
   it is quiet but not faint: grey at body size, not at footnote size. */
.v-hero__target {
  font-size: var(--v-text-base);
  color: var(--v-ink-soft);
  margin: var(--v-space-3) 0 0;
}

.v-hero__trust {
  font-size: var(--v-text-s);
  line-height: 1.6;
  color: var(--v-ink-soft);
  margin: var(--v-space-5) 0 0;
  max-width: 46ch;
}

/* The cut-out hero. No radius and no clipping, because there is no edge to
   round: the shape of the image IS the shape of the products. */
.v-hero__panel--float {
  border-radius: 0;
  overflow: visible;
  /* The motion layer gives every panel a box-shadow, which on a cut-out
     draws the rectangle of the img element under the alpha-shaped
     drop-shadow: two shadows, one of them the frame the brief removes. */
  box-shadow: none;
}
.v-hero__panel--float img {
  aspect-ratio: auto;
  max-height: none;
  object-fit: contain;
}
@media (min-width: 900px) {
  .v-hero__panel--float img {
    aspect-ratio: auto;
    /* Wider than its column and pulled down: the family of bottles reads as
       a photograph laid on the page rather than a picture placed in a slot,
       and the lowest bottle crosses onto the section below. */
    width: 118%;
    max-width: none;
    margin-bottom: calc(var(--v-space-8) * -1);
  }
}

/* ------------------------------------------------------------
   Famiglie. Six soft boxes; the box itself opens across the row.
   ------------------------------------------------------------ */
.v-fams {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(13rem, 1fr));
  gap: var(--v-space-3);
  align-items: start;
}
@media (max-width: 480px) {
  .v-fams {
    grid-template-columns: repeat(2, 1fr);
  }
}

/* 1.8rem is the label's own radius, from the brand's die-cut sticker. The
   pack's one visual absolute for this component is that nothing here has a
   sharp corner. */
.v-fam {
  border: 1.5px solid var(--v-line);
  border-radius: 1.8rem;
  background: var(--v-paper);
  transition: border-color 0.3s var(--v-ease), background 0.3s var(--v-ease);
}
.v-fam:hover {
  border-color: var(--v-ink);
}
.v-fam__summary {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: var(--v-space-2);
  padding: 1.8rem 1rem 1.4rem;
  cursor: pointer;
  text-align: center;
  list-style: none;
}
.v-fam__summary::-webkit-details-marker {
  display: none;
}
.v-fam__summary:focus-visible {
  outline: 2px solid var(--v-focus);
  outline-offset: 3px;
  border-radius: 1.8rem;
}
.v-fam__ill {
  display: block;
  width: 5.8rem;
  transition: width 0.3s var(--v-ease);
}
.v-fam__name {
  font-family: var(--v-font-system);
  font-size: 1.3rem;
  font-weight: 600;
  line-height: 1.25;
  margin: 0;
}
.v-fam__desc {
  margin: 0;
  color: var(--v-ink-soft);
  max-width: 56ch;
}

/* Open. The box takes the whole row, turns on its side and lets the
   description in. grid-column on a details element works because .v-fams is
   a grid and every details is one of its items. */
.v-fam[open] {
  grid-column: 1 / -1;
  padding: var(--v-space-5) var(--v-space-6);
  background: var(--v-wash);
  border-color: var(--v-ink);
  border-radius: 2.2rem;
  animation: v-fam-land 0.38s var(--v-ease);
}
.v-fam[open] .v-fam__summary {
  padding: 0;
}
.v-fam[open] .v-fam__name {
  font-size: var(--v-text-m);
}
.v-fam[open] .v-fam__desc {
  margin-top: var(--v-space-2);
  color: var(--v-ink-soft-tinted);
}

/* The open box, above 600px: icon on the left, name and description stacked
   beside it as one block.

   They have to READ as one block and they cannot BE one. The name lives
   inside the summary, because that is what gives the control its accessible
   name; the description lives outside it, because a closed box must not
   announce its own answer. Two siblings, and the second always starts below
   the full height of the first.

   So the icon comes out of the flow. The box reserves a left gutter, the
   icon is placed in it and centred against the whole box, and summary and
   description then stack naturally in the remaining column. Rejected: 
   display:contents on the summary, which would let one grid own all three
   but has a history of dropping elements out of the accessibility tree, and
   this element IS the control.

   The transform that centres the icon sits on the WRAPPER, never on the svg:
   the character animations drive transform on .v-arch, and sharing the
   property would make every icon jump to the top of its gutter the moment
   its animation took over. */
@media (min-width: 600px) {
  .v-fam[open] {
    position: relative;
    /* Flex column only to centre the pair against the icon: min-height is
       set by the icon, and left to itself the text would sit at the top of
       a box whose height it does not control. */
    display: flex;
    flex-direction: column;
    justify-content: center;
    padding-left: calc(var(--v-space-6) + 9.2rem + var(--v-space-5));
    min-height: calc(9.2rem * 320 / 240 + var(--v-space-5) * 2);
  }
  /* static, deliberately. Dawn's base.css gives every summary
     position:relative, which quietly made the SUMMARY the containing block
     for the icon below: left:0 then measured from the text column, not from
     the box, and the icon landed on top of the name. Measured, not guessed:
     offsetParent reported v-fam__summary. */
  .v-fam[open] .v-fam__summary {
    display: block;
    position: static;
    text-align: left;
  }
  .v-fam[open] .v-fam__ill {
    position: absolute;
    left: var(--v-space-6);
    top: 50%;
    transform: translateY(-50%);
    width: 9.2rem;
  }
}

/* The character animations. The icon scales in on every open; what it does
   after that is the block's own, and it is what makes six identical boxes
   feel like six different products. The second animation is a custom
   property so the six variants are six one-line declarations rather than six
   rules, and it is guarded by :not([data-v-anim='none']) because a shorthand
   naming an undefined custom property is invalid and would drop the scale-in
   along with it. */
.v-fam[data-v-anim='press'] { --v-char: v-char-press; }
.v-fam[data-v-anim='squeeze'] { --v-char: v-char-squeeze; }
.v-fam[data-v-anim='tilt'] { --v-char: v-char-tilt; }
.v-fam[data-v-anim='hop'] { --v-char: v-char-hop; }
.v-fam[data-v-anim='sway'] { --v-char: v-char-sway; }
.v-fam[data-v-anim='pop'] { --v-char: v-char-pop; }

.v-fam[open] .v-arch {
  animation: v-fam-in 0.4s var(--v-ease);
}
.v-fam[open][data-v-anim]:not([data-v-anim='none']) .v-arch {
  animation: v-fam-in 0.4s var(--v-ease), var(--v-char) 0.65s var(--v-ease) 0.18s;
}
/* A leaf bends from where it is attached, not from its middle. */
.v-fam[data-v-anim='sway'] .v-arch {
  transform-origin: 50% 92%;
}

@keyframes v-fam-land {
  0%, 100% { transform: rotate(0); }
  25% { transform: rotate(-1.1deg); }
  55% { transform: rotate(0.9deg); }
  80% { transform: rotate(-0.4deg); }
}
@keyframes v-fam-in {
  from { opacity: 0; transform: scale(0.72); }
  to { opacity: 1; transform: scale(1); }
}
@keyframes v-char-press {
  0%, 100% { transform: translateY(0); }
  35% { transform: translateY(6px); }
  65% { transform: translateY(-2px); }
}
@keyframes v-char-squeeze {
  0%, 100% { transform: scale(1, 1); }
  40% { transform: scale(1.07, 0.9); }
  70% { transform: scale(0.98, 1.03); }
}
@keyframes v-char-tilt {
  0%, 100% { transform: rotate(0); }
  35% { transform: rotate(-7deg); }
  70% { transform: rotate(3deg); }
}
@keyframes v-char-hop {
  0%, 100% { transform: translateY(0); }
  30% { transform: translateY(-8px); }
  55% { transform: translateY(0); }
  75% { transform: translateY(-4px); }
}
@keyframes v-char-sway {
  0%, 100% { transform: rotate(0); }
  30% { transform: rotate(6deg); }
  60% { transform: rotate(-4deg); }
  85% { transform: rotate(2deg); }
}
@keyframes v-char-pop {
  0% { transform: scale(1) rotate(0); }
  40% { transform: scale(1.14) rotate(90deg); }
  70%, 100% { transform: scale(0.97) rotate(90deg); }
}

/* The pack asks for all of it off under reduced motion and means all of it:
   the landing shake, the icon's scale-in and its character animation. The
   width transition goes too, because a box that grows smoothly is still
   motion. What stays is the state change itself, which is the information. */
@media (prefers-reduced-motion: reduce) {
  .v-fam,
  .v-fam[open],
  .v-fam[open] .v-arch,
  .v-fam[open][data-v-anim]:not([data-v-anim='none']) .v-arch {
    animation: none;
  }
  .v-fam__ill {
    transition: none;
  }
}

/* ------------------------------------------------------------
   La tua prima linea
   ------------------------------------------------------------ */
.v-line {
  display: grid;
  gap: var(--v-space-6);
  align-items: center;
}
@media (min-width: 750px) {
  .v-line {
    grid-template-columns: 1fr auto;
    gap: var(--v-space-8);
  }
}
.v-line__text {
  font-size: var(--v-text-m);
  line-height: 1.6;
  color: var(--v-ink-soft);
  max-width: 52ch;
  margin: var(--v-space-4) 0 0;
}
/* The three silhouettes stand on one baseline, which is what makes them read
   as a line of products rather than three drawings. */
.v-line__row {
  display: flex;
  align-items: flex-end;
  justify-content: center;
  gap: var(--v-space-6);
}
.v-line__item {
  display: block;
  width: 8.4rem;
}
@media (min-width: 750px) {
  .v-line__item {
    width: 10.4rem;
  }
}

/* ------------------------------------------------------------
   Kit prova
   ------------------------------------------------------------ */
.v-kit {
  display: grid;
  gap: var(--v-space-6);
  align-items: center;
}
@media (min-width: 750px) {
  .v-kit {
    grid-template-columns: 1fr 0.95fr;
    gap: var(--v-space-8);
  }
}
.v-kit__actions {
  margin-top: var(--v-space-6);
}
.v-kit__media {
  justify-self: center;
  width: min(42rem, 100%);
}

/* ------------------------------------------------------------
   Personalizzazione, with the section photograph
   ------------------------------------------------------------ */
.v-custom {
  display: grid;
  gap: var(--v-space-6);
}
@media (min-width: 900px) {
  .v-custom--media {
    grid-template-columns: 1fr 0.62fr;
    gap: var(--v-space-8);
    align-items: start;
  }
}
/* The one image on this page that is not a cut-out, so it keeps the panel
   radius the V3 photographs use. It also starts level with the first
   accordion rather than with the heading, which is what puts it beside
   Formula. */
.v-custom__figure {
  margin: 0;
  border-radius: var(--v-radius-panel);
  overflow: hidden;
}
.v-custom__figure img {
  display: block;
  width: 100%;
  height: auto;
  object-fit: cover;
  aspect-ratio: 4 / 5;
}

/* ------------------------------------------------------------
   Steps, one accordion per step
   ------------------------------------------------------------ */
.v-steps--each {
  list-style: none;
  padding: 0;
  margin: var(--v-space-6) 0 0;
}
/* The numeral keeps each step's address inside its own summary. It is
   aria-hidden: the list is already ordered. */
.v-steps--each .v-step__num {
  margin-right: var(--v-space-4);
  color: var(--v-ink-soft);
}
/* Only the LAST accordion draws a bottom rule, so four adjacent ones read as
   one ruled list rather than four stacked boxes. */
.v-steps--each > li:last-child .v-acc {
  border-bottom: 1px solid var(--v-ink);
}

/* ------------------------------------------------------------
   Closing band with the figure
   ------------------------------------------------------------ */
.v-cta-band--media {
  display: grid;
  gap: var(--v-space-6);
  align-items: center;
  text-align: left;
}
@media (min-width: 900px) {
  .v-cta-band--media {
    grid-template-columns: 1fr 0.8fr;
    gap: var(--v-space-8);
  }
}
/* The band is centred by default. With the figure beside it, centred text in
   a half-width column reads as a mistake, so the body goes left. */
.v-cta-band--media .v-cta-band__actions {
  justify-content: flex-start;
}
.v-cta-band__media {
  justify-self: center;
  width: min(38rem, 100%);
}
/* The figure enters from below rather than sitting inside the band: half a
   person cropped by a section edge is the effect the pack asks for, and it
   only works if the image is allowed to reach the edge. */
@media (min-width: 900px) {
  .v-cta-band__media {
    margin-bottom: calc(var(--v-section-pad) * -1);
    align-self: flex-end;
  }
}

/* ------------------------------------------------------------
   Nav: the header always carries an action
   ------------------------------------------------------------ */
/* Below 431px the full CTA label does not fit beside the lockup and the
   toggle, and the V5 answer was to drop the button and leave the bar with a
   hamburger and nothing to do. The V6 pack requires an action at every
   width, so the button stays and the label shortens instead. */
.v-nav .v-nav__cta {
  display: inline-flex;
}
.v-nav__cta-long {
  display: none;
}
.v-nav__cta-short {
  display: inline;
}
@media (min-width: 431px) {
  .v-nav__cta-long {
    display: inline;
  }
  .v-nav__cta-short {
    display: none;
  }
}
@media (max-width: 430px) {
  .v-nav .v-nav__cta {
    padding: 1rem 1.4rem;
    font-size: 1.3rem;
  }
  .v-nav__bar {
    gap: var(--v-space-2);
  }
}

/* The closing band's lead was written for the ink ground and only for it:
   hair on ink is 14.51:1, and it is deliberately quiet because the claim
   under it carries the weight. V6 puts the band on sand so the cut-out has
   something to stand on, and hair on sand is 1.05:1, which is not quiet, it
   is gone. On the three light grounds it takes the ground's own secondary
   ink instead, which is the contrast-checked value for each of them. */
.v-section--paper .v-cta__lead,
.v-section--wash .v-cta__lead,
.v-section--sand .v-cta__lead {
  color: var(--v-ink-soft);
}

/* In the row layout the numeral is a display element, 34px and above the
   title, because the row has nothing else to give the eye. Inside a summary
   it has a title beside it, so it goes back to being what it is: an index.
   Mono, small, secondary, on the line. */
.v-steps--each .v-step__num {
  display: inline;
  font-family: var(--v-font-mono);
  font-size: var(--v-text-s);
  font-weight: 500;
  letter-spacing: 0.04em;
  color: var(--v-ink-soft);
  margin: 0 var(--v-space-4) 0 0;
}

/* ------------------------------------------------------------
   Chi sei. The fork by client type, from the structure phase:
   soft cards in the famiglie style without the expansion, each
   card one link to its branch page. The block arrived with the
   ricarica branch's selector; these rules are shared with it,
   so a later merge keeps one copy.
   ------------------------------------------------------------ */
.v-who {
  list-style: none;
  padding: 0;
  margin: var(--v-space-6) 0 0;
  display: grid;
  /* The prototype's .who: 200px minimum per card and a 14px gap, so three
     cards hold one row down to about 700px instead of dropping the third. */
  grid-template-columns: repeat(auto-fit, minmax(20rem, 1fr));
  gap: 1.4rem;
  align-items: stretch;
}
.v-who__item {
  display: flex;
}
.v-who__card {
  flex: 1;
  display: flex;
  flex-direction: column;
  padding: var(--v-space-5) var(--v-space-5) var(--v-space-6);
  border: 1.5px solid var(--v-line);
  border-radius: 1.8rem;
  background: var(--v-paper);
  color: inherit;
  text-decoration: none;
  /* The prototype's .who a: the border darkens and nothing moves. The
     earlier lift on hover was this branch's own invention and the prototype
     is the source of truth, so it goes. */
  transition: border-color var(--v-t-micro) var(--v-ease);
}
.v-who__card:hover {
  border-color: var(--v-ink);
}
.v-who__card:focus-visible {
  outline: 2px solid var(--v-focus);
  outline-offset: 3px;
}
.v-who__ill {
  display: block;
  width: 5.2rem;
  margin-bottom: var(--v-space-2);
}
.v-who__name {
  font-family: var(--v-font-system);
  font-size: var(--v-text-m);
  font-weight: 600;
  margin: 0;
}
.v-who__line {
  margin: var(--v-space-2) 0 0;
  font-size: var(--v-text-base);
  line-height: 1.55;
  /* The prototype's .who span is #333, the lead tone, not the grey the
     kickers use: the card's one sentence is the argument, not a footnote.
     There is no token for that tone, so the literal stands as the prototype
     writes it. */
  color: #333;
}

/* The path line: the public deep link at the card's foot, mono because a
   URL is data. margin-top auto pins the three lines level across cards of
   unequal text, the same trade v-route__spec makes with its border. The
   arrow rides with the path rather than floating alone, so the affordance
   and the address read as one destination. */
.v-who__path {
  margin-top: auto;
  padding-top: var(--v-space-4);
  display: block;
  font-family: var(--v-font-mono);
  font-size: var(--v-text-xs);
  letter-spacing: 0.14em;
  text-transform: uppercase;
  color: var(--v-ink);
}
.v-who__go {
  display: inline-block;
  font-size: var(--v-text-s);
  transition: transform 0.4s var(--v-ease-out);
}
.v-who__card:hover .v-who__go {
  transform: translateX(6px);
}
@media (prefers-reduced-motion: reduce) {
  .v-who__card:hover .v-who__go {
    transform: none;
  }
}

/* ============================================================
   Motion contract, fase 1 (struttura). The prototype's "gesti"
   block, ported one rule at a time. Every class below is a
   hidden-until-seen state that velia.js resolves by adding
   is-in, so the same three nets that guard .v-rv guard these:
   the <noscript> block in theme.liquid, the reduced-motion rule
   at the end of this file, and the script's own fail-open path
   (?static, no IntersectionObserver, the 5s intro timer).
   Appended last so it wins at equal specificity over anything
   above that touches opacity or transform.
   ============================================================ */

:root {
  /* The prototype's timings verbatim, and one curve for all of them, so a
     stagger, a stamp and a drawn stroke decelerate together. --v-dy is the
     rise distance; the prototype's 12px is 1.2rem at Dawn's 62.5% root. */
  --v-t-micro: 200ms;
  --v-t-in: 420ms;
  --v-t-sign: 650ms;
  --v-dy: 1.2rem;
  --v-stagger: 60ms;
}

/* Group reveal. The class sits on the CONTAINER and the script observes the
   container, so a grid of cards costs one observer entry instead of eight,
   and its children rise in order: each direct child waits one stagger step
   longer than the previous one. Eight steps because the widest grid on the
   site (the hotellerie accessories list) has eight items. */
.v-rv-group > * {
  opacity: 0;
  transform: translateY(var(--v-dy));
  transition: opacity var(--v-t-in) var(--v-ease), transform var(--v-t-in) var(--v-ease);
}
.v-rv-group.is-in > * {
  opacity: 1;
  transform: none;
}
.v-rv-group.is-in > :nth-child(2) {
  transition-delay: var(--v-stagger);
}
.v-rv-group.is-in > :nth-child(3) {
  transition-delay: calc(var(--v-stagger) * 2);
}
.v-rv-group.is-in > :nth-child(4) {
  transition-delay: calc(var(--v-stagger) * 3);
}
.v-rv-group.is-in > :nth-child(5) {
  transition-delay: calc(var(--v-stagger) * 4);
}
.v-rv-group.is-in > :nth-child(6) {
  transition-delay: calc(var(--v-stagger) * 5);
}
.v-rv-group.is-in > :nth-child(7) {
  transition-delay: calc(var(--v-stagger) * 6);
}
.v-rv-group.is-in > :nth-child(8) {
  transition-delay: calc(var(--v-stagger) * 7);
}

/* Scale-in for images and cards. Slower than a text reveal on purpose: a
   photograph settling at the signature speed reads as placed, at the text
   speed it reads as popping. */
.v-st {
  opacity: 0;
  transform: scale(0.96);
  transition: opacity var(--v-t-sign) var(--v-ease), transform var(--v-t-sign) var(--v-ease);
}
.v-st.is-in {
  opacity: 1;
  transform: none;
}

/* Drawn strokes. The class goes on the svg; every stroke inside draws from
   nothing to full length. --v-len defaults to 1 because the v-arch service
   pictograms carry pathLength="1" on every stroke, which makes the dash a
   one-value affair regardless of the shape's real length. An svg whose
   strokes lack pathLength sets --v-len inline to its longest path length,
   the way the prototype did. rect is included because the magazzino icon
   is three of them. */
.v-dr path,
.v-dr circle,
.v-dr rect,
.v-dr line {
  stroke-dasharray: var(--v-len, 1);
  stroke-dashoffset: var(--v-len, 1);
  transition: stroke-dashoffset var(--v-t-sign) var(--v-ease);
}
.v-dr.is-in path,
.v-dr.is-in circle,
.v-dr.is-in rect,
.v-dr.is-in line {
  stroke-dashoffset: 0;
}

/* The label chip on a product: the stamp. Centred a little below the
   product's middle, where a real label sits on a bottle, and it lands
   AFTER the product has settled (delay 60% of the signature time), so the
   eye sees the object first and the name second, which is the order the
   whole page argues. System face, not mono: the chip carries a brand name,
   not a datum. The tracked text needs the extra left padding to sit
   optically centred, because letter-spacing adds its space after the last
   glyph only. */
.v-label {
  position: absolute;
  left: 50%;
  top: 56%;
  transform: translate(-50%, -50%) scale(1.06);
  opacity: 0;
  border: 2px solid var(--v-ink);
  border-radius: 0.8rem;
  padding: 0.5rem 1.1rem;
  padding-left: calc(1.1rem + 0.28em);
  background: var(--v-paper);
  color: var(--v-ink);
  white-space: nowrap;
  font-family: var(--v-font-system);
  font-weight: 500;
  font-size: 1.1rem;
  line-height: 1.6;
  letter-spacing: 0.28em;
  transition: opacity var(--v-t-sign) var(--v-ease), transform var(--v-t-sign) var(--v-ease),
    background var(--v-t-in) var(--v-ease), color var(--v-t-in) var(--v-ease);
  transition-delay: calc(var(--v-t-sign) * 0.6);
}
.v-label.is-in,
.is-in .v-label,
.v-label--now {
  opacity: 1;
  transform: translate(-50%, -50%) scale(1);
}
/* The peeled corner, the same mark the lockup frame carries. */
.v-label__peel {
  position: absolute;
  right: -1.5px;
  bottom: -1.5px;
  width: 0.9rem;
  height: 0.9rem;
  background: var(--v-ink);
  clip-path: polygon(100% 0, 100% 100%, 0 100%);
  border-bottom-right-radius: 0.5rem;
  transition: background var(--v-t-in) var(--v-ease);
}
/* Marchio Velia: the chip inverts to ink, the peel to paper. The body
   attribute is set by the configurator step (fase 2); velia.js keeps it at
   "tuo" until then, so today this rule never matches. */
body[data-v-brand='velia'] .v-label {
  background: var(--v-ink);
  color: var(--v-paper);
}
body[data-v-brand='velia'] .v-label__peel {
  background: var(--v-paper);
}

/* Reduced motion: everything above is shown finished, and nothing waits on
   a transition. One rule for the whole contract so a new class cannot be
   added to the motion without being added here. The label keeps its
   translate because that is its position, not its motion, and the pin
   steps all read at full strength because the fade between them IS the
   motion. .v-rv has its own rule beside its definition and is not repeated. */
@media (prefers-reduced-motion: reduce) {
  .v-rv-group > *,
  .v-st,
  .v-dr path,
  .v-dr circle,
  .v-dr rect,
  .v-dr line,
  .v-pin__steps > div {
    opacity: 1 !important;
    transform: none !important;
    stroke-dashoffset: 0 !important;
    transition: none !important;
    animation: none !important;
  }
  .v-label,
  .v-label__peel {
    opacity: 1 !important;
    transition: none !important;
    animation: none !important;
  }
  .v-label {
    transform: translate(-50%, -50%) !important;
  }
}


/* ============================================================
   Structure phase, September: additions to four existing sections
   (v-steps, v-credentials, v-cta-band, v-hero). Each block is scoped
   to a modifier the section only emits when the new setting is on, so
   every page published before this fragment renders unchanged.
   ============================================================ */

/* ------------------------------------------------------------
   Steps, one accordion per step: the preview cue
   ------------------------------------------------------------ */
/* v-accordion already prints the preview beside the title, wrapping under
   it when the row is narrow. The prototype's summary reads differently:
   the cue sits flush RIGHT ("trenta minuti", "entro 60 giorni"), which is
   what turns four closed rows into a timeline you can scan down the right
   edge. So inside the steps list the head takes the whole row, stops
   wrapping, and pushes the preview to the far side. */
.v-steps--each .v-acc__head {
  flex: 1 1 auto;
  flex-wrap: nowrap;
  justify-content: space-between;
}
.v-steps--each .v-acc__preview {
  flex: 1 0 auto;
  text-align: right;
}
/* Below 640px the cue and the title fight for one line and the title is
   the one that matters. Same breakpoint as the prototype. The words are
   still one click away in the body, so nothing is lost, only duplicated
   less. */
@media (max-width: 640px) {
  .v-steps--each .v-acc__preview {
    display: none;
  }
}

/* ------------------------------------------------------------
   Credentials, prose layout with the label macro beside it
   ------------------------------------------------------------ */
/* The words keep their 62ch measure inside the left column; the macro
   takes the right. 1.2fr against .8fr because the sentence is the payload
   and the picture is its evidence, not the other way round. */
.v-cred__grid {
  display: grid;
  gap: 3rem;
  align-items: center;
}
@media (min-width: 701px) {
  .v-cred__grid {
    grid-template-columns: 1.2fr 0.8fr;
  }
}
/* height:auto for the same reason as .v-hero__panel img: v-figure emits
   the intrinsic width and height, and the height hint would otherwise pin
   the 1254px square at its pixel size. The radius is the prototype's 16px,
   a step under the 22px it gives full-width photographs, because this one
   sits inside a column and a bigger corner there reads as a sticker. */
.v-cred__media img {
  display: block;
  width: 100%;
  height: auto;
  border-radius: 1.6rem;
}

/* ------------------------------------------------------------
   Closing band, stacked: figure above the claim, everything centred
   ------------------------------------------------------------ */
/* The two-column band (.v-cta-band--media) turns the wrap into a grid and
   sends the text left; the stack does neither. It keeps the band's own
   centred flow and only sizes the figure: 30rem, the prototype's 300px,
   and a 1rem breath before the claim. Both rules below outrank the
   two-column ones on purpose, because the media rule further up gives
   every figure a negative bottom margin so it can hang out of the band,
   and a figure that sits ABOVE the words has nothing to hang out of. */
.v-cta-band--stack .v-cta-band__media {
  width: min(30rem, 100%);
  margin-bottom: 1rem;
}
@media (min-width: 900px) {
  .v-cta-band--stack .v-cta-band__media {
    margin-bottom: 1rem;
    align-self: auto;
  }
}

/* ------------------------------------------------------------
   Hero, capped cut-out
   ------------------------------------------------------------ */
/* With a max-width on the panel the V6 rule that lets the image run 118%
   wide of its column would push it past the cap it was just given, so the
   image goes back to the panel's width and the panel centres in its
   column, which is the prototype's .group: a flex box centring an image
   capped at 520px. Only emitted when image_max is set. */
.v-hero__panel--capped {
  width: 100%;
  justify-self: center;
}
@media (min-width: 900px) {
  .v-hero__panel--capped img {
    width: 100%;
    max-width: 100%;
    margin-bottom: 0;
  }
}


/* ------------------------------------------------------------
   La scala, re-cut to the complete prototype (structure phase,
   September 2026). Four flat columns, each closed at the top by
   one ink rule: numeral, name, line, then the archetype below.
   The stair the ricarica branch drew is gone because the
   prototype has none. Replica, not interpretation.
   ------------------------------------------------------------ */

/* The prototype's lead sits 22px above the row. .v-intro's own 3.2rem is
   the roomier rhythm of the older sections, so it is pulled in here. */
.v-scala__intro {
  margin-bottom: 2.2rem;
}
.v-scala {
  list-style: none;
  padding: 0;
  margin: 0;
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(17rem, 1fr));
  gap: 1.4rem;
}
.v-scala__step {
  border-top: 1.5px solid var(--v-ink);
  padding-top: 1.4rem;
}
/* System face, not mono: the numeral is an ordinal, a label for the step,
   and the V6 rule keeps mono for figures a reader might want to compare. */
.v-scala__num {
  display: block;
  font-family: var(--v-font-system);
  font-size: 2.2rem;
  font-weight: 800;
  line-height: 1.2;
  color: var(--v-ink);
}
.v-scala__name {
  font-family: var(--v-font-system);
  font-size: var(--v-text-base);
  font-weight: 600;
  line-height: 1.6;
  margin: 0.4rem 0 0.2rem;
  color: var(--v-ink);
}
/* #333 is the prototype's body-copy grey for every step and tick line,
   darker than --v-ink-soft on purpose: these lines are the argument, not a
   footnote, so the prototype sets them one step below ink rather than at
   caption grey. A declared literal, the same one the who cards' line
   carries, because no token sits between ink and grey. */
.v-scala__line {
  margin: 0;
  font-size: var(--v-text-s);
  line-height: 1.6;
  color: #333;
}
.v-scala__ill {
  display: block;
  width: 5.4rem;
  margin-top: 0.8rem;
}

/* ------------------------------------------------------------
   La promessa bulk, re-cut to the complete prototype (structure
   phase, September 2026). The sand band is an inset panel on a
   paper section, text left and the refill photograph right, and
   the section carries no top padding because the band belongs to
   the scala above it. Replica, not interpretation.
   ------------------------------------------------------------ */

/* Flush top. The prototype runs the band straight under the scala with
   padding-top:0 and nothing ruled between them, and the branch pages hang
   their opening photograph under the hero the same way. The hairline goes
   with the padding: with no padding above it the section's border-top would
   sit directly under the previous section's last line and read as a
   printing error, the same reading the die-rule wrapper suppresses. Defined
   once here, consumed by v-bulk-promise and v-photo. */
.v-section--flush-top {
  padding-top: 0;
  border-top: 0;
}

.v-band {
  background: var(--v-sand);
  background-image: var(--v-grain-soft);
  border-radius: 2.2rem;
  padding: 3.4rem;
  /* Sand is sand whether it fills the viewport or a panel: the same
     contrast-safe grey and focus swap the full sand ground performs. */
  --v-ink-soft: var(--v-ink-soft-tinted);
  --v-focus: var(--v-focus-tinted);
}
.v-band--media {
  display: grid;
  grid-template-columns: 1.1fr 0.9fr;
  gap: 2.8rem;
  align-items: center;
}
@media (max-width: 700px) {
  .v-band--media {
    grid-template-columns: 1fr;
  }
}
.v-band__heading {
  margin: 0 0 0.8rem;
}
/* #333: the prototype's lead grey, see .v-scala__line. The two figures
   inside it are .v-num nodes and take ink on their own. */
.v-band__text {
  margin: 0;
  font-size: var(--v-text-base);
  line-height: 1.6;
  color: #333;
  max-width: 58ch;
}
.v-band__note {
  margin: 0.8rem 0 0;
  font-size: 1.3rem;
  line-height: 1.6;
  color: var(--v-ink-soft);
}
.v-band__media img {
  display: block;
  width: 100%;
  height: auto;
  border-radius: 1.6rem;
}


/* ------------------------------------------------------------
   Ricarica programmata, re-cut to the complete prototype
   (structure phase, September 2026). Three voices on an auto-fit
   grid, pictogram above, bold name, one line below, and the
   warehouse photograph full width under the row. The pictograms
   draw themselves in through .v-dr, which the motion layer owns;
   this file only sizes them. Replica, not interpretation.
   ------------------------------------------------------------ */

.v-replen {
  list-style: none;
  padding: 0;
  /* The prototype stacks 12px under the h2 and 18px over the row. .v-heading
     already carries 1.6rem, so the row adds the difference. */
  margin: 1.4rem 0 0;
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(20rem, 1fr));
  gap: 2.2rem;
}
.v-replen__ill {
  display: block;
  width: 6rem;
}
.v-replen__name {
  font-family: var(--v-font-system);
  font-size: var(--v-text-base);
  font-weight: 600;
  line-height: 1.6;
  margin: 0.8rem 0 0.2rem;
  color: var(--v-ink);
}
/* #333: the prototype's line grey, see .v-scala__line. */
.v-replen__line {
  margin: 0;
  font-size: var(--v-text-s);
  line-height: 1.6;
  color: #333;
}.v-replen__media {
  margin-top: 2.6rem;
}
.v-replen__media img {
  display: block;
  width: 100%;
  height: auto;
  border-radius: 1.6rem;
}


/* ============================================================
   Pin: "Dal prodotto al tuo nome" (v-pin.liquid), the racconto.
   The state CSS of design/round2/velia-racconto.html translated
   selector for selector onto the theme's tokens: .stage is
   .v-pin__stage, #body is .v-morph__body, .parts is .v-morph__parts,
   .label and .peel are .v-pin__label and .v-pin__peel, .badge is
   .v-morph__badge, .steps is .v-pin__steps. Same values (px into rem
   at Dawn's 62.5% root, the racconto's timings onto --v-t-in and
   --v-t-sign, its curve onto --v-ease). Lengths inside the svg stay
   in px because there they are user units of the 240 x 320 viewBox,
   not CSS pixels. Every state is keyed on data-state, which velia.js
   copies from the step in view.
   ============================================================ */

/* Two columns, the stage on the left pinned while the steps on the right
   scroll past it. align-items: start is what lets the sticky stage stick:
   a stretched grid item is as tall as the column and has nowhere to go.
   The 1.8rem top margin is the racconto's own gap under the heading. */
.v-pin {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: 4rem;
  align-items: start;
  margin-top: 1.8rem;
}

/* 16vh top plus 64vh height leaves the stage centred in the viewport with
   the same breath above and below, so the product never sits under the nav
   and never kisses the fold. Pearl because the product is drawn in ink
   line and needs a ground that is not the page's paper to read as a
   stage rather than a stray icon. */
.v-pin__stage {
  position: sticky;
  top: 16vh;
  height: 64vh;
  display: flex;
  align-items: center;
  justify-content: center;
  background: var(--v-wash);
  border-radius: 2.2rem;
}
.v-pin__scene {
  display: flex;
  align-items: flex-end;
  justify-content: center;
  position: relative;
}

/* The product. Relative so the label chip can centre itself on it; 21rem
   is the racconto's 210px. overflow visible on the svg because the badge
   circle at the top right runs close to the viewBox edge and a clipped
   tick would read as a printing error. */
.v-pin__prod {
  position: relative;
  width: 21rem;
}
.v-morph {
  width: 21rem;
  height: auto;
  color: var(--v-ink);
  overflow: visible;
  display: block;
}

/* The body outline. Unfilled through the first three steps so the liquid
   under it shows in the flask and the formats read as line drawings; at
   "Pronto" it fills paper, the bottle becoming a finished, opaque object. */
.v-morph__body {
  fill: none;
  transition: fill var(--v-t-sign) var(--v-ease);
}
.v-pin__stage[data-state='3'] .v-morph__body {
  fill: var(--v-paper);
}

/* The details of each format (pump head, tube cap, jar lid, spray nozzle)
   are hidden until the script gives the current format .show, then draw
   themselves in as a dash travelling the stroke. 300 is longer than any of
   the detail strokes, so every one starts fully hidden. */
.v-morph__parts {
  opacity: 0;
}
.v-morph__parts path,
.v-morph__parts rect {
  stroke-dasharray: 300;
  stroke-dashoffset: 300;
}
.v-morph__parts.show {
  opacity: 1;
}
.v-morph__parts.show path,
.v-morph__parts.show rect {
  animation: v-drawin 320ms var(--v-ease) forwards;
}
@keyframes v-drawin {
  to {
    stroke-dashoffset: 0;
  }
}

/* The liquid: a sand rectangle clipped to the body outline, parked below
   the viewBox and lifted 190 user units into the flask at state 0 over a
   slow 1400ms, so the flask fills as the section arrives. The group fades
   out as soon as the state leaves the formula step, before the body morphs
   to a format that a filled clip would look wrong in. */
.v-morph .liquid {
  transition: opacity var(--v-t-in) var(--v-ease);
}
.v-pin__stage:not([data-state='0']) .liquid {
  opacity: 0;
}
.v-morph .lq {
  transition: transform 1400ms var(--v-ease);
}
.v-pin__stage[data-state='0'] .lq {
  transform: translateY(-190px);
}

/* Bubbles rise in the liquid only while the chemists work (state 0), four
   of them staggered so the loop never reads as one pulse. */
.v-morph .bub {
  opacity: 0;
}
.v-pin__stage[data-state='0'] .bub {
  animation: v-bubble 2.6s var(--v-ease) infinite;
}
.v-pin__stage[data-state='0'] .b2 {
  animation-delay: 0.6s;
}
.v-pin__stage[data-state='0'] .b3 {
  animation-delay: 1.3s;
}
.v-pin__stage[data-state='0'] .b4 {
  animation-delay: 1.9s;
}
@keyframes v-bubble {
  0% {
    opacity: 0;
    transform: translateY(0);
  }
  15% {
    opacity: 0.9;
  }
  100% {
    opacity: 0;
    transform: translateY(-150px);
  }
}

/* The label chip, the racconto's own and not the shared .v-label: its
   opacity, scale, background and colour are driven by the stage's state,
   not by the reveal observer or body[data-v-brand]. Centred a little below
   the product's middle, where a real label sits. Hidden through the first
   two steps, it stamps itself on at "Poi il tuo nome" (paper) and inverts
   to ink at "Pronto". System face, not mono: it carries a name, not a
   datum. The extra left padding sits the tracked text optically centred,
   because letter-spacing adds its space after the last glyph only. */
.v-pin__label {
  position: absolute;
  left: 50%;
  top: 56%;
  transform: translate(-50%, -50%) scale(1.06);
  opacity: 0;
  border: 2px solid var(--v-ink);
  border-radius: 0.8rem;
  padding: 0.5rem 1.1rem;
  padding-left: calc(1.1rem + 0.28em);
  background: var(--v-paper);
  color: var(--v-ink);
  white-space: nowrap;
  font-family: var(--v-font-system);
  font-weight: 500;
  font-size: 1.1rem;
  line-height: 1.6;
  letter-spacing: 0.28em;
  transition: opacity var(--v-t-sign) var(--v-ease), transform var(--v-t-sign) var(--v-ease),
    background var(--v-t-in) var(--v-ease), color var(--v-t-in) var(--v-ease);
}
.v-pin__stage[data-state='2'] .v-pin__label,
.v-pin__stage[data-state='3'] .v-pin__label {
  opacity: 1;
  transform: translate(-50%, -50%) scale(1);
}
.v-pin__stage[data-state='3'] .v-pin__label {
  background: var(--v-ink);
  color: var(--v-paper);
}
.v-pin__stage[data-state='3'] .v-pin__label .v-pin__peel {
  background: var(--v-paper);
}
/* The peeled corner, the same mark the lockup frame carries. */
.v-pin__peel {
  position: absolute;
  right: -1.5px;
  bottom: -1.5px;
  width: 0.9rem;
  height: 0.9rem;
  background: var(--v-ink);
  clip-path: polygon(100% 0, 100% 100%, 0 100%);
  border-bottom-right-radius: 0.5rem;
}

/* The badge: a paper disc with a tick, top right of the product. Faded in
   at "Pronto" and its strokes drawn in 200ms later, so the disc lands first
   and the tick is written on it. 400 is longer than the circle's
   circumference, so the stroke starts fully hidden. */
.v-morph__badge {
  opacity: 0;
  transition: opacity var(--v-t-in) var(--v-ease);
}
.v-morph__badge path,
.v-morph__badge circle {
  stroke-dasharray: 400;
  stroke-dashoffset: 400;
  transition: stroke-dashoffset var(--v-t-sign) var(--v-ease) 200ms;
}
.v-pin__stage[data-state='3'] .v-morph__badge {
  opacity: 1;
}
.v-pin__stage[data-state='3'] .v-morph__badge path,
.v-pin__stage[data-state='3'] .v-morph__badge circle {
  stroke-dashoffset: 0;
}

/* Each step is a viewport-tall room so only one is in view at a time,
   which is what the .6 threshold in velia.js relies on. Dimmed until the
   observer hands it .is-on; the fade is the same in-curve as every other
   reveal on the page. */
.v-pin__steps > div {
  min-height: 58vh;
  display: flex;
  flex-direction: column;
  justify-content: center;
  opacity: 0.28;
  transition: opacity var(--v-t-in) var(--v-ease);
}
.v-pin__steps > div.is-on {
  opacity: 1;
}
.v-pin__title {
  font-family: var(--v-font-system);
  font-size: 2rem;
  font-weight: 800;
  letter-spacing: -0.01em;
  color: var(--v-ink);
  margin: 0 0 0.6rem;
}
.v-pin__text {
  font-family: var(--v-font-system);
  font-size: var(--v-text-base);
  line-height: 1.6;
  color: #333;
  max-width: 44ch;
  margin: 0;
}

/* One column below 700px, but the stage keeps sticking. The racconto lets
   it go static here and gives the steps no height, so on a phone all four
   sit in view at once, the observer lights the last one and the stage
   freezes on "Pronto" while the reader is still on the formula. The buyer
   is a salon owner on a phone (CLAUDE.md), so the gesture has to survive
   there: the stage pins under the nav at a third of the screen, the steps
   stay tall enough that one at a time clears the 60% threshold, and the
   product shrinks to fit. This is the one rule of the racconto's CSS that
   is deliberately not copied (5 September). */
@media (max-width: 700px) {
  /* Block flow, not a one-column grid: a sticky grid item can only travel
     inside its own row, and the stage's row is exactly its own height. In
     block flow it travels the whole .v-pin, past all four steps. */
  .v-pin {
    display: block;
  }
  .v-pin__stage {
    position: sticky;
    top: calc(var(--v-nav-h, 6.4rem) + 1.2rem);
    height: 34vh;
    z-index: 1;
  }
  .v-pin__prod,
  .v-morph {
    width: 16rem;
  }
  .v-pin__steps > div {
    min-height: 56vh;
    justify-content: flex-start;
    padding: 2.4rem 0 1.8rem;
  }
}
/* The stage cannot stick inside .v-section's overflow:hidden: a hidden
   overflow makes the section the sticky element's scrollport, and a section
   that does not scroll pins nothing, so the stage simply scrolled away with
   the steps (measured: top -386px at step two). overflow:clip clips the same
   way without creating a scroll container, so the viewport is the scrollport
   again and the stage travels the steps as designed. Browsers without clip
   keep the hidden fallback and lose only the pin. (5 September.) */
@supports (overflow: clip) {
  .v-section--pin {
    overflow: clip;
  }
}

/* Reduced motion: the bubbles stop (the racconto's own rule) and every
   step reads at full strength with no fade. The morph and the label stamp
   stay: they are the content of the section, not decoration on it, and
   each runs once per step change rather than looping. */
@media (prefers-reduced-motion: reduce) {
  .v-morph .bub {
    animation: none !important;
  }
  }


/* ============================================================
   Taste: "Un assaggio del configuratore" (v-taste.liquid).
   ============================================================ */

/* Stage left, choice right, vertically centred on each other so the
   bottle sits level with the middle of the two cards. The container is
   the rv-group: the two columns rise one after the other. */
.v-taste {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: 3.4rem;
  align-items: center;
}

/* Same pearl stage as the pin, so the two bottles read as the same object
   seen twice, not as two illustrations. Fixed minimum height so the stage
   does not shrink when a card's text wraps shorter. */
.v-taste__stage {
  background: var(--v-wash);
  border-radius: 2.2rem;
  min-height: 34rem;
  display: flex;
  align-items: center;
  justify-content: center;
}
.v-taste__prod {
  position: relative;
  width: 20rem;
  margin: 0 auto;
}
.v-taste__ill {
  display: block;
  width: 100%;
  height: auto;
  color: var(--v-ink);
}

/* The choice column is a flex column, so the button and the flag stretch
   to the column's width exactly as the prototype's do. Not a bug. */
.v-taste__choice {
  display: flex;
  flex-direction: column;
  gap: 1rem;
}

/* Option cards are real buttons (fase 2 gives them handlers), styled as
   the who-cards are: hair border, ink on hover, pearl when pressed.
   font: inherit undoes the UA button face; the colour is set because
   Safari paints buttons in its own grey otherwise. */
.v-taste__opt {
  text-align: left;
  border: 1.5px solid var(--v-line);
  border-radius: 1.8rem;
  padding: 1.6rem 1.8rem;
  background: var(--v-paper);
  color: var(--v-ink);
  font: inherit;
  line-height: 1.6;
  cursor: pointer;
  transition:
    border-color var(--v-t-micro, 200ms) var(--v-ease),
    background var(--v-t-micro, 200ms) var(--v-ease);
}
.v-taste__opt b {
  display: block;
  font-weight: 600;
  font-size: 1.6rem;
}
.v-taste__opt span {
  font-size: 1.4rem;
  color: #333;
}
.v-taste__opt:hover {
  border-color: var(--v-ink);
}
.v-taste__opt[aria-pressed='true'] {
  border-color: var(--v-ink);
  background: var(--v-wash);
}
.v-taste__opt:focus-visible {
  outline: 2px solid var(--v-focus);
  outline-offset: 3px;
}

/* The name field, hidden in fase 1. display:none is the prototype's
   initial state, not a placeholder: the block only opens when the
   configurator script (fase 2) gives it .is-on, and that rule is here
   already so wiring it is a script change, not a CSS one. */
.v-taste__input {
  display: none;
  flex-direction: column;
  gap: 0.6rem;
  padding: 0.4rem 0.6rem 0.8rem;
}
.v-taste__input.is-on {
  display: flex;
}
.v-taste__input label {
  font-family: var(--v-font-system);
  font-size: 1.3rem;
  color: var(--v-ink-soft);
}
.v-taste__input input {
  font: inherit;
  font-size: 1.6rem;
  padding: 1rem 1.2rem;
  border: 1.5px solid var(--v-ink);
  border-radius: 1rem;
  background: var(--v-paper);
  color: var(--v-ink);
}
.v-taste__input input:focus-visible {
  outline: 2px solid var(--v-focus);
  outline-offset: 3px;
}.v-taste__go {
  margin-top: 1rem;
}

@media (max-width: 700px) {
  .v-taste {
    grid-template-columns: 1fr;
  }
  .v-taste__stage {
    min-height: 26rem;
  }
}


/* ============================================================
   Lines: the three hotellerie kit lines as cards (v-lines).
   The prototype's .linee/.linea, px to rem. The card's tone is the range's
   own reading (paper, pearl, ink: the cards climb the fascia), so it is a
   per-card modifier and not a section ground. The two greys on the rows
   and swatches are rgba on purpose: the same 1px line has to read on
   paper, on pearl and on ink, and a token colour would vanish on one of
   the three. The line's name is a heading for the outline, so its house
   heading margin is reset to the card's own rhythm.
   ============================================================ */
.v-lines {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(23rem, 1fr));
  gap: 1.6rem;
  list-style: none;
  margin: 0;
  padding: 0;
}
.v-linea {
  border: 1px solid var(--v-line);
  border-radius: 2.2rem;
  padding: 2.2rem 2rem 1.8rem;
  overflow: hidden;
  background: var(--v-paper);
  color: var(--v-ink);
}
.v-linea--pearl {
  background: var(--v-wash);
}
.v-linea--dark {
  background: var(--v-ink);
  color: var(--v-paper);
  border-color: var(--v-ink);
}
.v-linea__img {
  display: block;
  width: 100%;
  height: auto;
  margin: 0 0 1rem;
}
.v-linea__name {
  font-family: var(--v-font-system);
  font-weight: 800;
  font-size: 1.8rem;
  letter-spacing: 0.02em;
  line-height: 1.3;
  color: inherit;
  margin: 0;
}
.v-linea__role {
  font-size: 1.3rem;
  opacity: 0.7;
  margin: 0;
}
.v-linea__voce {
  font-size: 1.35rem;
  padding: 0.8rem 0;
  border-top: 1px solid rgba(128, 128, 128, 0.3);
  margin: 0;
}
.v-linea__voce--sw {
  margin-top: 1rem;
}
.v-linea__sw {
  display: inline-block;
  width: 1.8rem;
  height: 1.8rem;
  border-radius: 0.5rem;
  border: 1px solid rgba(128, 128, 128, 0.4);
  vertical-align: middle;
  margin-right: 0.4rem;
}

/* ============================================================
   List: a titled two-column ruled list (v-list).
   The prototype's .lst, px to rem. Stacking: the prototype keeps several
   lists in one section with 34px between a list and the next heading.
   Here each list is a section, so the stacked modifier drops the
   section's own top padding and hairline and pulls it up over the
   previous section's bottom padding, which resolves to this element's own
   --v-section-pad (7.2rem, 11.2rem above 750px) and so tracks the
   breakpoint by itself. The lists share a ground, so the overlap paints
   nothing. #333 is the prototype's body grey for list text, the same
   value the "chi sei" lines carry.
   ============================================================ */
.v-list-section--stacked {
  margin-top: calc(3.4rem - var(--v-section-pad));
  padding-top: 0;
  border-top: 0;
}
.v-list__note {
  font-family: var(--v-font-system);
  font-size: 1.4rem;
  color: var(--v-ink-soft);
  margin: 0 0 1rem;
}
.v-list {
  columns: 1;
  column-gap: 3rem;
  font-size: 1.5rem;
  color: #333;
  list-style: none;
  margin: 0;
  padding: 0;
}
@media (min-width: 600px) {
  .v-list {
    columns: 2;
  }
}
.v-list__item {
  padding: 0.6rem 0;
  border-top: 1px solid var(--v-line);
  break-inside: avoid;
}

/* ============================================================
   Photo: one full-width still in the page column (v-photo).
   The figure is the sizing box, so the azienda cut-out can be capped and
   centred by an inline max-width without the image knowing. Three radii,
   because three prototype images: the two rounded panels at 22px and the
   cut-out on paper, which has no corner to round. v-section--flush-top is
   the bulk band's rule and is not repeated here.
   ============================================================ */
.v-photo {
  margin: 0;
}
.v-photo--capped {
  margin-inline: auto;
}
.v-photo__img {
  display: block;
  width: 100%;
  height: auto;
}
.v-photo--r22 .v-photo__img {
  border-radius: 2.2rem;
}
.v-photo--r16 .v-photo__img {
  border-radius: 1.6rem;
}
.v-photo--r0 .v-photo__img {
  border-radius: 0;
}


/* ============================================================
   Static site layer, September 2026. The pages left Shopify and are
   plain HTML now: the section wrappers, the Dawn header and the
   commerce scripts are gone, and this block carries what the theme
   used to get from the platform, plus the first-pass fixes of the
   decoupled site (tap targets, focus rings, the catalogue overview,
   the form's mail confirmation).
   ============================================================ */

/* The nav sticks through its wrapper, whose containing block is the body.
   Sticking .v-nav itself never worked: its containing block was only as
   tall as the bar. */
.v-nav-wrap {
  position: sticky;
  top: 0;
  z-index: 50;
}

/* Skip link: visually hidden until it takes focus, then a real button
   pinned under the nav so it is never behind the sticky bar. */
.v-skip {
  position: absolute;
  left: -999rem;
  top: 0;
  z-index: 60;
}
.v-skip:focus {
  left: var(--v-page-pad);
  top: calc(var(--v-nav-h) + var(--v-space-2));
  outline: 2px solid var(--v-focus);
  outline-offset: 2px;
}

/* Current page in the nav: the underline the hover draws, held in ink. */
.v-nav__link[aria-current='page'] {
  border-bottom-color: var(--v-ink);
}
.v-nav__menu a[aria-current='page'] {
  font-weight: 600;
}

/* Tap targets. Footer links and the nav anchors were text lines with no
   padding, 20px tall on a phone. 4.4rem is the WCAG 2.5.8 floor. */
.v-nav__link {
  display: none;
  min-height: 4.4rem;
  align-items: center;
}
@media (min-width: 900px) {
  .v-nav__link {
    display: inline-flex;
  }
}
.v-nav__menu a:not(.v-btn) {
  min-height: 4.4rem;
  display: flex;
  align-items: center;
}
.v-nav__menu a.v-btn {
  min-height: 4.4rem;
}
.v-nav__brand {
  display: inline-flex;
  align-items: center;
  min-height: 4.4rem;
}
.v-nav .v-nav__cta {
  min-height: 4.4rem;
}
/* Radio and checkbox rows: the label is the target, so it takes the height. */
.v-radio,
.v-field--consent {
  min-height: 4.4rem;
}
.v-field--consent {
  align-items: center;
}
.v-field--consent input {
  margin-top: 0;
}
.v-field--radio fieldset {
  gap: 0;
}
.v-footer__nav a,
.v-footer__legal-nav a {
  display: inline-flex;
  align-items: center;
  min-height: 4.4rem;
  padding-inline: var(--v-space-1);
}
.v-card__cta {
  display: inline-flex;
  align-items: center;
  min-height: 4.4rem;
}

/* Focus rings on every link that lacked one. The Dawn base only styled
   its own components. */
.v-nav__brand:focus-visible,
.v-nav__link:focus-visible,
.v-nav__menu a:focus-visible,
.v-footer__nav a:focus-visible,
.v-footer__legal-nav a:focus-visible,
.v-card__cta:focus-visible,
.v-section a:not([class]):focus-visible {
  outline: 2px solid var(--v-focus);
  outline-offset: 2px;
  border-radius: var(--v-radius);
}

/* Catalogue overview: the six families on the ruled grid, each cell led by
   its packaging archetype rather than a placeholder monogram, and closed by
   the link to the form. Same cells as the rest of the grid; the link is
   pinned to the foot so six of them line up. */
.v-cell--fam {
  display: flex;
  flex-direction: column;
  padding: var(--v-space-6) var(--v-space-5) var(--v-space-5);
}
.v-cell__idx {
  font-family: var(--v-font-mono);
  font-size: var(--v-text-xs);
  letter-spacing: 0.1em;
  color: var(--v-ink-soft);
  margin: 0 0 var(--v-space-4);
}
.v-cell__arch {
  width: 6.4rem;
  margin: 0 0 var(--v-space-4);
}
.v-cell--fam .v-cell__title {
  font-size: var(--v-text-m);
}
.v-cell--fam .v-cell__text {
  font-size: var(--v-text-base);
  line-height: 1.6;
  max-width: 34ch;
}
.v-cell--fam .v-card__cta {
  margin-top: auto;
  padding-top: var(--v-space-5);
  align-self: flex-start;
}

/* Sections that carry a numbers strip after the intro. */
.v-stats__grid + .v-intro,
.v-intro + .v-stats__grid {
  margin-top: var(--v-space-2);
}

/* The mail confirmation under the form. The static site composes a mailto
   on submit; this line tells the visitor what just happened and gives the
   address in plain text for the case where nothing opened. */
.v-form__status {
  border: 1px solid var(--v-ink);
  border-radius: var(--v-radius);
  padding: var(--v-space-4) var(--v-space-5);
  font-size: var(--v-text-s);
  line-height: 1.6;
  margin: 0;
  max-width: 68rem;
}
.v-form__status:focus-visible {
  outline: 2px solid var(--v-focus);
  outline-offset: 2px;
}

/* Contact page: the address line under the intro, mono because it is data. */
.v-contact__mail {
  font-family: var(--v-font-mono);
  font-size: var(--v-text-base);
  margin: calc(var(--v-space-6) * -1) 0 var(--v-space-6);
}
.v-contact__mail a {
  color: currentColor;
  text-underline-offset: 0.25em;
}
.v-kv + .v-cta-band__actions {
  margin-top: var(--v-space-6);
}
