/* ============================================================
   LAXMI DIAMONDS BENGALURU
   Revision 2, light and image-led. Every value resolves to DESIGN.md.
   ============================================================ */

:root{
  /* BRAND. The brand book's Palette Summary, which supersedes the first
     colour card. Four colours, and the book states what each one is for.
        Dark Burgundy #5A1E2D  backgrounds
        Wine Red      #8B3243  titles and dots
        Soft Beige    #EDE2C8  buttons, action areas, the logo, the light ground
        Premium Gold  #B3862D  buttons and action areas

     GOLD IS A WORKING COLOUR AGAIN. Revision 34 held it back to the mark,
     which was right for the first card and wrong for the book: the book lists
     it as one of four and puts buttons and action areas in gold or beige. It
     is a mid tone, luminance 0.268, so it is a FILL and never a word: gold on
     beige is 2.56:1 and gold on burgundy is 3.84:1, which is large text only.
     A gold button carries the near black label at 4.94:1.

     THE ACCENT IS STILL TWO TOKENS, for the same forced reason as in 34. Wine
     Red on Dark Burgundy is 1.59:1. --accent is Wine and is the title and dot
     colour on a light ground at 6.19:1; --accent-soft is Beige and does that
     job on burgundy at 9.84:1.

     UTILITIES ARE NEUTRAL, AND THAT IS A CORRECTION. Revision 34 tinted every
     scrim, shadow, hairline and secondary text string burgundy, which read as
     pink over the whole page. Fades, drop shadows, outlines, overlays and
     secondary copy are black and white at opacity. A palette colour appears
     only where something is actually PAINTED in it. */
  --burgundy:#5A1E2D;
  --wine:#8B3243;
  --beige:#EDE2C8;
  --gold:#B3862D;

  --primary:#5A1E2D;
  --primary-deep:#5A1E2D;
  --primary-raised:#6A2534;
  --accent:#8B3243;
  --accent-soft:#EDE2C8;
  /* the diamond section only: dark, with the burgundy still in it, so the
     light rays have something to be light against */
  --band-ground:#1E0A11;
  /* Not an accent, and never text. It is the material the seals are struck
     in, and it is a token so the one rule that reads it is not carrying a
     raw hex. */
  --silver:#C9CDD6;

  /* page */
  /* WHITE, and Soft Beige is demoted to what the book calls it: a button and
     action colour. As a full page ground the beige tinted every photograph on
     the page and left the burgundy panels reading as the only white space,
     which is backwards. The panel tints are near-white neutrals so a tinted
     band is still a band without becoming a second ground. */
  --paper:#FFFFFF;
  --panel:#F7F5F2;
  --panel-warm:#FAF9F7;
  --ink:#21201C;
  --ink-muted:#4C4840;
  --ink-deep:#5A1E2D;
  --ink-raised:#6A2534;
  --title:#8B3243;
  --on-accent:#EDE2C8;
  /* the primary button's label. See the button block for the measured figure. */
  --on-gold:#FFFFFF;
  --on-beige:#21201C;
  --on-ink:#EDE2C8;
  --outline:rgba(0,0,0,.12);
  --outline-strong:rgba(0,0,0,.22);
  --outline-light:rgba(255,255,255,.16);

  /* spacing scale: 4 8 12 16 24 32 48 64 96 128 */
  --s-xs:4px;  --s-sm:8px;  --s-1:12px; --s-md:16px;
  --s-lg:24px; --s-xl:32px; --s-2xl:48px;
  --s-3xl:64px; --s-4xl:96px; --s-section:128px;

  /**
   * The floating bar's height, declared once.
   *
   * It was written in three places and GUESSED in four more: the anchor scroll
   * margin assumed 64 at every width, the phone menu sheet assumed 56 against a
   * 60 bar and the tablet sheet assumed 60 against a 64 bar, so the sheet
   * opened underneath the bar it hangs from and anchored links landed behind
   * it. Anything that has to sit clear of the bar reads this.
   */
  --nav-h:72px;

  /* one gutter for navbar and every section */
  --gutter:32px;
  --container:1400px;

  /* shape lock */
  --radius:4px; --radius-round:50%;

  /* motion */
  --ease:cubic-bezier(.16,1,.3,1);
  /* a longer, gentler deceleration for the hero wipe and the picture
     settle, where --ease arrives too abruptly at this duration */
  --ease-soft:cubic-bezier(.22,.61,.24,1);
  --dur-hover:400ms;
  --dur-reveal:900ms;

  /**
   * The heading typewriter, declared here and nowhere else.
   *
   * These three used to be literals in the rule below AND constants in
   * headings.js, which is two declarations of one number: the script's only job
   * with them is to know when the line has finished so it can stop the dot
   * pulsing, and the moment the two disagree the dot stops early or late. The
   * script reads them off this block now.
   *
   * THESE ARE PER LETTER. They were per word, at 120ms, and a six word
   * heading at 120ms is six discrete pops rather than an entrance: each word
   * lands, the line stops, the next one lands. Under about 100ms two events
   * fuse into one, and that is not a floor to stay above here, it is the
   * effect being asked for. At 22ms a letter cascade is not read as letters
   * arriving at all, it is read as light crossing the line once, left to
   * right, and --tw-run at fifteen times the step is what keeps it a sweep
   * instead of a queue.
   *
   * The cap is in letters, so a six word heading of 33 characters takes
   * 33 * 22 + 340, about 1.1s, and nothing on the page can run past
   * 46 * 22 + 340, about 1.35s.
   */
  --tw-step:22ms;    /* between letters */
  --tw-run:340ms;    /* each letter's own arrival */
  --tw-cap:46;       /* so a long heading cannot run away */
}

/**
 * THE PHONE HAS ITS OWN SCALE, NOT THE DESKTOP ONE.
 *
 * --gutter and --s-section stepped down here already. The three large steps did
 * not, and that was the whole of the mobile spacing problem: 36 declarations
 * across the stylesheet reach for --s-2xl, --s-3xl or --s-4xl directly, so 48,
 * 64 and 96 of air landed unchanged on a 390 screen. The section rhythm is the
 * worst of it, because it was set optically from a debug pass at 1681x1064 and
 * every one of those measurements is a desktop measurement.
 *
 * Stepping the tokens rather than writing 36 overrides is what keeps the
 * optical work intact: the corrections were made against each other, so scaling
 * the whole set preserves every relationship in them. Nothing is normalised
 * back to one value, which that pass explicitly warned against.
 *
 * Roughly half of desktop, and still strictly ordered, 24 < 32 < 48 < 64, with
 * every value on the scale.
 */
@media (max-width:900px){ :root{ --gutter:24px; --s-section:96px; --nav-h:64px; } }
@media (max-width:600px){
  :root{
    --gutter:16px;
    --s-section:64px;
    --nav-h:60px;
    --s-2xl:24px;   /* 48 on desktop */
    --s-3xl:32px;   /* 64 */
    --s-4xl:48px;   /* 96 */
  }
}

*,*::before,*::after{box-sizing:border-box}
html{scroll-behavior:smooth}
/* every anchor target clears the floating bar */
:target,section[id],div[id]{scroll-margin-top:calc(var(--s-md) + var(--nav-h) + var(--s-lg))}
@media (prefers-reduced-motion:reduce){ html{scroll-behavior:auto} }

body{
  margin:0;
  background:var(--paper);
  color:var(--ink);
  font-family:Manrope,system-ui,-apple-system,"Segoe UI",sans-serif;
  font-size:16px;
  line-height:1.7;
  -webkit-font-smoothing:antialiased;
  /* NO overflow-x here. A horizontal guard on body computes overflow-y to auto,
     which makes body its own scroll container: position:sticky then has nothing
     to stick to and animation-timeline:view() resolves against a box that never
     scrolls, so the hero froze at 82%% and the craft stage stopped pinning.
     Nothing on this page overflows horizontally, the marquee rails clip
     themselves, so the guard was buying nothing and cost both effects. */
}

img{max-width:100%;height:auto;display:block}
a{color:inherit;text-decoration:none}
h1,h2,h3,h4,p,figure,blockquote{margin:0}
ul,ol{margin:0;padding:0;list-style:none}

::selection{background:var(--ink);color:var(--on-ink)}
:focus-visible{outline:2px solid var(--ink);outline-offset:3px}

.shell{
  width:100%;
  max-width:var(--container);
  margin-inline:auto;
  padding-inline:var(--gutter);
}
.section{padding-block:var(--s-section)}

/* ---------- type ---------- */
.d-xl,.d-lg,.d-md,.figure{font-family:"Cormorant Garamond",Georgia,serif;font-weight:300}
/* TITLES ARE WINE, BODY COPY IS NEUTRAL. The brand book gives Wine Red to
   titles and dots. Body and secondary copy stay a neutral near black, which
   is what keeps the page from reading pink: a wine heading over black body
   copy is a brand colour used once, where a wine heading over wine-tinted
   body copy is a wash. 6.19:1 on paper, 5.42:1 on panel. `.is-dark` already
   overrides all of these to `--on-ink`, since wine on burgundy is 1.59:1. */
.d-xl,.d-lg,.d-md,h1,h2,h3{color:var(--title)}
.d-xl{font-size:clamp(40px,5.4vw,82px);line-height:1.04;letter-spacing:-.02em}
.d-lg{font-size:clamp(32px,4vw,56px);line-height:1.1;letter-spacing:-.015em}
.d-md{font-size:clamp(22px,2.4vw,32px);line-height:1.2;font-weight:400}
.figure{font-size:clamp(44px,4.8vw,68px);line-height:1;font-variant-numeric:lining-nums tabular-nums}

.lead{font-size:18px;line-height:1.7;color:var(--ink-muted)}
.body{font-size:16px;line-height:1.7;color:var(--ink-muted)}
.small{font-size:14px;line-height:1.6;color:var(--ink-muted)}
.measure{max-width:60ch}
.center{text-align:center}
.center .measure{margin-inline:auto}

.d-xl em,.d-lg em,.d-md em{
  font-style:italic;font-weight:300;line-height:1.1;
  padding-bottom:4px;display:inline-block;
}
.label{
  font-size:12px;font-weight:500;letter-spacing:.14em;
  text-transform:uppercase;color:var(--ink-muted);
}
.accent{color:var(--accent)}

/* ---------- buttons ---------- */
.btn{
  display:inline-flex;align-items:center;gap:var(--s-1);
  padding:16px var(--s-xl);
  border-radius:var(--radius);
  font-family:Manrope,sans-serif;font-size:15px;font-weight:600;
  line-height:1.2;letter-spacing:.01em;white-space:nowrap;
  border:1px solid transparent;cursor:pointer;
  transition:background var(--dur-hover) var(--ease),
             border-color var(--dur-hover) var(--ease),
             transform var(--dur-hover) var(--ease),
             color var(--dur-hover) var(--ease);
}
.btn i{font-size:18px;transition:transform var(--dur-hover) var(--ease)}

/* TWO STYLES, AND THEY LOOK THE SAME ON EITHER GROUND.
   There used to be three, and effectively four: a gold fill, an outline, a
   separate fill for dark grounds, and the outline inverting under `.is-dark`.
   Four appearances for two jobs is why the page read as several different
   button systems. Primary is gold, secondary is beige, and neither changes
   when the ground does.

   THE PRIMARY LABEL IS WHITE ON GOLD, WHICH MEASURES 3.30:1. That is below
   the 4.5:1 a 15px label needs and it is a deliberate, instructed choice, not
   an oversight. Premium Gold is a mid tone at luminance 0.268, so no light
   label clears AA on it: beige is worse at 2.56:1. The fix, if it is ever
   wanted, is one value: gold at 0.80 is #8F6B24 and carries white at 4.89:1
   while still reading as the same colour. */
.btn-primary,.btn-onink{background:var(--gold);color:var(--on-gold);border-color:transparent}
.btn-primary:hover,.btn-onink:hover{transform:translateY(-2px)}
.btn-primary:hover i,.btn-onink:hover i{transform:translateX(4px)}
.btn-primary:active,.btn-onink:active{transform:translateY(0) scale(.99)}
.btn-primary[disabled],.btn-onink[disabled]{opacity:.45;cursor:not-allowed;transform:none}

/* Secondary. The beige fill separates from white by only 1.29:1, so the
   hairline is doing the work of saying this is a control rather than a
   paragraph with a background. Its label is 12.67:1. */
.btn-ghost{background:var(--beige);color:var(--on-beige);border-color:rgba(0,0,0,.16)}
.btn-ghost:hover{border-color:rgba(0,0,0,.34);transform:translateY(-2px)}
.btn-ghost:hover i{transform:translateX(4px)}
.btn-ghost:active{transform:translateY(0) scale(.99)}

@media (max-width:600px){ .btn{width:100%;justify-content:center} }

/* ---------- nav: a floating bar ----------
   Detached from the top edge and inset by the page gutter, so it reads as an
   object over the page rather than a band welded to it. The outer element is
   pointer-transparent, so the gutter either side of the bar never eats clicks
   meant for the page beneath. */
/* A solid bar in the brand primary. It was translucent and blurred, which meant
   the browser recomposited a full-width blur over whatever scrolled beneath it
   on every frame, and the bar's own contrast changed with the content behind.
   A filled bar is one paint, and the logo reads on it at any scroll position. */
/* once the page has moved, the bar lifts off the content a little further */
/* raised, not deeper. The closing panel is --primary and the footer is
   --primary-deep, which is the exact tone the bar had, so a filled bar
   disappeared into both. --primary-raised keeps it a distinct plane over every
   ground on the page, dark or light. */


/* the gold fill on a dark ground, which is the one place gold is allowed to
   shout. On the old pale bar this slot carried the dark button instead. */

/* ---------- the bezel arch ---------- */
.arch{-webkit-clip-path:url(#bezel-arch);clip-path:url(#bezel-arch)}
.arch-m{display:none}
/* A tall narrow box stretches objectBoundingBox coordinates, so the desktop
   ogee becomes a steep spike on a phone with its shoulders dragged inward.
   The phone gets its own path: the turn starts higher up the side and the
   shoulders run out to a tenth of the width from each edge. */
@media (max-width:700px){
  .arch{-webkit-clip-path:url(#bezel-arch-m);clip-path:url(#bezel-arch-m)}
  .arch-d{display:none}
  .arch-m{display:block}
}
.arch-frame{position:relative}
.arch-line{
  position:absolute;inset:0;width:100%;height:100%;
  color:var(--ink);pointer-events:none;
}
/* the opacity sits on the paths rather than the svg, which is where it was,
   so the outline reads the same as it always has */
.arch-d,.arch-m{stroke-opacity:.32}

/**
 * THE BORDER TRAIL: A GOLD LIGHT THAT TRAVELS THE OGEE'S EDGE.
 *
 * Two elements, and the outer one is the whole trick. `.arch-trail` is a
 * static frame masked to a narrow band lying on the ogee's own outline;
 * `.arch-trail i` is a soft round glow that rides the same curve on a motion
 * path. Only the part of the glow that falls on the band is ever painted, so
 * what a reader sees is a short lit stretch of the BORDER, moving. The circle
 * is the light source and is never itself visible.
 *
 * Without the mask it was exactly that: a gold disc sliding around inside the
 * picture. The reference component does the same thing with
 * `mask-clip:padding-box,border-box` and `mask-composite:intersect`, which
 * leaves a rectangle's border ring; a rectangle has a border box to subtract
 * and an ogee does not, so the band here is the path stroked into an SVG mask
 * instead. Same idea, one shape harder.
 *
 * THE MASK IS RASTERISED ONCE AND THE LIGHT IS A GRADIENT PAINTED ONCE. The
 * version before this lagged because it was a dashed SVG stroke under
 * `filter:drop-shadow()`, and a filter over a path whose geometry changes
 * every frame has to be recomputed every frame across the arch's whole box.
 * Nothing here is filtered and nothing here is redrawn: `offset-distance` is
 * the only property that moves.
 */
.arch-trail{
  position:absolute;inset:0;
  pointer-events:none;
  z-index:3;
  opacity:0;
  /* the band itself is set by the script, from the outline already in the
     document, so the curve is never written down twice */
  -webkit-mask-repeat:no-repeat;mask-repeat:no-repeat;
  -webkit-mask-size:100% 100%;mask-size:100% 100%;
}
.arch-trail i{
  --trail-size:220px;
  position:absolute;top:0;left:0;
  display:block;
  width:var(--trail-size);height:var(--trail-size);
  border-radius:var(--radius-round);
  /**
   * Saturated gold, not a white hot core. A light source is white in the
   * middle, which is true and is why the pattern is usually shown on a dark
   * card; this picture rotates through three frames and one of them is a
   * workshop ceiling, where cream over near-white is nothing at all.
   */
  background:radial-gradient(circle,
    rgba(179,134,45,1)   0%,
    rgba(179,134,45,.82) 20%,
    rgba(179,134,45,.42) 44%,
    rgba(179,134,45,.14) 64%,
    rgba(179,134,45,0)   80%);
  offset-rotate:0deg;
  offset-distance:0%;
  will-change:offset-distance;
}
@media (prefers-reduced-motion:no-preference){
  .arch-trail.is-on{
    opacity:1;
    transition:opacity 600ms var(--ease);
  }
  .arch-trail.is-on i{
    animation:archTrail 7s linear infinite;
    transition:width 420ms var(--ease),height 420ms var(--ease);
  }
  /* Reactive, and still travelling. The pointer lengthens the lit stretch by
     enlarging the light behind the band; driving the position from the pointer
     would mean writing offset-distance from script every frame, which is the
     one thing this shape exists to avoid. */
  @media (hover:hover) and (pointer:fine){
    .hero-media:hover .arch-trail.is-on i{--trail-size:340px}
  }
}
/* the light steps with the shape it is riding, or a 220px one on a 375px
   viewport lights half the arch at once */
@media (max-width:1000px){ .arch-trail i{--trail-size:150px} }
@media (max-width:700px){ .arch-trail i{--trail-size:120px} }

@keyframes archTrail{
  from{offset-distance:0%}
  to{offset-distance:100%}
}
/* the brand mark, auto-traced from the client's logo file rather than
   redrawn by hand: the real thing is a continuous-line knot whose petal tails
   flow into the grid, which an approximation does not reproduce. */
.brand-mark{width:60px;height:60px;display:block;margin-inline:auto;color:var(--beige)}

/* ---------- hero ---------- */
.hero{
  /* below the bar, then air. It was section rhythm plus 16, which
     cleared the bar at desktop by luck and by 4px on a phone once
     --s-section halved and the bar did not. */
  padding-top:calc(var(--s-md) + var(--nav-h) + var(--s-3xl));
  padding-bottom:var(--s-2xl);
  /* .hero-stage is positioned against this and is its offsetParent */
  position:relative;
  /* The media's own height, declared once so the breakpoint below overrides a
     token rather than the rule, and anything else that needs the picture's
     height reads it instead of restating the clamp. */
  --hero-media-h:clamp(420px,86vh,880px);
}

/* The hero ground is paper and nothing else. It carried twelve drifting
   light shafts until 2026-09-09; see DESIGN.md for why they went. */
.hero > .shell{position:relative;z-index:1}
/* the centred hero left these behind: margin-inline:auto indented the lead
   away from the headline once the block became a left column */
.hero h1{margin-bottom:0}
.hero .lead{margin-bottom:0;margin-inline:0}

/* The stage sits outside .shell, so it is already viewport width.
   100vw is deliberately NOT used: it counts the scrollbar and pushes 8px of
   horizontal overflow onto the page. */
.hero-stage{
  position:relative;
  z-index:1;
  margin-top:var(--s-3xl);
  width:100%;
  /* The shadow is cast from here, not from the arch itself. box-shadow would
     draw the frame's rectangle; drop-shadow reads the alpha the ogee clip
     leaves behind, so it follows the silhouette. Two of them: a wide soft one
     for the lift off the page and a short dense one for the contact edge,
     which is what stops a large soft shadow reading as fog. */
  filter:
    drop-shadow(0 2px 6px rgba(0,0,0,.10))
    drop-shadow(0 24px 48px rgba(0,0,0,.22));
}
.hero-media{
  position:relative;
  width:100%;
  height:var(--hero-media-h);
  overflow:hidden;
}
.hero-media img{width:100%;height:100%;object-fit:cover;object-position:center 42%}

/**
 * THE SCROLL EXPANSION IS GONE, and this reverses an earlier decision.
 *
 * It was a CSS view timeline that grew the picture from 0.62 to full bleed
 * over the first 600 of scroll, asked for in revision 3 and refined in
 * revision 4. It has been removed on request: the picture now renders at its
 * full size from first paint, which is exactly the state the animation ended
 * on and exactly what a browser without view timeline support always showed,
 * so nothing else in the section moves.
 *
 * transform-origin went with it. It existed only to decide which way the
 * animation's slack fell, and with no scale there is no slack.
 */

/* three proof cards breaking the lower edge of the media */

/* The logo wall's own padding is set with the rest of the section rhythm,
   further down. It was declared here as well, 48 over a full section, and that
   pair had been dead for some time: the optical pass rewrote it to 24 over 48
   and this copy simply lost, silently, to the later rule. */

@media (max-width:900px){
  .hero-stage{margin-top:var(--s-xl)}
  /* the token, not the height, so anything else that needs the picture's
     height reads one number rather than restating the clamp */
  .hero{--hero-media-h:clamp(340px,60vh,520px)}
}
/* the brand mark's own ladder lives with the navbar, further down: 56, then 48
   at 900 and 42 at 600. A second pair here said 38 and 34 and had been losing
   to it, so the logo was never the size this claimed. */

/* ---------- client logo wall ---------- */
.clients-note{text-align:center;margin-bottom:var(--s-xl)}
.client-rail{
  display:flex;flex-wrap:wrap;justify-content:center;align-items:center;
  gap:var(--s-xl) var(--s-2xl);
}
.client-rail li{display:flex;align-items:center;justify-content:center}
/* one ink tone, uniform optical height, width capped so a wide wordmark
   cannot dominate the row */
.client-rail img{
  height:30px;width:auto;max-width:148px;object-fit:contain;
  opacity:.6;
}
.client-rail img.is-svg{filter:brightness(0) saturate(0)}

@media (max-width:900px){
  .client-rail{gap:var(--s-lg) var(--s-xl)}
  .client-rail img{height:26px;max-width:120px}
}
@media (max-width:600px){
  .client-rail{gap:var(--s-md) var(--s-lg)}
  .client-rail img{height:22px;max-width:100px}
}


/* ---------- marquee rails ----------
   The client logo wall runs at every width: nine marks wrap into a ragged two
   line block otherwise, and a slow rail reads as a roster rather than a grid.
   The figure row only becomes a rail below 700px, where it would otherwise
   stack. Both hold the list twice, so translating -50% is a seamless loop, and
   both stop for anyone who asks for reduced motion. */
.rail{
  overflow:hidden;
  -webkit-mask-image:linear-gradient(90deg,transparent,#000 8%,#000 92%,transparent);
  mask-image:linear-gradient(90deg,transparent,#000 8%,#000 92%,transparent);
}
.rail-track{display:flex;flex-wrap:nowrap;width:max-content;align-items:center}
.rail-track > .rail-dup{display:flex}

.rail .client-rail{
  display:flex;flex-wrap:nowrap;width:max-content;
  gap:0;justify-content:flex-start;
}
.rail .client-rail li{padding-inline:var(--s-2xl);flex:0 0 auto}

@media (prefers-reduced-motion:no-preference){
  .rail-track{animation:railScroll 46s linear infinite}
}
@keyframes railScroll{
  from{transform:translateX(0)}
  to{transform:translateX(-50%)}
}

/* the figure row is a normal grid until there is no room for one */
.rail--figures{overflow:visible;-webkit-mask-image:none;mask-image:none}
.rail--figures .rail-track{display:contents;animation:none}
.rail--figures .rail-track > .rail-dup{display:none}

@media (max-width:700px){
  .rail .client-rail li{padding-inline:var(--s-lg)}
  @media (prefers-reduced-motion:no-preference){
    .rail-track{animation-duration:32s}
  }
  .rail--figures{
    overflow:hidden;
    -webkit-mask-image:linear-gradient(90deg,transparent,#000 10%,#000 90%,transparent);
    mask-image:linear-gradient(90deg,transparent,#000 10%,#000 90%,transparent);
  }
  .rail--figures .rail-track{display:flex;animation:railScroll 24s linear infinite}
  .rail--figures .rail-track > .rail-dup{display:flex}
  .rail--figures .figures{
    display:flex;flex-wrap:nowrap;width:max-content;gap:0;
    border-top:none;margin-top:var(--s-lg);padding-top:0;
  }
  .rail--figures .figures > div{
    flex:0 0 auto;padding-inline:var(--s-lg);
    border-left:1px solid var(--outline);min-width:200px;
  }
}

/* ---------- mosaic (closed setting) ---------- */
.mosaic{
  display:grid;
  grid-template-columns:repeat(12,1fr);
  /* 48 rather than 24. The copy column sits directly beside a photograph here,
     and the heading's dot hangs about 30 to the left of its first word; at 24
     it had nowhere to go but on top of the image. The gutter is what owns that
     space, so the gutter is what had to give. */
  gap:var(--s-2xl);
  align-items:start;
}
/* Every mosaic child is a grid item, so each one defaults to min-width:auto and
   a track will grow to its min-content rather than let it shrink. .m-wide holds
   a marquee whose .rail-track is width:max-content, and the overflow:hidden on
   .rail does not stop that 1406px from travelling up as a min-content
   contribution: an ancestor's overflow only zeroes the automatic minimum size
   of the box that carries it, and .rail is a plain block, not a grid item.
   Left at auto, one track inherits the whole untruncated marquee width and the
   grid drags the document out with it. Pinned to 0, the tracks are sized by the
   container, which is the only thing that should ever size them. */
.m-tall,.m-copy,.m-side,.m-wide{min-width:0}
.m-tall{grid-column:span 4}
.m-copy{grid-column:span 5}
.m-side{grid-column:span 3;display:grid;gap:var(--s-lg)}
.m-wide{grid-column:span 12}

.frame{position:relative;overflow:hidden;border-radius:var(--radius);background:var(--panel)}
.frame img{width:100%;height:100%;object-fit:cover}

.cap{font-size:13px;color:var(--ink-muted);margin-top:var(--s-1)}

.figures{
  display:grid;grid-template-columns:repeat(3,1fr);gap:var(--s-xl);
  margin-top:var(--s-2xl);padding-top:var(--s-lg);
  border-top:1px solid var(--outline);
}
.figures .figure{color:var(--title);margin-bottom:var(--s-sm);display:block}
.figures .label{display:block}

@media (max-width:1000px){
  .m-tall{grid-column:span 6}
  .m-copy{grid-column:span 6}
  .m-side{grid-column:span 12;grid-template-columns:1fr 1fr}
}
@media (max-width:600px){
  /* The track list collapses to one column here, and it has to.
     Twelve 1fr tracks with a 48px gutter need 11 x 48 = 528px of gutter before
     a single track gets any width at all. The gutter is 16 at this breakpoint,
     so the mosaic is only (viewport - 32) wide: under 560px the free space
     goes negative, every 1fr track computes to 0, and a span-12 item is
     11 gutters and nothing else. Exactly 528px, at every phone width, which is
     what pushed the document to a 544px scrollWidth inside a 360px viewport.
     Every item is already full width here, so the twelve tracks were buying
     nothing and the eleven gutters go with them. Keep the two declarations
     together: with one explicit column, a leftover `span 12` would generate
     eleven implicit columns and put the same 528px straight back. */
  .mosaic{grid-template-columns:1fr}
  .m-tall,.m-copy,.m-side,.m-wide{grid-column:1/-1}
  .m-side{grid-template-columns:1fr}
  .figures{grid-template-columns:1fr;gap:var(--s-lg)}
}

/* ---------- craft: one pinned stage, one step at a time ----------
   The panel pins for the whole run and the beats below it drive which stage is
   live. IntersectionObserver only, so nothing is hijacked: the page scrolls at
   its normal rate and the panel simply changes contents. */
/* the video band. 16:9, which is the source ratio and the ratio the YouTube
   player will use, so nothing reflows when the poster is swapped for it */
.vband{
  position:relative;width:100%;aspect-ratio:16/9;
  border-radius:var(--radius);overflow:hidden;
  background:var(--primary);margin-bottom:var(--s-4xl);
}

/**
 * The film grows as it is scrolled to.
 *
 * The same device the hero picture used to carry, moved here on request: a CSS
 * view timeline, so there is no scroll listener, nothing is pinned and nothing
 * can hijack. A browser without support renders it at full size, which is a
 * correct page rather than a broken one, and reduced motion gets the same.
 *
 * It is in flow with a fixed aspect ratio, so the scale changes nothing about
 * the layout: the smaller state simply shows more of the dark band around it.
 * That is also why no transform-origin is set. The hero's version needed
 * `center top` because scaling inside a fixed height box left slack that a
 * centred origin split above and below; there is no slack here, because the
 * box is the element.
 *
 * .vband also carries .reveal, and the two do not fight: an animation beats a
 * normal declaration, so this owns the transform while the reveal keeps the
 * fade. Without view timeline support the reveal owns both again.
 */
@supports (animation-timeline: view()){
  @media (prefers-reduced-motion:no-preference){
    .vband{
      animation:vbandRise linear both;
      animation-timeline:view();
      /* finishes at roughly the point the band reaches the middle of the
         screen, so the growth is over before it is being read */
      animation-range:cover 10% cover 50%;
    }
    @keyframes vbandRise{
      from{transform:scale(.88)}
      to{transform:scale(1)}
    }
  }
}
.vband-btn{
  position:absolute;inset:0;width:100%;height:100%;
  padding:0;border:0;background:none;cursor:pointer;display:block;
}
.vband-loop,.vband-frame{
  position:absolute;inset:0;width:100%;height:100%;
  object-fit:cover;display:block;border:0;
}
.vband-scrim{
  position:absolute;inset:0;
  background:linear-gradient(180deg,rgba(0,0,0,.10) 0%,rgba(0,0,0,.52) 100%);
}
.vband-ui{
  position:absolute;left:var(--s-xl);bottom:var(--s-xl);
  display:flex;align-items:center;gap:var(--s-md);
  color:var(--on-ink);text-align:left;
}
.vband-icon{
  flex:0 0 auto;width:56px;height:56px;
  display:flex;align-items:center;justify-content:center;
  border-radius:var(--radius-round);
  background:var(--accent);color:var(--on-accent);
  transition:transform var(--dur-hover) var(--ease);
}
.vband-icon i{font-size:26px;margin-left:3px}
.vband-btn:hover .vband-icon,
.vband-btn:focus-visible .vband-icon{transform:scale(1.06)}
.vband-btn:focus-visible{outline:2px solid var(--accent-soft);outline-offset:-4px}
.vband-txt b{
  display:block;font-family:"Cormorant Garamond",Georgia,serif;
  font-weight:400;font-size:24px;line-height:1.15;
}
.vband-txt span{display:block;font-size:13px;color:rgba(255,255,255,.8);margin-top:2px}

/* reduced motion: the loop holds on its poster frame rather than playing */
@media (prefers-reduced-motion:reduce){
  .vband-loop{display:none}
  .vband{background:var(--primary) url("../img/craft-poster.jpg") center/cover}
}

@media (max-width:700px){
  .vband{aspect-ratio:16/9}
  .vband-ui{left:var(--s-lg);bottom:var(--s-lg);gap:var(--s-sm)}
  .vband-icon{width:48px;height:48px}
  .vband-icon i{font-size:22px}
  .vband-txt b{font-size:19px}
}

/* the beats are pulled up under the pinned panel: they contribute the scroll
   distance without adding a screen of empty space above it */


/* The incoming picture fades in ON TOP of the outgoing one, which holds at full
   opacity underneath until it is covered, then cuts.
   Cross-fading both at once put two photographs at partial opacity over a pale
   panel: the composite never reached full opacity, so the panel showed through
   and every one of the twelve steps flashed a hazy double exposure. */

/* Copy is replaced, not dissolved. Fading two stages of copy through each other
   stacked two headings and two paragraphs on the same pixels, which reads as a
   rendering fault rather than a transition. The outgoing slide is removed at
   once and only the incoming one animates. */

.craft-count{
  font-size:13px;color:var(--ink-muted);
  font-variant-numeric:lining-nums tabular-nums;
}

@media (min-width:1001px){ .craft-mobile-media{display:none} }

/* ---------- collections ---------- */
.tiles{
  display:grid;grid-template-columns:repeat(4,1fr);
  grid-auto-rows:232px;
  gap:var(--s-md);
}
.tile{
  position:relative;border-radius:var(--radius);overflow:hidden;
  background:var(--panel);display:flex;flex-direction:column;justify-content:flex-end;
  min-height:200px;
}
.tile img{position:absolute;inset:0;width:100%;height:100%;object-fit:cover}
.tile-body{position:relative;padding:var(--s-lg);z-index:1}
.tile-body h3{font-family:"Cormorant Garamond",serif;font-size:24px;font-weight:400;line-height:1.2}
.tile-body p{font-size:13px;margin-top:var(--s-xs)}

/* THE SCRIM WAS SET BY EYE AND IT IS A CONTRAST FIGURE.
   A single linear ramp from .82 to nothing is only .82 at the very bottom edge:
   measured, it is between .17 and .45 across the band the HEADING occupies,
   because the heading sits near the top of the body box and the ramp has
   already given most of its density away. That is fine over a dark photograph
   and useless over this one. These are pictures of diamonds, so the brightest
   pixel under the heading is a specular highlight at rgb(255,255,250), and the
   text was landing on it at 1.19:1.
   Wine to Beige fixed the two flat tiles and could not fix these, because the
   problem here is not the ink, it is that the ground is uncontrolled.
   So the ramp holds its density through the text and gives it away only above:
   .92 at the foot, still .72 at 55%, gone by the top, over --s-4xl of lead
   instead of --s-3xl. Sampled per pixel across both photographs, worst case
   over 240,000 pixels: heading 8.32:1 and 9.81:1, body copy 9.43:1.
   The body copy needed it too. At the old ramp it measured 3.81:1, which fails
   AA on its own and was failing before any of this was touched. */
.tile-image .tile-body{
  color:var(--on-ink);
  background:linear-gradient(to top,
    rgba(0,0,0,.92) 0%,
    rgba(0,0,0,.72) 55%,
    rgba(0,0,0,0) 100%);
  padding-top:var(--s-4xl);
}
.tile-image .tile-body p{color:rgba(255,255,255,.8)}
.tile-invert{background:var(--ink);color:var(--on-ink)}
.tile-invert .tile-body p{color:rgba(255,255,255,.75)}
.tile-invert i{font-size:22px;margin-top:var(--s-md);display:inline-block;transition:transform var(--dur-hover) var(--ease)}
.tile-invert:hover i{transform:translate(4px,-4px)}
.tile-flat .tile-body p{color:var(--ink-muted)}

/* Tiles without a photograph carry the bezel geometry as a large, quiet fill.
   It is part of the container, bled off two edges and sitting under the text,
   never a glyph stacked above it. */
.tile-fill{
  position:absolute;
  right:var(--fill-x,-24%);
  bottom:var(--fill-y,-30%);
  top:auto;
  height:var(--fill-h,150%);
  width:auto;
  color:var(--ink);
  opacity:var(--fill-o,.07);
  pointer-events:none;
}
.tile-invert .tile-fill{color:var(--beige);opacity:.18}
@media (max-width:600px){ .tile-fill{height:120%;right:-28%;bottom:-22%} }

.t-lg{grid-column:span 2;grid-row:span 2}
.t-tall{grid-column:span 1;grid-row:span 2}
.t-sm{grid-column:span 1;grid-row:span 1}

@media (max-width:1000px){
  .tiles{grid-template-columns:repeat(2,1fr);grid-auto-rows:210px}
  .t-lg{grid-column:span 2;grid-row:span 2}
  .t-tall{grid-column:span 1;grid-row:span 2}
  .t-sm{grid-column:span 1;grid-row:span 1}
}
@media (max-width:600px){
  .tiles{grid-template-columns:1fr;grid-auto-rows:auto}
  .t-lg,.t-tall,.t-sm{grid-column:span 1;grid-row:span 1;min-height:260px}
}

/* ---------- proof: testimonial cards ----------
   The client's own cards carry a photograph of the person and their company
   logo. A quote with only a name under it reads as filler, which is what the
   earlier version looked like. */
/* Six cards, three widths, no ragged row ends. The lead runs two rows deep
   beside a stacked pair, then a band of three closes the section. Every row
   resolves to twelve columns, so the section never leaves an unexplained gap. */
.proof-run{display:grid;grid-template-columns:repeat(12,1fr);gap:var(--s-lg)}
.q-lead{grid-column:span 6;grid-row:span 2}
.q-wide{grid-column:span 6}
.q{grid-column:span 4}

.quote{
  display:flex;flex-direction:column;
  background:var(--panel);
  border:1px solid var(--outline);
  border-radius:var(--radius);
  padding:var(--s-xl);
  transition:border-color var(--dur-hover) var(--ease);
}
.quote:hover{border-color:var(--outline-strong)}
.quote.is-lead{
  background:var(--panel-warm);padding:var(--s-2xl);
  position:relative;overflow:hidden;
  /* the mark reads whole here rather than cropped to arcs, and sits in the
     band the lead quote leaves empty between the text and the attribution */
  --fill-h:74%; --fill-x:-10%; --fill-y:-8%; --fill-o:.055;
}

.q-mark{
  font-family:"Cormorant Garamond",serif;font-size:52px;line-height:.7;
  color:var(--accent);display:block;margin-bottom:var(--s-sm);
}
.quote p{
  font-family:"Cormorant Garamond",serif;font-weight:400;
  font-size:19px;line-height:1.45;color:var(--ink);
  margin-bottom:var(--s-xl);
}
.quote.is-lead p{font-size:clamp(21px,2.1vw,27px);line-height:1.35}

/* the attribution row: face, who they are, and the house they run */
.q-foot{
  margin-top:auto;padding-top:var(--s-lg);
  border-top:1px solid var(--outline);
  display:grid;grid-template-columns:auto 1fr auto;
  gap:var(--s-md);align-items:center;
}
.q-face{
  width:52px;height:52px;border-radius:var(--radius-round);
  object-fit:cover;object-position:center 22%;
  background:var(--panel-warm);
  border:1px solid var(--outline);
}
.quote.is-lead .q-face{width:64px;height:64px}
.q-who b{display:block;font-size:14.5px;font-weight:600;color:var(--ink);line-height:1.3}
.q-who span{display:block;font-size:12.5px;color:var(--ink-muted);margin-top:2px;line-height:1.3}
.q-logo{
  height:26px;width:auto;max-width:92px;object-fit:contain;
  opacity:.55;justify-self:end;
}
.q-logo.is-svg{filter:brightness(0) saturate(0)}
.quote.is-lead .q-logo{height:30px;max-width:110px}

@media (max-width:1100px){
  .q-lead{grid-column:span 12;grid-row:auto}
  .q-wide,.q{grid-column:span 6}
  /* five cards in two columns leave an orphan, so the last one runs full width */
  .proof-run > .quote:last-child{grid-column:span 12}
}

@media (max-width:760px){
  .q-wide,.q{grid-column:span 12}
  /* the rail is one grid item, so the lead card above it keeps its own row */
  .proof-rail{grid-column:span 12}
  .quote{padding:var(--s-lg)}
  .q-foot{grid-template-columns:auto 1fr;row-gap:var(--s-md)}
  .q-logo{grid-column:2;justify-self:start;margin-top:var(--s-xs)}
}
@media (max-width:600px){
  /* narrow card, so the mark bleeds large the way it does in the flat tiles
     rather than sitting mid-size, where it reads as a stray fragment */
  .quote.is-lead{--fill-h:120%;--fill-x:-30%;--fill-y:-18%;--fill-o:.05}
}

@media (max-width:760px){
  /**
   * THE PHONE RAIL, USED TWICE: THE FIVE SUPPORTING QUOTES AND THE AWARDS.
   *
   * Stacked, five testimonial cards and five award cards are eight screens of
   * scrolling for content nobody reads end to end. As a rail they are one
   * screen, the reader takes as much of it as they want, and the section keeps
   * a size the page can afford to give it.
   *
   * SCROLL SNAP, NOT A SCRIPT. The browser already has a carousel: an
   * overflow-x container with scroll-snap gives momentum, a real scrollbar
   * position, keyboard arrows on focus, trackpad and touch gestures, and it
   * degrades to a plain scrolling row with the stylesheet half loaded. A
   * scripted one has to reimplement all of that and usually reimplements the
   * first two.
   *
   * The card is 84% wide so the next one shows at the edge. That sliver is the
   * only affordance a rail needs and it is the one thing a full width card
   * cannot give: without it the row reads as a single card and nobody swipes.
   *
   * scroll-padding-inline matches the gutter, so a snapped card lands on the
   * page's own rail rather than hard against the viewport edge.
   */
  .proof-rail,
  .awards-grid{
    display:flex;
    gap:var(--s-md);
    overflow-x:auto;
    overscroll-behavior-inline:contain;
    scroll-snap-type:x mandatory;
    scroll-padding-inline:var(--gutter);
    /* the rail runs to both page edges, so the sliver at the right is a card
       continuing rather than a card cut off by a container */
    margin-inline:calc(var(--gutter) * -1);
    padding-inline:var(--gutter);
    padding-bottom:var(--s-sm);
    /* the scrollbar is noise on a touch rail, and the peeking card says more */
    scrollbar-width:none;
  }
  .proof-rail::-webkit-scrollbar,
  .awards-grid::-webkit-scrollbar{display:none}
  .proof-rail > *,
  .awards-grid > *{
    flex:0 0 84%;
    scroll-snap-align:start;
  }
}

/* above the phone the rail is not a rail: it is the same grid it always was,
   and the wrapper stops being a box at all */
@media (min-width:761px){
  .proof-rail{display:contents}
}

/* ---------- reach ---------- */
.reach-grid{display:grid;grid-template-columns:repeat(3,1fr);gap:var(--s-2xl)}
.place h3{
  font-size:15px;font-weight:600;color:var(--ink);
  padding-bottom:var(--s-1);margin-bottom:var(--s-1);
  border-bottom:1px solid var(--outline);
}
.place p{font-size:14px;line-height:1.6;color:var(--ink-muted)}
.place i{color:var(--ink);margin-right:var(--s-sm)}
@media (max-width:900px){ .reach-grid{grid-template-columns:1fr;gap:var(--s-xl)} }

/* ---------- vision, the one inverted panel ---------- */

/* ---------- footer ---------- */
.foot{color:var(--on-ink);padding-block:0 var(--s-xl)}
.foot-grid{display:grid;grid-template-columns:repeat(3,1fr);gap:var(--s-2xl);margin-bottom:var(--s-2xl)}

/**
 * THE FOOTER'S LINK COLUMNS ARE DISCLOSURES, AND ON A PHONE THEY ARE CLOSED.
 *
 * Three columns of four links is nothing at desktop and a screen and a half of
 * scrolling on a phone, where they stack: the footer became the tallest thing
 * on the page and the copyright line sat a long way past the last thing anyone
 * wanted. Collapsed to three headings it is one screen, and the reader opens
 * the one they are looking for.
 *
 * <details> rather than a scripted accordion, and the choice matters. It opens
 * on click, on Enter and on Space, it is announced as a disclosure, it is in
 * the find-in-page index, and it works with the stylesheet off and the script
 * gone. A div and a class does none of that without a great deal of ARIA to
 * put it back.
 *
 * Above 700 they are simply open: a UA rule hides a closed details' children,
 * and an author rule outranks it, so the list is shown whatever the element's
 * own state is and the summary stops being a control.
 */
.foot-acc > summary{
  list-style:none;cursor:pointer;
  display:flex;align-items:center;justify-content:space-between;gap:var(--s-sm);
}
.foot-acc > summary::-webkit-details-marker{display:none}
.foot-acc > summary i{
  font-size:18px;color:rgba(255,255,255,.6);
  transition:transform var(--dur-hover) var(--ease);
}
.foot-acc[open] > summary i{transform:rotate(180deg)}
.foot-acc > summary:focus-visible{outline:2px solid var(--accent-soft);outline-offset:3px;border-radius:var(--radius-sm)}

@media (min-width:701px){
  /* Open, always, and the summary is a heading again rather than a control.
     The opening itself is the `open` attribute, set in the markup and managed
     by the script: a closed <details> hides its content with
     content-visibility on ::details-content, so there is no author rule on the
     list that can reopen it. Only these two are CSS's job. */
  .foot-acc > summary{cursor:default;pointer-events:none}
  .foot-acc > summary i{display:none}
}
.foot h4{
  font-size:12px;letter-spacing:.14em;text-transform:uppercase;
  color:rgba(255,255,255,.55);margin-bottom:var(--s-md);font-weight:500;
}
.foot p,.foot li{font-size:14px;line-height:1.7;color:rgba(255,255,255,.78)}
.foot li{margin-bottom:var(--s-sm)}
.foot a:hover{color:var(--on-ink)}
.foot-links{display:grid;gap:var(--s-sm)}
.social{display:flex;gap:var(--s-sm);margin-top:var(--s-md)}
.social a{
  width:44px;height:44px;display:inline-flex;align-items:center;justify-content:center;
  border:1px solid var(--outline-light);border-radius:var(--radius);
  color:rgba(255,255,255,.7);
  transition:color var(--dur-hover) var(--ease),border-color var(--dur-hover) var(--ease);
}
.social a:hover{color:var(--on-ink);border-color:rgba(255,255,255,.5)}
.foot-base{
  border-top:1px solid var(--outline-light);padding-top:var(--s-lg);
  display:flex;align-items:center;justify-content:space-between;gap:var(--s-md);flex-wrap:wrap;
}
.foot-base .social{margin-top:0}
.foot-base p{font-size:13px;color:rgba(255,255,255,.55)}

@media (max-width:900px){ .foot-grid{grid-template-columns:1fr 1fr;gap:var(--s-xl)} }
@media (max-width:700px){
  /* one column of closed disclosures, each a row with a real tap target and a
     rule under it, so the three read as a list rather than three headings */
  .foot-grid{grid-template-columns:1fr;gap:0;margin-bottom:var(--s-xl)}
  .foot-acc{border-bottom:1px solid var(--outline-light)}
  .foot-acc > summary{min-height:52px;padding-block:var(--s-sm)}
  .foot-acc > .foot-links{padding-bottom:var(--s-md)}
}

/* touch targets */
@media (pointer:coarse),(max-width:600px){
  .foot-links a,.place a{display:inline-flex;align-items:center;min-height:44px}
  .foot-links li{margin-bottom:0}
}

/* skip link */
.skip:focus{
  left:var(--gutter) !important;top:var(--s-md);z-index:60;
  background:var(--ink);color:var(--on-ink);
  padding:var(--s-1) var(--s-md);font-size:14px;font-weight:600;border-radius:var(--radius);
}

/* ---------- reveal ---------- */
.reveal{opacity:1;transform:none}
@media (prefers-reduced-motion:no-preference){
  .js-reveal .reveal{opacity:0;transform:translateY(16px)}
  .js-reveal .reveal.is-in{
    opacity:1;transform:none;
    transition:opacity var(--dur-reveal) var(--ease),transform var(--dur-reveal) var(--ease);
  }

  /* images settle out of a slight zoom as their section arrives.
     This is the only motion on imagery; there is no hover state. */
  .js-reveal .reveal img{transform:scale(1.06)}
  .js-reveal .reveal.is-in img{
    transform:scale(1);
    transition:transform 1100ms var(--ease);
  }
  .js-reveal .reveal .client-rail img,
  .js-reveal .reveal.is-in .client-rail img{transform:none;transition:none}
  /* The words hold at their from-state until the hero is released.
     A bare `animation` here starts the moment the span is parsed, and the split
     runs from an inline script at the top of the body, so the whole headline
     went through its entrance behind the loading screen: measured, it finished
     at 1086ms against an overlay that did not start lifting until 1136ms.
     The per-word `animation-delay` is set inline on each span, and an inline
     declaration outranks this shorthand, so the stagger survives being moved
     into the gated rule. */
  .hero-word{display:inline-block;opacity:0;transform:translateY(18px)}
  .hero-words.is-in .hero-word{animation:heroWord 900ms var(--ease) forwards}
  @keyframes heroWord{to{opacity:1;transform:none}}
}
@media (prefers-reduced-motion:reduce){
  .reveal,.js-reveal .reveal{opacity:1;transform:none}
  .js-reveal .reveal img,.js-reveal .reveal.is-in img{transform:none;transition:none}
  .hero-word{opacity:1;transform:none}
}


/* ==========================================================================
   QUALITY, FOUNDER, AWARDS

   These three sections shipped with markup and no stylesheet rules at all.
   Unstyled, the quality section alone ran 5745px tall: each bespoke mark is an
   inline SVG with fill="none" and no stroke, so with no CSS the shapes were
   invisible and the SVG stretched to the full container width. The pillars
   stacked one per row at 1336px square.
   ========================================================================== */

/* ---------- quality: four pillars ---------- */
.quality{padding-block:var(--s-section)}
.pillars{
  margin-top:var(--s-4xl);
  /* Wider gutters than the carded version needed. Each card used to carry 32 of
     its own padding on both sides, so removing them took 64 out of every gap
     and the four columns started reading as one block of text. */
  display:grid;grid-template-columns:repeat(4,1fr);gap:var(--s-2xl);
}
.pillar{padding:0;background:none;border:0}
/* the marks are drawn, not filled: they carry no presentation attributes, so
   the stroke is set here and inherits the pillar's colour */
.mark{
  display:block;width:44px;height:44px;
  margin-bottom:var(--s-lg);
  color:var(--accent);
  stroke:currentColor;stroke-width:1.15;
  stroke-linecap:round;stroke-linejoin:round;
  fill:none;
  vector-effect:non-scaling-stroke;
}
.pillar h3{
  font-family:"Cormorant Garamond",Georgia,serif;font-weight:400;
  font-size:23px;line-height:1.2;color:var(--ink);
  margin-bottom:var(--s-sm);
}
.pillar p{font-size:15px;line-height:1.65;color:var(--ink-muted)}

@media (max-width:1100px){
  .pillars{grid-template-columns:repeat(2,1fr);gap:var(--s-xl)}
}
@media (max-width:600px){
  .pillars{grid-template-columns:1fr;margin-top:var(--s-2xl);gap:var(--s-xl)}
  .mark{width:36px;height:36px;margin-bottom:var(--s-md)}
}

/* ---------- founder ---------- */
.founder-grid{
  display:grid;grid-template-columns:0.8fr 1.2fr;
  gap:var(--s-4xl);align-items:start;
}
/* the portrait sits above the name, not beside it */
.founder-portrait{
  display:block;width:100%;height:auto;aspect-ratio:680/880;
  object-fit:cover;object-position:center 18%;
  border-radius:var(--radius);background:var(--panel);
  margin-bottom:var(--s-lg);
}
.founder-id{display:flex;flex-direction:column;gap:2px}
.founder-id b{
  font-family:"Cormorant Garamond",Georgia,serif;font-weight:400;
  font-size:26px;line-height:1.2;color:var(--ink);
}
.founder-id span{font-size:14px;line-height:1.5;color:var(--ink-muted)}

.founder-body{display:flex;flex-direction:column;gap:var(--s-lg)}
.founder-body .d-md{color:var(--ink);max-width:26ch}
/* the statement ends in a way in, not a signature */
.founder-cta{
  align-self:flex-start;
  margin-top:var(--s-sm);
}

@media (max-width:900px){
  .founder-grid{grid-template-columns:1fr;gap:var(--s-2xl)}
  .founder-portrait{max-width:340px}
}

/* ---------- awards ---------- */
.awards-grid{
  margin-top:var(--s-3xl);
  display:grid;grid-template-columns:repeat(3,1fr);gap:var(--s-xl);
}
.award{
  display:flex;flex-direction:column;
  background:var(--panel-warm);
  border:1px solid var(--outline);
  border-radius:var(--radius);
  overflow:hidden;
}
/* the client photographs its own trophies, so each award gets a real container
   rather than a line of text */
.award-shot{
  position:relative;
  aspect-ratio:520/440;
  background:var(--panel);
  overflow:hidden;
}
.award-shot img{
  width:100%;height:100%;object-fit:cover;object-position:center 30%;
  display:block;
}
.award-yr{
  position:absolute;top:var(--s-md);left:var(--s-md);z-index:1;
  font-family:"Cormorant Garamond",Georgia,serif;font-weight:400;
  font-size:15px;line-height:1;letter-spacing:.06em;
  color:var(--on-ink);
  background:rgba(0,0,0,.78);
  padding:var(--s-sm) var(--s-1);
  border-radius:var(--radius);
  font-variant-numeric:lining-nums tabular-nums;
}
.award-body{
  padding:var(--s-lg);
  display:flex;flex-direction:column;gap:var(--s-xs);
  flex:1 1 auto;
}
.award-body h3{
  font-family:"Cormorant Garamond",Georgia,serif;font-weight:400;
  font-size:21px;line-height:1.22;color:var(--ink);
}
.award-body p{font-size:14.5px;line-height:1.5;color:var(--ink-muted)}
.award-body .when{
  margin-top:auto;padding-top:var(--s-1);
  font-size:12.5px;line-height:1.45;color:var(--ink-muted);
  border-top:1px solid var(--outline);
}

@media (max-width:1100px){
  .awards-grid{grid-template-columns:repeat(2,1fr);gap:var(--s-lg)}
}
/* The rail's own rules are declared once, up with the quotes that share them.
   These are the two the base .awards-grid rule above puts back after it, and
   they are restated rather than moved, because the shared block belongs beside
   the other thing it styles. */
@media (max-width:760px){
  .awards-grid{display:flex;gap:var(--s-md)}
}
@media (max-width:600px){
  .awards-grid{margin-top:var(--s-2xl)}
}


/* ---------- craft: the depth rail ----------
   Signed off from explorations/craft-step-options-2.html, pattern taken from
   React Bits DepthCarousel and rebuilt natively. It replaces the pinned
   twelve-step panel, which cost roughly twelve screens of scroll to read.
   Three things are load bearing: the focused card carries its own caption over
   a fade, the receding cards carry nothing so they stay photographs, and the
   whole thing is draggable on a finger. Naming the stage above the rail was the
   first answer and is now only the no-script fallback: with the script the
   words and the picture they describe are one object, which is what was asked
   for. */
/* No panel. On the dark band the rail was a tonal box inside a tonal box,
   which is two containers doing one container's job.

   And no top padding. The gap from the section title to the first card was 184:
   96 of margin under the heading block, 48 of padding here, and 40 of slack
   inside the stage, three separations doing one separation's work. This one is
   the redundant one. */
.dp{
  padding:0 0 var(--s-xl);
}

/* the reader. Fixed minimum height so the rail never jumps when a stage name
   or description runs to a different number of lines. */
/* Only ever seen without the script. With it, the caption lives inside the
   focused card and this is hidden, so the stage is named in one place. */
.js-dp .dp-read{display:none}
.dp-read{
  max-width:56ch;margin:0 auto var(--s-xl);padding-inline:var(--s-xl);
  text-align:center;color:var(--ink);min-height:150px;
}
.dp-read .n{
  display:block;font-family:"Cormorant Garamond",Georgia,serif;
  font-size:15px;letter-spacing:.16em;color:var(--accent);
  font-variant-numeric:lining-nums tabular-nums;
}
.dp-read h3{
  font-family:"Cormorant Garamond",Georgia,serif;font-weight:400;
  font-size:clamp(26px,3vw,38px);line-height:1.15;margin-top:var(--s-xs);
}
.dp-read p{color:var(--ink-muted);font-size:16px;line-height:1.7;margin-top:var(--s-sm)}

/* Without the script this is a plain swipeable row, so the twelve stages are
   still readable. The script adds .js-dp and takes over positioning. */
.dp-stage{
  display:flex;gap:var(--s-md);overflow-x:auto;
  padding-inline:var(--s-xl);scroll-snap-type:x mandatory;
}
.dp-stage > .dp-c{flex:0 0 300px;scroll-snap-align:center}
/* The focused card is a landscape rectangle, the ones behind it stay portrait.
   Every card's BOX is the focused size; the ones behind are scaled down to the
   side size, and the image inside is counter-scaled by the inverse so it centre
   crops rather than squashing. That keeps the whole thing on transform, which
   composites, instead of animating width and height, which are layout. The four
   sizes live here so a breakpoint is a value change; the script reads them. */
.js-dp .dp-stage{
  display:block;position:relative;height:560px;
  perspective:1400px;touch-action:pan-y;
  padding-inline:0;
  /**
   * CLIP, NOT HIDDEN, AND THE DIFFERENCE IS THE WHOLE OF A REAL BUG.
   *
   * From stage eight onward the rail lurched sideways: the focused card, which
   * is centred by construction, was measured 735 to 739 off the stage's own
   * centre, with the cards past it sliced and greyed. The card's transform was
   * identical to a working one. What had moved was the STAGE, whose
   * `scrollLeft` had gone from 0 to 739.
   *
   * `overflow:hidden` clips the paint and still makes a scroll container. The
   * cards here are absolutely positioned and transformed out to either side,
   * and a transformed box counts toward scrollable overflow, so the stage had
   * 2075 of it against a 1336 box: 739 of scrollable width nobody asked for.
   * The browser then used it. It needed no click and no focus to do so, which
   * is what ruled out every theory about the controls: it happens under
   * autoplay alone with the body focused, the offset creeping 310, 591, 739 as
   * the cards move. That is scroll anchoring doing its job on a container that
   * should never have been scrollable.
   *
   * `overflow:clip` clips identically and creates NO scroll container, so
   * there is no offset to anchor, to reveal a focused child into, or to leave
   * behind. `overflow:hidden` stays ahead of it for Safari before 16, where
   * `overflow-anchor` is the belt to that brace.
   */
  overflow:hidden;
  overflow:clip;
  overflow-anchor:none;
  /**
   * THE CARDS DISSOLVE AT THE EDGES RATHER THAN BEING SLICED BY THEM.
   *
   * This is the glitch that was reported as the rail "breaking up" on scroll,
   * and it was not a scroll bug at all: it is there at rest. The outermost
   * visible card sits 569 from the centre and is 322 wide, so its outer edge
   * lands at 730 against a stage half width of 668. `overflow:hidden` cut it
   * on a hard vertical line, 62 in from nothing. Mid advance it is worse,
   * measured at 720 and 739 on both sides at once while those cards are
   * half faded and moving, which is what made it read as a fault rather than
   * as a carousel.
   *
   * The fade is the same device the client logo rail already uses at both its
   * ends, for the same reason: a row that continues past its container should
   * say so, not stop.
   *
   * The ramp is derived, not chosen. It can never be wider than the gap
   * between the focused card and the stage edge, or it would start eating the
   * card the whole thing is about: 96 at desktop where there is 278 of room,
   * and it falls back to the real gap on a phone, where there is only 42.
   */
  --dp-fade:min(96px, calc((100% - var(--dp-fw)) / 2));
  -webkit-mask:linear-gradient(to right,
    transparent 0, #000 var(--dp-fade),
    #000 calc(100% - var(--dp-fade)), transparent 100%);
          mask:linear-gradient(to right,
    transparent 0, #000 var(--dp-fade),
    #000 calc(100% - var(--dp-fade)), transparent 100%);
  /* Two a side rather than three, and everything larger for it. Six receding
     cards were six thumbnails; four are photographs you can actually read, and
     the stage that matters is now big enough to carry its own caption. */
  --dp-fw:780px; --dp-fh:520px;   /* focused, landscape */
  --dp-sw:400px; --dp-sh:520px;   /* the rest */
}
.dp-c{
  border-radius:var(--radius);overflow:hidden;background:var(--primary);
}
.js-dp .dp-c{
  position:absolute;top:50%;left:50%;
  width:var(--dp-fw);height:var(--dp-fh);
  /* centred by transform, not by half-size negative margins: those are derived
     from the card dimensions rather than chosen, so they fall off the spacing
     scale and have to be edited again at every breakpoint */
  transform:translate(-50%,-50%);
  transition:transform 700ms var(--ease),opacity 700ms var(--ease),filter 700ms var(--ease);
  will-change:transform,opacity;
}
.dp-c img{width:100%;height:100%;object-fit:cover;display:block}
.js-dp .dp-c img{
  transform-origin:center;
  transition:transform 700ms var(--ease);
}
.dp-stage:focus-visible{outline:2px solid var(--accent);outline-offset:2px;border-radius:var(--radius)}

/* the rail is a thing you can take hold of, and it says so */
.js-dp .dp-stage{cursor:grab}
.js-dp .dp-stage.is-dragging{cursor:grabbing}
/* and while it is being held it tracks the hand exactly, with no easing
   between where the finger is and where the card is */
.js-dp .dp-stage.is-dragging .dp-c,
.js-dp .dp-stage.is-dragging .dp-c img,
.js-dp .dp-stage.is-dragging .dp-cap{transition:none}
/* the photographs are the drag handle, so the browser's own image drag has to
   get out of the way */
.js-dp .dp-c img{-webkit-user-drag:none;user-select:none;pointer-events:none}
@media (prefers-reduced-motion:reduce){
  .js-dp .dp-c{transition:none}
}

.dp-ui{
  display:flex;align-items:center;justify-content:center;
  gap:var(--s-lg);margin-top:var(--s-xl);flex-wrap:nowrap;
}
/* the arrows are controls, not calls to action, so they stay square and never
   stretch to fill the row the way the site .btn otherwise would */
.dp-ui .dp-arrow{flex:0 0 auto;width:48px;height:48px;padding:0;justify-content:center}
.dp-dots{
  /* A WINDOW, NOT A ROW.

     Twelve dots is a ruler, and at 375 the row wrapped and left one dot
     orphaned on a second line under the arrows. So the row runs inside a fixed
     window seven slots wide and slides under it, keeping the active dot dead
     centre. The reader gets position and neighbourhood, which is all a
     pagination dot has ever been for, and never a count to read.

     Centred rather than clamped at the ends. Clamping fills the window at the
     first and last item, which is tidier, but it puts the active dot under the
     mask's fade where it is the one dot that must stay crisp. */
  --dp-slot:24px;
  --dp-win:7;
  flex:0 0 auto;
  width:calc(var(--dp-slot) * var(--dp-win));
  overflow:hidden;
  /* the fade the window needs. Without it the outermost dot is cut in half by
     the overflow and reads as a rendering fault; with it the row simply
     thins out at both ends and there is no edge to find. */
  -webkit-mask:linear-gradient(90deg,transparent 0,#000 16%,#000 84%,transparent 100%);
          mask:linear-gradient(90deg,transparent 0,#000 16%,#000 84%,transparent 100%);
}
.dp-track{
  display:flex;flex-wrap:nowrap;align-items:center;
  /* the active dot's centre placed at the window's centre: its own centre sits
     at (i + .5) slots, the window's at (win / 2) */
  transform:translateX(calc((var(--dp-win) / 2 - var(--dp-i, 0) - .5) * var(--dp-slot)));
}
@media (prefers-reduced-motion:no-preference){
  .dp-track{transition:transform var(--dur-hover) var(--ease)}
}
/**
 * The dot is the timer, exactly as the hero's roll tabs are.
 *
 * A dot that only says WHICH stage is showing leaves the reader without the
 * other half: how long it stays and when it will move. The active one grows
 * into a pill and a gold fill sweeps it over one interval, so position and
 * progress are the same object rather than two things to read. This is the
 * same primitive as .roll-tab and deliberately so, one progress indicator per
 * project; the only difference is that these are round at rest.
 *
 * WIDTH, AND THE HOUSE RULE IT BENDS. The rule is that nothing animates a
 * layout property, and the previous version kept it by holding the track at a
 * constant 24px and animating how much of it was CLIPPED, with
 * `clip-path:inset(0 8.5px round 999px)` making the clipped remainder a
 * circle. It did not survive contact: the `round` did not take, so every dot
 * on the page rendered as a 7px SQUARE and the active one as a rectangle. A
 * shape that depends on a clip corner radius to be a shape at all is one
 * unsupported keyword away from being wrong, and it was wrong.
 *
 * So it is width now, on an absolutely positioned element, where there is
 * nothing left in flow to reflow. A 7px box at radius 999 is a circle and a
 * 22px box at radius 999 is a pill, and neither can be anything else. The
 * button's own slot never changes size, so the row still cannot shift.
 */
/**
 * THE PIP, AND WHY THE ACTIVE ONE HAS A TINTED TRACK.
 *
 * The active pip is a pill and the gold sweeps across it over one interval.
 * The pill's own track used to be the same muted grey as the resting dots,
 * which on the dark band is very nearly the ground: what you actually saw was
 * the GOLD, and a gold fill caught halfway across a rounded box is a round end
 * and a straight cut. It read as a lozenge, a teardrop, a bitten circle, and
 * never once as a pill, because the pill was invisible and only its contents
 * were lit.
 *
 * So the active track is gold held down to 24%. The full pill silhouette is
 * there from the moment it opens, in the same hue as the thing filling it, and
 * the sweep is a fill INSIDE a shape rather than a shape of its own. Position
 * first, progress second, which is the order the reader needs them in.
 */
.dp-dot,
.roll-tab{
  position:relative;
  height:20px;padding:0;border:0;
  background:none;cursor:pointer;
}
.dp-dot{flex:0 0 var(--dp-slot)}
/**
 * THE HERO'S THREE ARE SIZED TO THEIR OWN PIP, NOT TO A COMMON SLOT.
 *
 * They sat in a fixed 22px slot each, which is what the craft rail does and is
 * right there: that row is a sliding window and its arithmetic is index times
 * slot, so the slots have to be equal. Here it put a 7px dot afloat in a 22px
 * box while the pill filled its own, and the gaps a reader actually sees came
 * out at 19px between the two dots and 11.5px between the second dot and the
 * pill. The row's BOX was centred to the pixel the whole time; its INK was not,
 * and ink is what centring means.
 *
 * Fitted slots make every visible gap the row's own gap, and the group
 * re-centres itself as the active one moves because the container is anchored
 * at its own middle. That is a width transition on three small absolutely
 * positioned rows, which is the one place the no-layout-animation rule gives
 * way: the alternative is a row that is measurably centred and visibly is not.
 */
.roll-tab{flex:0 0 auto;width:var(--pip-r)}
.roll-tab.is-on{width:var(--pip-pill)}
@media (prefers-reduced-motion:no-preference){
  .roll-tab{transition:width var(--dur-hover) var(--ease)}
}
.dp-dot i,
.roll-tab i{
  position:absolute;top:50%;left:50%;
  transform:translate(-50%,-50%) scale(calc(1 - .1 * min(var(--d,0),3)));
  display:block;
  width:var(--pip-r);height:var(--pip-r);
  /**
   * 999px, NOT --radius-round, and this is the whole of the shape bug.
   *
   * --radius-round is 50%, which is a percentage of each axis independently.
   * On the 7px square that is a circle and looks correct, which is exactly why
   * it survived: the resting dots were right. On the 22 by 7 pill it is an 11px
   * horizontal radius against a 3.5px vertical one, which is an ELLIPSE. It was
   * read as a lozenge and a teardrop and it was neither; it was an ellipse,
   * because the corner radius grew with the width instead of staying at half
   * the height.
   *
   * A length clamps to half the SHORT side, so 999px is a circle at 7 wide and
   * a true pill with semicircular caps at 22, and nothing in between is ever
   * an ellipse.
   */
  border-radius:999px;
  background:var(--outline-strong);
  /* so the fill below is cut to the pill rather than to its own box */
  overflow:hidden;
}
@media (prefers-reduced-motion:no-preference){
  .dp-dot i,
  .roll-tab i{
    transition:width var(--dur-hover) var(--ease),
               transform var(--dur-hover) var(--ease),
               background var(--dur-hover) var(--ease);
  }
}
.dp-dot.is-on i,
.roll-tab.is-on i{width:var(--pip-pill);background:var(--pip-track-on)}
/* the fill */
.dp-dot i::after,
.roll-tab i::after{
  content:"";position:absolute;inset:0;
  background:var(--pip-fill);
  transform:scaleX(0);transform-origin:left center;
}
/* Without the timer running, and under reduced motion, the active pip is
   simply full: it still says which item this is, it just makes no claim
   about when the next one arrives. */
.dp-dot.is-on i::after,
.roll-tab.is-on i::after{transform:scaleX(1)}
@media (prefers-reduced-motion:no-preference){
  .dp-dot.is-on.is-counting i::after{animation:rollFill var(--dp-ms) linear forwards}
  .roll-tab.is-on.is-counting i::after{animation:rollFill var(--roll-ms) linear forwards}
}
.dp-dot:focus-visible,
.roll-tab:focus-visible{outline:2px solid var(--accent);outline-offset:3px;border-radius:999px}
/* the narrow end: five slots instead of seven, so the arrows and the window
   still sit on one line inside a 320 viewport */
@media (max-width:400px){ .dp-dots{--dp-win:5} }

/* The rail's own card sizes at these two widths are set with the rest of the
   rail, further down. They were declared here too, at 360/400 and 340/224, and
   that pair had been dead since the cards were enlarged to two a side: the
   later rule won every time, so editing these changed nothing and looked as
   though the change had not taken. */
@media (max-width:900px){
  .dp-read{min-height:170px}
}
@media (max-width:560px){
  .dp{padding:var(--s-xl) 0 var(--s-lg)}
  .dp-read{padding-inline:var(--s-lg);min-height:164px;margin-bottom:var(--s-lg)}
  .dp-ui{gap:var(--s-md);margin-top:var(--s-lg)}
  .dp-ui .dp-arrow{width:44px;height:44px}
}


/* ---------- hero: the slider ----------
   Copy left, buttons right. The slides are stacked in one grid cell so the
   block never changes height as they rotate, and the outgoing slide is removed
   at once rather than dissolved: two headlines fading through each other on the
   same pixels reads as a fault, which is the same rule the craft copy follows. */
/* the measure belongs on the headline, in the headline's own font. A ch cap on
   the wrapper is read in the body font and collapses an 82px display line to
   about 180px, which wrapped it to five lines. */


/* the images stack in the same aperture and step with the copy */

/* ---------- hero: one stretched proof strip ---------- */


/* ==========================================================================
   NAVIGATION

   The bar sits on the same rail as every section. It used to carry its own
   padding system: the shell centres a 1400px container and pads it by the
   gutter, while the bar was pushed in by the gutter and then padded again by
   24px, so the logo landed 12px right of the headline below it and the CTA 13px
   short of the section's right edge. .nav-inner is now literally a shell, so
   the bar's contents and the page's contents share one left and right edge at
   every width.

   The client's own navigation is twelve flat links. Grouped into two panels
   and one direct link, with a line under each item saying what is behind it.
   ========================================================================== */
.nav{
  position:fixed;top:var(--s-md);left:0;right:0;z-index:50;
  padding-inline:var(--gutter);
  pointer-events:none;
}
.nav-inner{
  pointer-events:auto;
  /* the container less its two gutters IS the width of section content, so the
     bar's own edges land on the same rail as the copy and the strip below it */
  max-width:calc(var(--container) - var(--gutter) * 2);
  margin-inline:auto;
  padding-inline:var(--s-lg);
  height:var(--nav-h);
  display:flex;align-items:center;justify-content:space-between;gap:var(--s-xl);
  border-radius:var(--radius);
  border:1px solid var(--outline-light);
  background:var(--primary);
  transition:background var(--dur-hover) var(--ease),
             box-shadow var(--dur-hover) var(--ease);
}
.nav.is-scrolled .nav-inner{
  background:var(--primary-raised);
  border-color:rgba(255,255,255,.22);
  box-shadow:0 12px 36px rgba(0,0,0,.32);
}

.brand{display:flex;align-items:center;flex:0 0 auto}
.brand img{height:56px;width:auto;display:block}

.nav-main{display:flex}
.nav-links{display:flex;align-items:center;gap:var(--s-lg)}
.nav-item{position:relative;display:flex}
.nav-top{
  display:inline-flex;align-items:center;gap:var(--s-xs);
  font-family:inherit;font-size:14px;font-weight:500;
  color:rgba(255,255,255,.74);
  background:none;border:0;padding:var(--s-sm) 0;cursor:pointer;
  border-bottom:1px solid transparent;
  transition:color var(--dur-hover) var(--ease),border-color var(--dur-hover) var(--ease);
}
.nav-top i{font-size:16px;transition:transform var(--dur-hover) var(--ease)}
.nav-top:hover,.nav-item.is-open > .nav-top{color:var(--on-ink)}
.nav-item.is-open > .nav-top i{transform:rotate(180deg)}
.nav-top.is-current{color:var(--on-ink);border-bottom-color:var(--accent-soft)}
.nav-top:focus-visible{outline:2px solid var(--accent-soft);outline-offset:3px}

/* the panel. Anchored to its own item, not the full width: two columns of
   links is not enough content to justify a full-bleed sheet. */
.mega{
  position:absolute;top:calc(100% + var(--s-1));left:50%;
  transform:translateX(-50%) translateY(-6px);
  min-width:520px;
  background:var(--primary-raised);
  border:1px solid var(--outline-light);
  border-radius:var(--radius);
  box-shadow:0 22px 56px rgba(0,0,0,.38);
  padding:var(--s-lg);
  opacity:0;visibility:hidden;pointer-events:none;
  transition:opacity 200ms var(--ease),transform 200ms var(--ease),visibility 200ms;
}
.nav-item.is-open > .mega{opacity:1;visibility:visible;transform:translateX(-50%);pointer-events:auto}
.mega-grid{display:grid;grid-template-columns:1fr 1fr;gap:var(--s-lg)}
.mega-col{display:flex;flex-direction:column;gap:var(--s-xs)}
/**
 * The gold group labels are gone, and with them the misalignment they caused.
 *
 * "THE HOUSE" and "INSIGHT" sat at the column's own edge while every link
 * under them was inset by the link's 12 of padding, so the label and the list
 * it named started on two different rails. That was the visible fault; the
 * label being there at all was the real one. Two columns of five links between
 * them do not need naming, and an uppercase gold eyebrow over each is louder
 * than anything it labels.
 *
 * The negative inline margin is the alignment: it pulls the link's TEXT out to
 * the panel's padding rail while the hover panel still bleeds 12 either side,
 * so what lines up is the words rather than the boxes around them.
 */
.mega-link{
  display:block;padding:var(--s-sm) var(--s-1);
  margin-inline:calc(var(--s-1) * -1);
  border-radius:var(--radius);
  transition:background var(--dur-hover) var(--ease);
}
/* the dot is the hover state now. The title brightens with it, so the row
   still responds as a whole rather than only at its left edge. */
.mega-link:hover b,.mega-link:focus-visible b{color:var(--accent-soft)}
.mega-link:focus-visible{outline:2px solid var(--accent-soft);outline-offset:-2px}
.mega-link b{
  display:block;font-size:14.5px;font-weight:600;color:var(--on-ink);line-height:1.3;
}
.mega-link span{
  display:block;font-size:12.5px;line-height:1.45;
  color:rgba(255,255,255,.6);margin-top:2px;
}

.nav-cta{display:flex;align-items:center;gap:var(--s-md);flex:0 0 auto}
.nav-cta .btn{padding:12px var(--s-lg);font-size:14px}
.nav-toggle{
  display:none;background:none;border:1px solid var(--outline-light);
  color:var(--on-ink);width:44px;height:44px;
  /* the browser's own button padding was showing through, 1 top and bottom and
     6 either side, which is four values off the scale on the one control a
     phone reader touches first */
  padding:0;
  align-items:center;justify-content:center;cursor:pointer;border-radius:var(--radius);
}
.nav-toggle:hover{border-color:rgba(255,255,255,.34)}
.nav-toggle i{font-size:22px}

@media (max-width:1100px){
  .nav-links{gap:var(--s-md)}
  .mega{min-width:460px}
}

/**
 * Below 900 the menu is the whole screen, and each panel is an accordion in
 * place rather than a second interaction to learn.
 *
 * It was a card hung under the bar, sized to its own content. On a phone that
 * leaves the page visible under it and either side of it, so the menu's four
 * items sat over whatever section happened to be behind them and the two read
 * as one list. A menu is a mode: it takes the screen, or it is a dropdown
 * pretending to be one.
 *
 * The bar stays on top of the sheet, because the bar is where the close button
 * is. That is the only thing the z-index and the top padding are for: the
 * sheet fills the viewport, and its content starts below the bar's own box.
 */
@media (max-width:900px){
  .nav-inner{padding-inline:var(--s-md)}
  /* The sheet is a CHILD of .nav-inner, so the bar cannot be lifted over it:
     a parent paints its background under its own children whatever the
     z-index says. The two things that have to stay reachable are lifted
     instead, and they are siblings of the sheet once .nav-main is display:
     contents, so here z-index means what it looks like it means. The bar's own
     box disappears behind the sheet while the menu is open, which is right:
     the menu is one surface with the mark on it and the way out of it. */
  .brand,.nav-cta{position:relative;z-index:2}
  .brand img{height:48px}
  .nav-main{display:contents}
  .nav-links{
    position:fixed;
    inset:0;
    z-index:1;
    /* clear of the floating bar, which sits --s-md down and is --nav-h tall */
    padding:calc(var(--s-md) + var(--nav-h) + var(--s-lg)) var(--s-md) var(--s-2xl);
    overflow-y:auto;
    /* momentum scroll inside the sheet, and no rubber banding through to the
       page underneath it */
    -webkit-overflow-scrolling:touch;
    overscroll-behavior:contain;
    flex-direction:column;align-items:stretch;justify-content:flex-start;
    gap:var(--s-xs);
    background:var(--primary);
    border:0;
    border-radius:0;
    opacity:0;transform:translateY(-8px);
    pointer-events:none;
    transition:opacity 240ms var(--ease),transform 240ms var(--ease);
  }
  .nav-links.is-open{opacity:1;transform:none;pointer-events:auto}
  .nav-item{flex-direction:column;align-items:stretch}
  .nav-top{
    font-size:17px;font-family:"Cormorant Garamond",Georgia,serif;font-weight:400;
    justify-content:space-between;width:100%;
    min-height:48px;padding-inline:var(--s-md);
    border-bottom:1px solid var(--outline-light);
  }
  .nav-item:last-child .nav-top{border-bottom:none}
  /**
   * THE OPEN PANEL'S OWN RHYTHM, WHICH IT DID NOT HAVE.
   *
   * Every gap in here was inherited from the desktop panel, where the two
   * columns sit side by side and the vertical distances between them never
   * meet. Stacked they all meet, and they were 0, 4 and 8: the eyebrow started
   * on the exact pixel the accordion header ended, the links sat 4 apart, and
   * the break between the two groups was 8. A group boundary twice the size of
   * an item boundary is not a boundary, so the whole panel read as one
   * undifferentiated list with two gold labels loose in it.
   *
   * Four distances on the 8px scale, each clearly larger than the one below
   * it: 16 off the header, 32 between groups, 12 under an eyebrow, 8 between
   * links. The eyebrow now sits far from the group above and close to the one
   * it names, which is the whole job of an eyebrow.
   */
  .mega{
    position:static;transform:none;min-width:0;
    background:none;border:0;box-shadow:none;
    padding:var(--s-md) 0 var(--s-lg);
    display:none;opacity:1;visibility:visible;pointer-events:auto;
    transition:none;
  }
  .nav-item.is-open > .mega{display:block;transform:none}
  /* the group break, against the 8 between links inside a group */
  .mega-grid{grid-template-columns:1fr;gap:var(--s-lg)}
  /* between links: 8 between the boxes, which is 24 between the text blocks,
     because each link carries 8 of its own padding */
  .mega-col{gap:var(--s-sm)}
  /**
   * No pull-out here, and that is the alignment.
   *
   * At desktop the link's text is pulled out to the panel's own padding rail,
   * because the panel edge is the only thing there is to align to. In the
   * sheet the reference is the accordion header above it, which carries
   * --s-md of its own padding: pulling the links out put their text 16 to the
   * LEFT of the header naming them, which is the same fault the gold eyebrow
   * had, in the other direction. Padding and no margin puts both at 32.
   */
  .mega-link{padding-inline:var(--s-md);margin-inline:0}

  /* The rule divides ITEMS, not an item from its own contents. Left on the
     header it drew a line between "The craft" and the panel belonging to it,
     so an open group read as two things. Open, it moves to the foot of the
     panel and the group is one block. */
  .nav-item.is-open > .nav-top{border-bottom-color:transparent}
  .nav-item.is-open > .mega{border-bottom:1px solid var(--outline-light)}
  .nav-cta .btn{display:none}
  .nav-toggle{display:inline-flex}
}
@media (max-width:600px){
  .brand img{height:42px}
}


/* ---------- hero: static copy, buttons stacked ----------
   Words left, buttons right, and the two buttons run top to bottom rather than
   side by side so the pair reads as one column against the copy block. */
/* Copy left, actions right. The canonical split hero puts the CTA in the copy
   column and gives the right column proof or a picture; with only two buttons
   over there the space between them reads as a hole at any wide viewport. The
   column is stretched to the copy block's height and given a hairline, so it
   is a defined panel the buttons sit at the foot of rather than a pair of
   controls floating against the right edge. */
.hero-head{
  display:grid;grid-template-columns:minmax(0,1fr) auto;
  align-items:stretch;gap:var(--s-2xl) var(--s-4xl);
  /* A deliberate exception to the shared rail: the hero is inset further than
     every other section so the copy and the actions are not thrown to opposite
     edges of a 1336px row. It is the one block on the page with only two
     things in it, so it needs a narrower measure to hold together. */
  padding-inline:var(--s-4xl);
}
.hero-words{min-width:0}
/* a wider measure so the copy carries its own column instead of ending
   halfway across it, which is what left the middle looking empty */
.hero-head .d-xl{margin:0;max-width:18ch}
.hero-head .lead{
  font-size:18px;line-height:1.7;color:var(--ink-muted);
  margin-top:var(--s-md);max-width:58ch;
}
.hero-actions{
  display:flex;flex-direction:column;align-items:flex-start;
  justify-content:center;
  gap:var(--s-1);min-width:232px;
  padding-left:var(--s-4xl);
  border-left:1px solid var(--outline);
}


/* ---------- hero: the featured strip ----------
   One stretched container over the lower edge of the hero image. The client's
   own slider content lives here: three items, their headings, their buttons.
   The stroke across the top is the timer made visible, and it is the only
   thing moving, so the strip does not compete with the headline above it. */
/* The aperture is the shape and it never moves. The pictures live inside it and
   are the only thing that scales, so the ogee stays put while the photograph
   settles. */
.hero-aperture{
  position:absolute;inset:0;overflow:hidden;
}
.hero-img{
  position:absolute;inset:0;width:100%;height:100%;object-fit:cover;
  object-position:center 62%;
  opacity:0;z-index:1;transform:scale(1.10);
  transition:opacity 1ms linear 700ms,transform 1ms linear 700ms;
}
/* the one leaving holds at full opacity underneath until it is covered, then
   cuts. Fading both would put two photographs at partial opacity over the page
   ground, which is the haze this project has already fixed twice elsewhere. */
.hero-img.is-live{
  opacity:1;z-index:2;transform:scale(1);
  transition:opacity 700ms var(--ease-soft),transform 2600ms var(--ease-soft);
}

/* Entrance: a split reveal. The frame opens from its centre line outward, so
   the apex of the ogee appears first and the shape grows into itself. The split
   is an inset on the frame and the ogee is a reference clip on the aperture
   inside it, so the two never fight over one clip-path. */
@media (prefers-reduced-motion:no-preference){
  .js-reveal .hero-media{
    -webkit-clip-path:inset(0 50% 0 50%);
    clip-path:inset(0 50% 0 50%);
  }
  .js-reveal .hero-media.is-in{
    -webkit-clip-path:inset(0 0 0 0);
    clip-path:inset(0 0 0 0);
    transition:-webkit-clip-path 1600ms var(--ease-soft) 120ms,
               clip-path 1600ms var(--ease-soft) 120ms;
  }
  /* the first picture settles over a longer arc than a step between slides */
  .js-reveal .hero-media.is-in .hero-img.is-live{
    transition:opacity 700ms var(--ease-soft),transform 3200ms var(--ease-soft);
  }

  /* The strip under the aperture takes the frame's split, and the same
     duration, easing and delay, so the two open as one object rather than as
     two things that happen to be near each other. It used to be a .reveal,
     which meant it waited for its own observer, arrived on a scroll and
     therefore always came in after the frame had finished.

     The vertical inset is negative and constant, and that is not decoration:
     the card casts `0 18px 48px`, and a clip that ended at inset(0) would trim
     the shadow off the finished state. Held at -80 the shadow is inside the
     clip the whole way through, so only the horizontal split moves.

     Scoped to `.hero` on purpose. component.html carries a .roll specimen with
     no hero around it and no loading screen to release it, and an unscoped
     rule would leave that specimen clipped to nothing forever. */
  .js-reveal .hero .roll{
    -webkit-clip-path:inset(-80px 50% -80px 50%);
    clip-path:inset(-80px 50% -80px 50%);
  }
  .js-reveal .hero .roll.is-in{
    -webkit-clip-path:inset(-80px);
    clip-path:inset(-80px);
    transition:-webkit-clip-path 1600ms var(--ease-soft) 120ms,
               clip-path 1600ms var(--ease-soft) 120ms;
  }
}

.roll{
  /* the dwell per featured item. The tab fill and the JS timer both read this,
     so they cannot drift apart. */
  position:relative;z-index:2;
  margin-top:calc(var(--s-4xl) * -1);
  background:var(--paper);
  border:1px solid var(--outline);
  border-radius:var(--radius);
  box-shadow:0 18px 48px rgba(0,0,0,.10);
  overflow:hidden;
}

.roll-track{display:grid}
.roll-item{
  grid-area:1 / 1;
  display:grid;grid-template-columns:minmax(0,1fr) auto;
  align-items:center;gap:var(--s-xl);
  padding:var(--s-lg) var(--s-2xl);
  opacity:0;visibility:hidden;pointer-events:none;
  transition:none;
}
.roll-item.is-live{
  opacity:1;visibility:visible;pointer-events:auto;
  transition:opacity 420ms var(--ease);
}
.roll-txt b{
  display:block;font-family:"Cormorant Garamond",Georgia,serif;
  font-weight:400;font-size:26px;line-height:1.2;color:var(--ink);
}
.roll-txt span{
  display:block;font-size:14.5px;line-height:1.55;
  color:var(--ink-muted);margin-top:var(--s-xs);max-width:52ch;
}
.roll-cta{white-space:nowrap}

/**
 * THE SLIDE INDICATOR SITS ON THE PICTURE, NOT UNDER THE COPY.
 *
 * It was three 44px rules in the corner of the card below, which is a
 * different primitive from the one the craft rail uses for exactly the same
 * job. There is one progress indicator in this project and this is it: the
 * dots and the pill from .dp-dot, sharing every rule and both colour tokens.
 *
 * Centred on the aperture and held clear of the card that overlaps its foot.
 * The card rises --s-4xl into the picture, so the indicator sits that plus a
 * --s-lg above the arch's base and lands on the photograph rather than on the
 * seam between the two.
 *
 * IT IS INSIDE .hero-aperture ON PURPOSE. The cursor lens is a fixed canvas at
 * z-index 60 and nothing inside the hero's own stacking context can be painted
 * over it. What the lens does instead is carve itself out of anything matching
 * NO_LENS that it finds inside the image's own parent, which is how the stage
 * captions stay legible under it. `.roll-nav` is in that list now, so the lens
 * dots stop at the indicator's edge instead of running across it.
 */
.roll-nav{
  position:absolute;left:50%;
  bottom:calc(var(--s-4xl) + var(--s-lg));
  transform:translateX(-50%);
  z-index:2;
  /* 8 rather than 4: it is the only thing between two 7px dots now that the
     slots no longer pad them apart */
  display:flex;align-items:center;gap:var(--s-sm);
  /* The ground here is a photograph, so it is every colour. A drop shadow on
     the row is what separates the pips from whatever frame is behind them,
     and it costs one filter rather than a scrim over the picture. */
  filter:drop-shadow(0 1px 3px rgba(0,0,0,.55));
}
/* and the resting track is light rather than the muted rule, for the same
   reason: --outline-strong is drawn for paper and vanishes into skin tone.
   Scoped off the active one, which takes --pip-track-on like every other pip;
   unscoped it repainted the pill and won on source order. */
.hero-aperture .roll-tab:not(.is-on) i{background:rgba(255,255,255,.58)}
@keyframes rollFill{ from{transform:scaleX(0)} to{transform:scaleX(1)} }

@media (max-width:1000px){
  .hero-head{grid-template-columns:1fr;align-items:start;gap:var(--s-lg);padding-inline:0}
  .hero-actions{flex-direction:row;flex-wrap:wrap;min-width:0;padding-left:0;border-left:none}
  .roll-item{gap:var(--s-lg);padding:var(--s-lg)}
  .roll-cta{grid-column:1 / -1;justify-self:start}
}
@media (max-width:700px){
  .roll{margin-top:calc(var(--s-2xl) * -1)}
  /* the card overlaps by --s-2xl here rather than --s-4xl */
  .roll-nav{bottom:calc(var(--s-2xl) + var(--s-md))}
  .roll-item{grid-template-columns:1fr;gap:var(--s-md)}
  .roll-txt b{font-size:22px}
  .hero-head .lead{font-size:16px}
}


/* ---------- footer: the crown ----------
   The page opens on the ogee and closes on it. The shape rises out of the
   footer's top edge carrying the two things a buyer needs to act on, which is
   why there is no separate call to action section any more: the ending is one
   block, not two doing the same job. */
/* the statement and its one action, where the closing panel used to be */



/* ---------- footer: the shaped top edge ----------
   Not a shape standing on the footer. The cap is one continuous path that
   starts flat on the footer line, leaves it horizontally so the join is
   concave, rises to the crown and returns to flat. The first version clipped a
   separate block and sat it on top; its vertical sides landed on the line and
   left a square shoulder each side, which is exactly what gives away a shape
   placed above a footer rather than a footer with a shaped edge.

   The fill below the cap is the footer's own background, so cap and footer are
   one silhouette in one colour with no seam to align. */
.foot{
  position:relative;
  color:var(--on-ink);
  padding-block:0 var(--s-xl);
  --crown-h:142px;
  background:linear-gradient(to bottom,
    transparent 0, transparent var(--crown-h),
    var(--ink-deep) var(--crown-h), var(--ink-deep) 100%);
}
.foot-crown{position:relative;height:var(--crown-h);color:var(--ink-deep)}
/* Contained, not full bleed. Stretched across the whole viewport the arch
   flattens into a lazy wave; held to its own width it keeps the proportions it
   was drawn at. The path starts and ends flat ON the footer line, and the
   footer's own background continues at exactly that height either side, so the
   join is seamless without anything having to line up. */
.foot-cap{
  position:absolute;bottom:0;left:50%;transform:translateX(-50%);
  width:min(480px,100%);height:100%;display:block;
}

/* Held by the arch, straddling the footer line.

   Sized to the text rather than guessed: the widest rendered line in either
   column is 151, so two columns and a 32 gutter need 334 and 360 leaves each
   column 13 of slack. That width is what set the arch's, not the other way
   round.

   The block hangs 32 below the line, which puts 34 of its 65 inside the arch.
   The half below sits on the footer's own ink, which is the same colour, so
   the join does not show and the arch does not have to be tall enough to
   swallow the whole block. */
.foot-crown-body{
  position:absolute;left:50%;transform:translateX(-50%);
  bottom:calc(var(--s-xl) * -1);
  width:min(360px,calc(100% - var(--gutter) * 2));
  display:grid;grid-template-columns:1fr 1fr;gap:var(--s-xl);
  text-align:center;z-index:1;
}
.foot-fact p{font-size:13.5px;line-height:1.6;color:rgba(255,255,255,.78)}
.foot-fact a{color:rgba(255,255,255,.78)}
.foot-fact a:hover{color:var(--on-ink)}
.foot-k{
  display:block;font-size:10.5px;font-weight:600;letter-spacing:.16em;
  text-transform:uppercase;color:var(--accent-soft);margin-bottom:var(--s-xs);
}

/* the mark centred at the head of the footer, as the reference does it */
.foot-lead{
  text-align:center;
  /* the facts hang 32 below the crown, so this is 96 less that overhang */
  padding-top:var(--s-4xl);
  padding-bottom:var(--s-3xl);
  margin-bottom:var(--s-2xl);
  border-bottom:1px solid var(--outline-light);
}
.foot-lead .brand{display:inline-flex;margin-bottom:var(--s-md)}
.foot-lead .brand img{height:44px}
.foot-lead p{max-width:46ch;margin-inline:auto;color:rgba(255,255,255,.7)}

@media (max-width:700px){
  /**
   * The facts sit IN the arch here, as they do at desktop.
   *
   * They used to drop into flow below it: stacked they are taller than a 100px
   * crown, and holding them inside a crown that short spilled them onto the
   * paper above as dark text on a light ground. The fix is the crown, not the
   * facts. At 200 the arch has the room, and the block starts 54% of the way
   * down it, which puts about two thirds of the block inside the shape and the
   * rest on the footer's own ink below the line, the same colour, so the join
   * does not show.
   *
   * 54% is also where the arch is wide enough to hold the text: the ogee is
   * about three quarters of its width by that height, and the widest line here
   * is a shade under half of a 390 viewport.
   */
  .foot{--crown-h:200px}
  /* the offset is PADDING on the crown, not margin on the block. As a margin
     it collapsed straight through the crown's top edge and moved the whole
     element down, arch and all, which put the block back below the shape it
     was supposed to be sitting in. */
  .foot-crown{height:auto;padding-top:calc(var(--crown-h) * .54)}
  .foot-cap{top:0;bottom:auto;height:var(--crown-h)}
  .foot-crown-body{
    position:relative;transform:none;width:auto;left:auto;bottom:auto;
    grid-template-columns:1fr;gap:var(--s-md);
    padding-inline:var(--gutter);
  }
  /* the block now ends inside the footer rather than above it, so the mark
     needs its own air rather than the overhang's */
  .foot-lead{padding-top:var(--s-2xl);padding-bottom:var(--s-2xl)}
  /* Bigger than the desktop's 44. It is the only mark in the footer and at 36
     it read as a caption next to the paragraph under it rather than as the
     thing the paragraph belongs to. */
  .foot-lead .brand img{height:56px}
}


/* ==========================================================================
   SECTION RHYTHM, SET OPTICALLY

   One padding value for every section is measured evenly and reads unevenly.
   The gap a reader perceives is not the padding, it is the padding PLUS the
   empty space the section's own content leaves inside its box.

   A section whose content fills its box edge to edge, a colour panel, a media
   band, a grid of bordered cards, contributes nothing of its own, so the
   nominal padding is the whole gap. A section built from a centred heading and
   a short measure of copy carries a screen's worth of internal air already, so
   the same number reads as far more space and the page develops holes.

   So padding is set per section, against what that section actually looks like,
   and the two sides of one section can differ because they face different
   neighbours. Every value is still on the scale; what changes is that the
   choice is made by eye and then written down, rather than assumed.

   Set from a debug overlay pass at 1681x1064. Do not "normalise" these back to
   one value: they are already the correction.
   ========================================================================== */

/* the logo wall: a caption and a thin rail, almost no internal air, and it is a
   continuation of the hero rather than a new subject */
.hero-band{padding-top:var(--s-lg);padding-bottom:var(--s-2xl)}

/* mosaic and copy: fills across, open down the sides */
#closed-setting{padding-top:var(--s-4xl);padding-bottom:var(--s-3xl)}

/* four bordered tiles in one row, so the box is genuinely filled */
.quality{padding-block:var(--s-4xl)}

/* The video band and the rail are a solid section, so by the optical rule they
   take the base token rather than a step down. The step down existed while this
   sat INSIDE the dark band, directly under the quality section with no visual
   boundary between them; it is a light section following a dark one now, and
   that boundary is the hardest on the page. Full section padding both sides. */

/* card grids. They look filled, but each card carries its own padding, so the
   section reads more open than its geometry suggests */
#collections,
#testimonials,
#awards{padding-block:var(--s-3xl)}

/* a portrait beside a short column: more solid than the card grids, less
   filled than the panels. It was the one section this pass never reached, so it
   was still falling back to `.section`'s full rhythm and carrying exactly twice
   its neighbours at every width, 128 against their 64 and 64 against their 32. */
#founder{padding-block:var(--s-4xl)}

/* three columns of short text, mostly air */
#reach{padding-top:var(--s-3xl)}


/* ==========================================================================
   THE DARK BAND

   Quality and Craft run on the dark ground, so the middle of the page reads as
   one block against the paper either side. They are adjacent, so the boundary
   between them is dark on dark and they carry as a single field: the padding
   still separates their content, it just does not draw a line.

   One modifier rather than two bespoke sections, so a third can join the band
   by adding a class. Everything below is the inversion that modifier implies.
   ========================================================================== */
.is-dark{background:var(--ink-deep);color:var(--on-ink)}
.is-dark .d-lg,
.is-dark h2,
.is-dark h3{color:var(--on-ink)}
.is-dark .body,
.is-dark .lead{color:rgba(255,255,255,.72)}

/* gold-deep is a dark gold for light grounds. On the band it disappears, so the
   accent goes back to the brand gold, which measures 9.8:1 here. */
.is-dark .mark{color:var(--accent-soft)}

.is-dark .pillar h3{color:var(--on-ink)}
.is-dark .pillar p{color:rgba(255,255,255,.7)}

/* the rail sat on --panel, a light grey, which on the band became a white box */
.is-dark .dp-read .n{color:var(--accent-soft)}
.is-dark .dp-read h3{color:var(--on-ink)}
.is-dark .dp-read p{color:rgba(255,255,255,.72)}
.is-dark .dp-tab{background:var(--outline-light)}
.is-dark .dp-tab::after{background:var(--accent-soft)}
/**
 * On the dot it is the TRACK that takes the colour, never the button.
 *
 * These two read `.dp-dot` and `.dp-dot.is-on` for a long time, which paints
 * the button's own box: a 24 by 20 slot, twelve of them side by side. The row
 * rendered as one continuous grey band with a gold rectangle in it, and the
 * round track inside was invisible underneath. It was read as the dots being
 * square, and the shape of the dot was never the problem.
 */
/* the RESTING track only. Unscoped it also repainted the active pill, which is
   the one that must keep its gold tint, and it won on source order. The fill
   needs no rule here at all: --pip-fill is already the bright gold on any dark
   ground. */
.is-dark .dp-dot:not(.is-on) i{background:var(--outline-light)}
.is-dark .dp-stage:focus-visible,
.is-dark .dp-dot:focus-visible{outline-color:var(--accent-soft)}

/* the ghost button is drawn for a light ground */
/* The secondary button does NOT invert on a dark ground any more. A beige
   fill with a near-black label is already legible on burgundy, and inverting
   it was the fourth button appearance on the page. */
.is-dark .btn-ghost{color:var(--on-beige);border-color:rgba(0,0,0,.16)}
.is-dark .btn-ghost:hover{border-color:rgba(0,0,0,.34)}


/* ==========================================================================
   TEXT LINKS DECLARE THEMSELVES

   A rule that draws itself in from the left on hover and on keyboard focus.
   On a pseudo element, because text-decoration cannot be transitioned and a
   border would move the line box every time it appeared.

   Not on everything that happens to be an anchor. Buttons, the logo lockups,
   the image tiles and the icon row are already unmistakably clickable, and a
   rule under those reads as a rendering fault rather than an affordance. What
   is left is the case that genuinely needs it: a link sitting inside a
   sentence or a list, where nothing but colour distinguishes it.
   ========================================================================== */
/* Prose only. A rule belongs under a link that sits inside a sentence, where
   nothing but colour would otherwise distinguish it from the words either
   side. Navigation is not a sentence: menu items, footer link lists and
   buttons are targets, and they take the dot below instead. */
p a:not(.btn):not(.tile):not(.brand){position:relative}

p a:not(.btn):not(.tile):not(.brand)::after{
  content:"";
  position:absolute;left:0;right:0;bottom:-2px;height:1px;
  background:currentColor;opacity:.55;
  transform:scaleX(0);transform-origin:left center;
  pointer-events:none;
}

@media (prefers-reduced-motion:no-preference){
  p a:not(.btn):not(.tile):not(.brand)::after{transition:transform 320ms var(--ease)}
}

p a:not(.btn):not(.tile):not(.brand):hover::after,
p a:not(.btn):not(.tile):not(.brand):focus-visible::after{transform:scaleX(1)}


/* ==========================================================================
   THE HOVER DOT

   What navigation and controls get instead of a rule. A small filled circle
   appears to the left of the label: a marker that says "this one", rather
   than a line that says "this is a link", which is a thing a menu item does
   not need to be told.

   It also replaces the grey panel the mega menu used to fill in behind
   whatever the pointer was over. That panel was doing the same job with a
   great deal more ink.

   Absolutely positioned, never reserving space in the flow. That is the whole
   reason it is not an inline pseudo element with a margin: nothing on the
   page may move when the pointer arrives.
   ========================================================================== */
/**
 * ONE RULE, ONE VALUE, EVERY PLACE THE MARK APPEARS.
 *
 * The rule: on hover the text shifts right by --dot-shift, and the dot sits at
 * MINUS that distance from it, which lands it exactly where the text began.
 * Both are on the same element, so they travel together and the arithmetic
 * cannot drift; at rest the dot is scaled to nothing, so its negative offset is
 * not occupying anything either, and nothing is ever drawn outside the box.
 *
 * The dot is anchored to the TEXT LINE, never to the element's box. That is why
 * the mega menu's dot is on its <b> and not on the row: the row is a title and
 * a description, so its middle is the space between two lines, and the dot was
 * sitting there rather than beside the title. Anything that can wrap gets an em
 * based offset instead, which is half a line height and therefore the middle of
 * the first line whether it wrapped or not.
 *
 * Buttons keep half the exception and lose the other half. The label still
 * holds still, because it is centred inside its own padding and shifting it
 * 14px right makes it visibly off centre for as long as the pointer is there.
 * But the dot is anchored to the LABEL now, not to the button's padding box.
 * Parked at a fixed inset from the button's edge it sat in open space with the
 * label somewhere else entirely, reading as a mark on the button rather than
 * as the mark that appears where a word begins, which is the only thing it
 * means anywhere else on the page. That is what .btn-t is for: it is the
 * label, so the dot has something to be beside.
 */
.nav-top,
.mega-link b,
.foot-links a,
.tile-body h3,
.btn-t{position:relative}

/* it is the label, and it must not become a line box of its own */
.btn-t{display:inline-block}

.nav-top::before,
.mega-link b::before,
.foot-links a::before,
.tile-body h3::before,
.btn-t::before{
  content:"";
  position:absolute;
  left:calc(var(--dot-shift) * -1);
  width:var(--dot-size);height:var(--dot-size);
  /* margin, not translate, so transform is free to carry the scale alone */
  margin-top:calc(var(--dot-size) / -2);
  border-radius:var(--radius-round);
  background:var(--dot);
  opacity:0;
  transform:scale(0);
  pointer-events:none;
}

/* single line text: the middle of the box IS the middle of the line */
.nav-top::before,
.mega-link b::before,
.foot-links a::before{top:50%}

/* can wrap, so half a line height from the top finds the first line */
.tile-body h3::before{top:.6em}

/* the label is one line by construction: .btn is white-space:nowrap */
.btn-t::before{top:50%}
/* the nav's button carries --s-lg of padding rather than --s-xl, and the full
   shift plus the dot's own width would leave it 4px off the button's edge */
.nav-cta .btn{--dot-shift:10px}

/**
 * A button with no label needs no rule any more, and that is the point.
 *
 * The mark exists to appear where a word begins. On the rail's arrows there is
 * no word, only an icon centred in a 48px square, and the dot used to arrive
 * beside the arrow glyph as a second unexplained mark inside the same button.
 * It took a `.dp-arrow::before{content:none}` to suppress, which is a list
 * every future icon-only control has to be added to. Hanging the dot on the
 * label instead means a control with no label has nowhere to put one.
 */

@media (prefers-reduced-motion:no-preference){
  .nav-top::before,
  .mega-link b::before,
  .foot-links a::before,
  .tile-body h3::before,
  .btn-t::before{
    transition:transform 260ms var(--ease),opacity 180ms var(--ease);
  }
  .nav-top,
  .mega-link,
  .foot-links a,
  .tile-body h3{transition:transform 260ms var(--ease)}
}

/* the shift */
.nav-top:hover,.nav-top:focus-visible,
.mega-link:hover,.mega-link:focus-visible,
.foot-links a:hover,.foot-links a:focus-visible,
.tile:hover .tile-body h3,.tile:focus-visible .tile-body h3{
  transform:translateX(var(--dot-shift));
}

/* and the dot arriving in what it opens up */
.nav-top:hover::before,.nav-top:focus-visible::before,
.mega-link:hover b::before,.mega-link:focus-visible b::before,
.foot-links a:hover::before,.foot-links a:focus-visible::before,
.tile:hover .tile-body h3::before,.tile:focus-visible .tile-body h3::before,
.btn:hover .btn-t::before,.btn:focus-visible .btn-t::before{
  opacity:1;transform:scale(1);
}


/* ==========================================================================
   WAX SEALS

   The four quality marks are pressed into wax rather than drawn. Each seal
   keeps its own mark inside it: that SVG is the emblem the die presses, and it
   is also what stays on screen when the seal cannot run, which is the case
   under prefers-reduced-motion and on anything without WebGL2. So the section
   is never waiting on a canvas to have content.

   96 rather than the mark's 44. A seal is an object with a rim, a recessed
   field and a raised emblem, and at icon size none of that is legible; it just
   reads as a gold blob. The size is what makes it a seal instead of an icon.
   ========================================================================== */
.seal{
  --seal-wax:var(--silver);
  position:relative;
  width:160px;height:160px;
  margin-bottom:var(--s-lg);
}
.seal .mark{
  position:absolute;top:50%;left:50%;
  transform:translate(-50%,-50%);
  margin-bottom:0;
}
/* The mark goes the moment the script commits to rendering, not when the
   first canvas appears. Hiding it on is-live meant the drawn icon was on
   screen until the bake finished and then swapped for a ball, which is
   the jump: two unrelated images in the same box, a beat apart. */
.js-seal .seal .mark{display:none}
.seal canvas{display:block;width:100%;height:100%}

@media (max-width:600px){
  .seal{width:128px;height:128px;margin-bottom:var(--s-md)}
}


.band{position:relative}


/* ==========================================================================
   THE BAND GEM

   A brilliant cut turning behind the dark band, fixed at the right of the
   viewport, one axis, driven by how far through the section the reader is.

   The layering is the only fiddly part. The gem has to sit ABOVE the dark
   ground and BELOW everything written on it, and the ground was on the two
   sections themselves, which would have buried it. So the band carries the
   ground now and its sections go transparent over the top; the gem takes
   z-index 0 in between, and the content takes 1. The rail keeps 2, because a
   progress marker that a decoration can pass in front of is not a marker.

   It is sticky rather than fixed, and the difference is the whole behaviour.
   Fixed, it was pinned to the viewport and had to be switched off by an
   observer once the band scrolled away, which meant it could still be caught
   overhanging the paper sections at the boundary. Sticky is bounded by its
   containing block by definition: it travels with the band, pins at the middle
   of the viewport while the band passes through, and is released before the
   band ends. It cannot leave the dark ground because the geometry will not let
   it.

   The track is what puts the padding at each end. Insetting the sticky
   element's containing block by 64 top and bottom is what makes it stop short
   of the band's own edges rather than running flush into the sections either
   side.
   ========================================================================== */
/* clip, not hidden, and on both axes. overflow:hidden would make the band a
   scroll container and the gem's position:sticky would then have nothing to
   stick to; clip crops without creating one. This is the outer guarantee that
   the stone cannot appear over the paper sections either side, and the track
   below is the inner one. */
.band{background:var(--band-ground);overflow:clip;position:relative}
/* The rays canvas fills the band and sits under everything the band holds.
   It is additive light on the ground, so it needs no blend mode of its own
   here; the shader does the adding. Fades in on first intersection so the
   first painted frame is not a hard switch. */
.rays-canvas{
  /* STICKY AND ONE VIEWPORT TALL, not absolute over the whole band.
     Spanning the band meant a 1905x2405 canvas redrawing every frame beside
     the stone's own two-pass render, 3.7 times the fill for no gain, and it
     put the ray origin a screen and a half above anything visible for most of
     the section. Stuck to the top of the screen the light comes from where
     light comes from, and the canvas is the size of what you can actually see.
     The negative bottom margin is what keeps it out of the flow. */
  position:sticky;top:0;
  display:block;width:100%;height:100vh;
  margin-bottom:-100vh;
  z-index:0;pointer-events:none;
  opacity:0;transition:opacity 1.2s var(--ease);
}
.rays-canvas.is-in{opacity:1}
.band > .is-dark{background:transparent}
.band > section{position:relative;z-index:1}

/* Big, low in the frame, and bounded by the band.

   Sticky inside a track rather than fixed. Fixed held it against the viewport
   and needed an observer to switch it off, which still let it be caught hanging
   over the paper section above at the boundary. A sticky element cannot leave
   its containing block, so insetting that block by 64 top and bottom is what
   gives the stone its padding from the band's own edges. It travels in with the
   band, sits low in the viewport while the band passes through, and is released
   before the band ends. */
.band-gem-track{
  position:absolute;
  left:0;right:0;
  /* Padding at the top, none at the bottom, and that asymmetry is the point.
     The stone enters clear of the section above, and at the end of the band it
     travels all the way down to the boundary instead of stopping short of it.
     It used to be inset 64 here as well, which left a strip of empty dark band
     under the stone at the close of the section. */
  top:var(--s-3xl);bottom:0;
  z-index:0;
  pointer-events:none;
}

/* The canvas box, declared once. It was written twice, in the height and again
   inside the sticky offset, which is one number in two places: change the size
   and the stone silently stops resting where it was meant to. */
.band-gem-track{--gem-h:min(79vw,1121px)}

.band-gem{
  position:sticky;
  /* bottom anchored: its foot rests a padding above the viewport floor */
  top:calc(100vh - var(--gem-h) - var(--s-3xl));
  margin-inline:auto;
  /* Sized so the STONE, not the canvas, is a little wider than the content
     rail: the shader's field of view puts the stone at 1.345 times the canvas
     height, so 1121 of height renders it about 1507 across against a 1361
     rail. It overhangs the margin by roughly 73 each side, which is the peek.
     The canvas is wider than tall because the empty air either side costs
     nothing and lets the stone sit off centre as it turns.

     Both numbers grew by 1.0676 when the framing was fixed, and that factor is
     not decoration: the field widened by exactly the same amount, so the stone
     renders at the identical size it did before and the extra canvas is the
     clear margin that stops it being cut. The two have to move together. */
  width:min(126vw,1794px);
  height:var(--gem-h);
  opacity:0;
}

@media (prefers-reduced-motion:no-preference){
  .band-gem{transition:opacity 900ms var(--ease)}
}

/* Held down, because the section's copy runs straight across it. The shader
   clamps its own output for the same reason; this is the second half of that
   budget. */
.band-gem.is-in{opacity:.55}

/**
 * ON A PHONE IT IS SMALLER, LOWER AND FAINTER, RATHER THAN ABSENT.
 *
 * It used to be `display:none` below 900, on the grounds that there is nowhere
 * on a narrow screen for a stone that size to sit that is not behind text. The
 * premise was right and the conclusion was too blunt: the band is the page's
 * one dark section and the stone is the only thing in it, so removing it left
 * the phone with a flat dark rectangle where the desktop has the section's
 * whole idea.
 *
 * Three changes make it safe rather than one:
 *
 *   - SMALLER, so it is a stone inside the band rather than a field the copy
 *     is laid over. At 150vw of canvas the shader's field renders it about
 *     three quarters of the viewport across instead of overhanging it.
 *   - LOWER, so its widest part sits under the gap below the copy rather than
 *     across the middle of it. The top of the stone is the narrow end.
 *   - FAINTER. 0.55 is a desktop budget, measured against copy that crosses
 *     the stone at a desktop measure. A phone line crosses more of it and at a
 *     larger relative size, so the ceiling comes down to 0.34.
 *
 * The shader is unchanged and still clamps its own output; this is the second
 * half of that budget, as it is at desktop.
 */
@media (max-width:900px){
  .band-gem-track{
    --gem-h:min(96vw,520px);
    top:var(--s-2xl);
  }
  .band-gem{
    width:min(150vw,780px);
    /* its foot rests --s-2xl above the viewport floor rather than --s-3xl */
    top:calc(100vh - var(--gem-h) - var(--s-2xl));
  }
  .band-gem.is-in{opacity:.34}
}


/* ==========================================================================
   THE DARK BAND: A STAGE FOR THE STONE.

   The band holds one section now. The video and the twelve stage rail moved out
   to their own section below it, so the stone is not sharing its ground with
   two other things competing for the same scroll.

   DESKTOP. The stone is centred, large, and sticks in the middle of the
   viewport for the length of the section. The four claims cross it as glass
   cards, one at a time, each rising in from below and leaving at the top. They
   are scattered across the measure rather than ruled into a grid: four cards in
   a 2x2 read as a specification table, and the point here is that each one gets
   its own moment against the stone.

   PHONE. The same idea as two rows, the stone pinned at the top and the copy
   travelling under it through a fade. That is further down this file and it is
   deliberately untouched.
   ========================================================================== */

.qs-grid{position:relative}
.qs-stage{min-width:0}
.qs-content{min-width:0}

/* The wrapper is not a layout box until the pinned mode wants one. Everywhere
   else it is display:contents, so the phone layout below and the no support
   fallback both behave exactly as if it were not in the markup. */
.qs-pin{display:contents}

/* FULL STRENGTH AT EVERY WIDTH, and it has to be stated outside the desktop
   block. The backdrop treatment holds the stone at .55, and at .34 under 900,
   because its copy runs straight across the stone and those are measured
   contrast budgets. Nothing crosses the stone in this treatment: the cards
   carry their own glass, the heading its own ground, and on a phone the copy
   travels UNDER the stone rather than over it. Scoped inside the min-width
   block this rule simply never reached the phone, which went on rendering the
   stone at a third of its brightness. */
.band--stage .band-gem.is-in{opacity:1}

/* ==========================================================================
   DESKTOP: ONE PINNED FRAME, AND THE CARDS ARE THE ONLY THING THAT MOVES.

   The first attempt put the cards in normal flow with a parallax on top. That
   is the wrong mechanism and it reads as exactly what it is: the cards ARE the
   page scrolling, so the section feels like four cards parked at four points
   with dead scroll in between. A transform running at 1.5 times the scroll is
   still the scroll.

   So nothing here is in flow. `.qs-grid` is a tall track that supplies the
   scroll and carries a named view timeline; `.qs-pin` sticks to the top of the
   viewport at exactly 100vh for the whole of it; the stone, the heading and the
   four cards are absolutely placed inside that pinned frame. The page stops
   moving, and the only thing the scroll drives is each card's own transform,
   from below the frame to above it. That is the difference between watching
   cards go past a fixed scene and scrolling a column of cards.

   Each card owns a slice of the timeline, overlapping its neighbour slightly so
   one is arriving as the last leaves, and `left` scatters them across the
   frame. The vertical travel is a straight line for every one of them.

   ALL OF IT IS INSIDE @supports AND prefers-reduced-motion. Without a scroll
   timeline there is nothing to place the cards along, so an absolutely placed
   card parked at translateY(108vh) would simply never be seen. The base layout
   stays in flow and stays readable, which is what a browser without this, and a
   reader who has asked for stillness, both get.
   ========================================================================== */
@media (min-width:821px){
  .band--stage{
    /* The strip the pinned heading owns, and therefore the line the stone has
       to stay under. Everything below is measured off it.

       A floor, not just a proportion, and the floor is the point: the heading
       is a fixed number of text lines, so its height barely moves with the
       viewport, while 18vh does. On a 620 tall window 18vh is 112 against a
       heading that still needs about 156, and the clearance measured 4px. That
       is the collision, and on a slightly wider wrap it would have been a
       negative number and the copy would have been sitting on the table again.
       190 is the heading at its tallest plus the plate's own --s-2xl lead. */
    --head-zone:max(20vh, 210px);
    /* the heading's own offset, clear of the floating bar rather than tucked
       under it: --s-3xl, not --s-md */
    --head-top:calc(var(--nav-h) + var(--s-3xl));
    --stage-top:calc(var(--head-top) + var(--head-zone));
    /* how much scroll each card gets to cross the frame */
    --card-span:60vh;
  }

  .qs-stage{position:absolute;inset:0;z-index:0;pointer-events:none}
  .qs-content{position:relative;z-index:1}

  .band--stage .band-gem-track{
    /* Three ceilings, because three different things can be the binding one:
       half the measure, whatever the viewport has left under the heading, and a
       hard cap so it does not become a wall on a 27 inch. */
    --gem-h:min(46vw, calc(100vh - var(--stage-top) - var(--s-3xl)), 560px);
    top:0;bottom:0;
  }
  .band--stage .band-gem{
    /* ABSOLUTE, NOT STICKY, AND THIS IS THE CUT-OFF BUG.
       The base rule makes the stone `position:sticky`, which is right for the
       backdrop treatment where the stone has to travel the band on its own. It
       is wrong here, because a sticky element resolves `top` against the
       VIEWPORT and the pin is already doing the sticking. Measured at 1920x651
       during the band's entry: the pin sat at y 156, so the stone should have
       painted at 156 + 378 = 534, and instead it stuck at 378, which is above
       the heading's own bottom edge at 448. The stone crossed the copy and its
       crown was clipped by the frame's top edge. Absolute puts it back inside
       the track, where the head-zone geometry already keeps the two apart. */
    position:absolute;
    /* CENTRED IN WHAT IS LEFT UNDER THE HEADING, NOT IN THE VIEWPORT.
       Centred in the viewport the stone runs up behind the heading, and the
       only way to keep that copy readable is then to lay a scrim over the
       stone, which hazes the one thing the section exists to show. Geometry
       instead of a gradient: keep them apart and neither needs protecting. */
    top:calc(var(--stage-top) + (100vh - var(--stage-top) - var(--gem-h)) / 2);
    width:100%;
    height:var(--gem-h);
  }

  /* The heading. NOT STICKY, and that is a fix rather than a simplification.
     It used to be `position:sticky; top:var(--head-top)`, which was dead code
     in the pinned build and a bug everywhere else. Dead, because the pinned
     stage overrides this to `position:absolute` a few rules down. A bug,
     because the stage needs BOTH a scroll timeline AND no reduced-motion
     preference, so anyone missing either got a pinned heading with four fully
     opaque cards travelling up through it. In the pinned build the cards are
     faded out by `uspFly` before they can reach the copy; without the timeline
     that fade does not run, so nothing was stopping them.
     Measured before this change, at 1440x900, worst card-over-heading overlap:
       Chrome                        64px, card at opacity 0.36   (by design)
       Chrome + prefers-reduced-motion  156px, card at opacity 1.00
       Firefox                          156px, card at opacity 1.00
     Static, the heading simply scrolls and the cards flow underneath it, which
     is what the fallback layout was always shaped for. `position:relative` is
     kept only so `z-index` still applies, since z-index is inert on a static
     box and the heading has to stay over the stone. */
  .band--stage .qs-content > .center{
    position:relative;
    z-index:3;
    padding-bottom:var(--s-lg);
  }
  /* A halo on the glyphs instead of a panel behind them. The card is faded out
     before it can reach this copy, so this only has to cover the last sliver of
     that overlap; hugging the letters means there is no edge to see and nothing
     is occluded on the way past. */
  .band--stage .qs-content > .center h2,
  .band--stage .qs-content > .center p{
    text-shadow:0 0 16px var(--band-ground), 0 0 32px var(--band-ground);
  }
  /* NO PLATE BEHIND THE HEADING, and removing it is a fix rather than a
     simplification. A band of solid ink from 72 to 252 sat across the frame,
     and a card rising through it lost its TITLE while its body was still in
     clear air: the card looked broken in half on the way past. It was there to
     stop the stone showing above the bar, which is not its job and never
     worked, because the stone is 126px below it and the thing actually
     protecting that strip is the pin's clip.
     The reference has no plate either. Its heading simply paints over the
     cards, and white copy over dark glass is a contrast the card's own ground
     already guarantees. */

  .band--stage .pillars{display:block;margin-top:var(--s-4xl)}
  .band--stage .pillar{
    width:min(420px, 32vw);
    margin-bottom:40vh;
    padding:var(--s-2xl);
    border-radius:var(--radius);
    /* WHITE GLASS, AND THE ALPHA IS A CONTRAST FIGURE, NOT A LOOK.
       The stone renders at full strength under these, so the card's own ground
       is the only thing standing between white copy and a white facet.
       0.90, measured rather than chosen, and re-solved for the burgundy. The old
       ink was a darker base, so .78 was enough for it; Bordeaux is (90,30,45)
       against the ink's (24,22,38) and the same alpha let the facet through,
       dropping the body copy to 4.06:1, under AA. At .90 the worst card over
       the brightest facet resolves to (106,51,66) and the body text lands at
       5.38:1, which is the 5.36 the ink version was solved to. */
    background:rgba(255,255,255,.74);
    border:1px solid rgba(255,255,255,.58);
    box-shadow:
      inset 0 1px 0 rgba(255,255,255,.85),
      inset 0 -1px 0 rgba(255,255,255,.28),
      0 24px 64px rgba(0,0,0,.34);
    -webkit-backdrop-filter:blur(26px) saturate(135%);
    backdrop-filter:blur(26px) saturate(135%);
  }
  .band--stage .pillar:last-child{margin-bottom:18vh}
  .band--stage .pillar:nth-child(1){margin-inline-start:6%}
  .band--stage .pillar:nth-child(2){margin-inline-start:56%}
  .band--stage .pillar:nth-child(3){margin-inline-start:18%}
  .band--stage .pillar:nth-child(4){margin-inline-start:62%}

  /* The card owns its own entrance, so the observer driven reveals inside it
     are switched off. Two fades over one element compound into a slower,
     muddier third and neither is in charge of it. */
  .band--stage .pillar .reveal,
  .band--stage .pillar .reveal.is-in{opacity:1;transform:none;transition:none}

  /* The card is light now, so everything inside it inverts back to the page's
     own ink. `.is-dark` sets these to the light colours for the band as a
     whole, and the card is a light object sitting inside a dark section. */
  .band--stage .pillar h3{color:var(--ink)}
  .band--stage .pillar p{color:var(--ink-muted)}
  .band--stage .pillar .mark{color:var(--wine)}

  @media (prefers-reduced-transparency:reduce){
    .band--stage .pillar{
      background:#FFFFFF;
      -webkit-backdrop-filter:none;
      backdrop-filter:none;
    }
  }

  /* ---------------------------- the pinned frame ---------------------------- */
  @supports (animation-timeline: view()){
    @media (prefers-reduced-motion:no-preference){
      .band--stage .qs-grid{
        /* one viewport of frame, plus one card-span of scroll per card */
        height:calc(100vh + 4 * var(--card-span));
        /* the four ranges overlap, so the pass is one continuous stream of
           cards rather than four separate turns with gaps between them */
        view-timeline-name:--stagepass;
      }
      .band--stage .qs-pin{
        display:block;
        position:sticky;
        top:0;
        height:100vh;
        /* THE FRAME IS CLIPPED BELOW THE BAR, NOT AT ITS OWN TOP EDGE.
           This is the line the whole card motion hangs off. The reference clips
           its stage at y 0 and lets cards keep travelling past it, which is why
           they sweep the entire height: the clip is what hides them, not the
           fade. Clipping at 0 here does not work, because the bar floats and
           the strip above and beside it is transparent page, so a card is still
           visible around it. Clip at the bar's own lower edge instead and the
           card gets the same full sweep with nothing to spill into.
           A short fade rides the last part of it so the card dissolves as it
           reaches the line rather than being guillotined by it. */
        clip-path:inset(calc(var(--nav-h) + var(--s-lg)) 0 0 0);
      }

      .band--stage .qs-content{position:absolute;inset:0;pointer-events:none}
      .band--stage .qs-content > .center{
        position:absolute;
        top:var(--head-top);
        left:0;right:0;
        margin-inline:auto;
        max-width:var(--container);
        padding-inline:var(--gutter);
        pointer-events:auto;
      }

      .band--stage .pillars{position:absolute;inset:0;margin:0}
      .band--stage .pillar{
        position:absolute;
        top:0;
        margin:0;
        opacity:0;
        transform:translateY(100vh);
        pointer-events:auto;
        will-change:transform,opacity;
        animation:uspFly linear both;
        /* The SECTION's timeline, not the card's. A card pinned inside a sticky
           frame never travels through the viewport, so view() on the card would
           resolve against a box that never moves and the animation would never
           advance. */
        animation-timeline:--stagepass;
      }

      /* Scattered across the frame, each on its own slice of the pass, the
         slices overlapping a little so one card is arriving as the last leaves.
         `contain` is the range where the track fully covers the viewport, which
         is exactly the stretch the pin is stuck for. */
      .band--stage .pillar:nth-child(1){left:6%;  animation-range:contain 0%  contain 34%}
      .band--stage .pillar:nth-child(2){left:56%; animation-range:contain 22% contain 56%}
      .band--stage .pillar:nth-child(3){left:18%; animation-range:contain 44% contain 78%}
      .band--stage .pillar:nth-child(4){left:62%; animation-range:contain 66% contain 100%}
      .band--stage .pillar:nth-child(n){margin-inline-start:0}

      /* THE CARD IS GONE BEFORE IT REACHES THE BAR, AND THAT IS THE WHOLE
         DIFFERENCE FROM THE REFERENCE.
         There the navigation is a solid full width bar, so a card can keep its
         opacity all the way up and simply slide under it; measured on that site
         a card is still 40% opaque at 251px ABOVE the viewport top, and the
         stage's own clip is what hides it. This bar floats, with transparent
         page above it and a margin down each side, so a card doing the same
         thing shows up around the bar. It has to be at zero before it gets
         there, so the travel stops at 22vh and the fade completes with it.
         Enter from a full viewport below, rise, dissolve just under the
         heading. */
      /* A FULL SWEEP, BOTTOM TO TOP.
         The travel was cut to 22vh in the last pass to keep the card away from
         the floating bar, and that traded the whole point away: 78vh of travel,
         a third of it spent fading, reads as a card sitting still. With the
         frame clipped below the bar the card can run the entire height and off
         the top, 145vh of travel against a 34% slice of the pass, which is 1.8
         times the scroll. The fade is a finish on the movement now, not a
         substitute for it. */
      /* THE FRAME DOES NOT CLOSE OUT. IT SCROLLS OUT.
         There used to be a `stageOut` fade here, taking the whole pinned frame
         to zero across the last 14% of the pass, so that once the fourth card
         had gone the stone and the heading went with it and the band left as
         flat ink. The reason was the spill: the frame's clip-path is anchored
         to the frame, not to the viewport, so the moment the pin releases the
         clip line rides up with it and the stone paints through the
         transparent strip above the floating bar and down the gutters beside
         it.
         That is accepted now, and it is accepted because it is what every
         other section on this page already does. The bar floats over ordinary
         content everywhere else and nothing else dissolves to avoid it, so the
         fade was the section behaving differently from the rest of the page in
         order to solve a problem the rest of the page does not treat as one.
         What it cost was the ending: the reader finishes the fourth card and
         the subject of the section evaporates in front of them, with a third
         of a viewport of empty ink still to scroll.
         So the pin now simply releases and the whole frame travels off the top
         with the band, heading and stone intact, the way a pinned scene is
         supposed to hand back the page. The stone keeps turning the entire
         way: band-gem.js runs its pass to the point the band's bottom edge
         clears the top of the viewport, not to the point the pin lets go. */

      /* The fade window is set by the HEADING, not by the top of the frame.
         The card has to be gone before its own title reaches the heading's
         copy, or the two sets of words cross and both are unreadable. At 761
         tall the travel is 761 to -342, the heading's copy ends at 292, and a
         card's title sits about 120 below its top: fading out across 40% to
         58% puts the card's title at 440 when the fade starts and 341 at the
         half way point, both clear of the heading, and at zero before it could
         reach it. By then the card has crossed the whole stone, which is the
         thing it is meant to travel past. */
      @keyframes uspFly{
        0%   {opacity:0;transform:translateY(100vh)}
        18%  {opacity:1}
        40%  {opacity:1}
        58%  {opacity:0}
        100% {opacity:0;transform:translateY(-45vh)}
      }
    }
  }
}

/* PLAIN DRAWN MARKS, NOT PRESSED WAX.
   wax-seal.js puts js-seal on the root and that hides EVERY drawn mark on the
   page, because normally a canvas has just replaced it. This section opts out
   of the canvas (data-seal-plain on the band), so the mark has to be handed
   back or the pillars come out with four empty boxes. The seal box itself goes
   with it: 160 square is the wax coin's frame, and there is no coin here, so
   the icon sizes and spaces like every other icon on the page. */
.js-seal .band--stage .seal .mark{display:block}
.band--stage .seal{
  width:auto;height:auto;
  margin-bottom:0;
}
.band--stage .seal .mark{
  position:static;
  transform:none;
  margin-bottom:var(--s-lg);
}

/* THE ROW SPLIT: the stone pins, everything else travels past it.
 *
 * The grid is dropped for normal flow here, and that is the whole mechanism.
 * A sticky grid item is bounded by its own grid AREA, and a stage sized to the
 * stone occupies a row exactly as tall as itself, so there is nothing for it to
 * travel through and it never sticks. In flow its containing block is the
 * section, so it pins under the bar and the content rides up over it.
 *
 * The stone sits ABOVE the copy and carries its own ground with a fade off the
 * bottom of it. That is a contrast requirement as much as a composition: the
 * stone renders at full strength in this treatment, so a line of body copy
 * crossing it would be white on white. Under it, against an opaque ground that
 * fades out below the stone, every line meets the same gradient at the same
 * place and the guarantee holds at every scroll position rather than at one. */
@media (max-width:820px){
  .qs-grid{display:block}

  .qs-stage{
    position:sticky;
    /* clear of the floating bar, by the bar's own token */
    top:calc(var(--nav-h) + var(--s-md));
    min-height:0;
    /* ABOVE the copy, not behind it. Putting the stone behind an opaque content
       block does pin it, and it also means the stone is gone from the second
       screen onward, which is the opposite of what pinning it was for. The
       copy travels UNDER the stone and dissolves as it goes. */
    z-index:2;
    /* it is a picture; the copy behind it is the thing to hit */
    pointer-events:none;
  }
  /* The ground the stone stands on.
     The canvas is transparent everywhere the stone is not, so without this the
     copy sliding underneath shows straight through its corners. Solid for the
     first two thirds, which is the part the stone actually occupies, then out
     to nothing: that tail IS the fade between the stone and the containers, and
     it is anchored to the stone rather than to the copy, so every line meets it
     at the same place. Pulled out to the viewport edges because .shell has
     already inset this box by the gutter. */
  .qs-stage::before{
    content:"";
    position:absolute;
    left:calc(var(--gutter) * -1);
    right:calc(var(--gutter) * -1);
    /* Up to the viewport's own top edge, not a fixed 96.
       The stage pins at nav + 16, so that is exactly the distance to the top of
       the screen, and anything less leaves the strip ABOVE the floating bar
       uncovered: the bar does not span it, so a pillar scrolling out showed
       through the gap over the stone. One more --s-md so it is not a hairline
       decision at a rounding boundary. */
    top:calc((var(--nav-h) + var(--s-md) + var(--s-md)) * -1);
    bottom:calc(var(--s-4xl) * -1);
    /* THE FADE IS A LENGTH, NOT A PERCENTAGE, AND THAT IS THE WHOLE POINT.
       This was one gradient holding solid to 78% of the box. 78 was measured
       against 96 above the stage and 96 below it, and it was right at exactly
       the width it was measured at: the spacing scale steps down under 600, so
       --s-4xl becomes 48, the box stops being 436 tall, and 78% lands 36px
       ABOVE the stone's tip. The copy went straight back to ghosting through
       its lower facets, which is the bug this number was chosen to fix.
       Two layers instead. The lower one is a fade exactly --s-4xl tall pinned
       to the bottom, which is exactly the overhang below the stone, so the fade
       is always under the stone and never on it. The upper one is flat ink for
       whatever is left. No arithmetic, and nothing to re-measure per width. */
    background:
      linear-gradient(to bottom, var(--band-ground), transparent)
        bottom / 100% var(--s-4xl) no-repeat,
      linear-gradient(var(--band-ground), var(--band-ground))
        top / 100% calc(100% - var(--s-4xl)) no-repeat;
    z-index:-1;
    pointer-events:none;
  }

  /* Both boxes come back into normal flow, and that is what makes the stage
     hold itself up. Absolute inside a stage with no content of its own needs
     the stage to be given a height, and every height available to guess with is
     a vw figure: at 375 the scrollbar takes 15 of those, so 66vw is 247 against
     a stage only 328 wide, the stone renders at 1.368 x 247 = 338 and loses 5px
     off each shoulder. In flow there is nothing to guess. The stone's own ratio
     sets the height, the stage takes its height from the stone, and the
     arithmetic is right at every width by construction. */
  .band--stage .band-gem-track{position:static}
  .band--stage .band-gem{
    position:static;
    width:100%;
    /* The shader's field of view, declared as geometry instead of guessed.
       1.368, not the 1.345 this was first written with: that figure came off a
       comment rather than off the solver, and it is 1.7% short, which put the
       stone's widest pose just outside its own canvas and shaved both
       shoulders. Solved against the vertex ring over 1500 poses. */
    aspect-ratio:1.38;
    height:auto;
    margin-inline:auto;
  }

  .qs-content{
    position:relative;
    z-index:0;
    /* Clears the fade. The fade is --s-4xl tall and sits below the stone, so
       the first line has to start past it or it arrives half dissolved at rest,
       which reads as a rendering fault rather than as depth. --s-section is the
       next step up and moves with the same breakpoints. */
    margin-top:var(--s-section);
  }

  /* one column of cards, plainly stacked. No glass and no pass: the stone is
     pinned above the copy here, not behind it, so there is nothing for a card
     to cross and nothing for it to need a ground against. */
  .band--stage .pillars{grid-template-columns:1fr;gap:var(--s-2xl)}
}


/* ==========================================================================
   THE PRELOADER

   Covers the page from the first painted frame, holds the mark and the
   wordmark on the brand ground, and leaves by a soft circle opening out of
   the middle. It carried a dot grid until 2026-09-09; see DESIGN.md.

   The monogram is the real one, drawn as a mask so it can take the brand gold;
   the wordmark is the logo's own artwork rather than type set to look like it.
   Neither is redrawn here.
   ========================================================================== */
.is-loading{overflow:hidden}

/* Transparent on purpose. The canvas paints the ground and the hole the wipe
   opens in it, both in one surface, so the reveal is a single composited arc
   rather than an animated mask over a painted background. */
.pre{
  position:fixed;inset:0;z-index:200;
  background:transparent;
  display:grid;place-items:center;
}

.pre-ground{position:absolute;inset:0;width:100%;height:100%;display:block}

.pre-body{
  position:relative;z-index:1;
  display:flex;flex-direction:column;align-items:center;
  gap:var(--s-lg);
}

/* The same traced mark the page already uses as an accent, referenced by
   <use> rather than loaded again: one definition, one silhouette, and it takes
   currentColor so it can be gold here and ink elsewhere. Masking a standalone
   file was tried first and cropped, because that file carries its artwork off
   the centre of its own viewBox. */
.pre-mark{
  display:block;width:104px;height:104px;
  color:var(--beige);fill:currentColor;
}

.pre-word{display:block;width:210px;height:auto}

.pre-bar{
  display:block;width:180px;height:1px;
  background:var(--outline-light);
  overflow:hidden;
}
.pre-bar i{
  display:block;width:100%;height:100%;
  background:var(--accent-soft);
  transform:scaleX(0);transform-origin:left center;
}

/* the body goes before the wipe starts, so the circle opens onto the site
   rather than onto a logo sliding out of the way */
.pre.is-out .pre-body{opacity:0;transform:scale(.96)}

@media (prefers-reduced-motion:no-preference){
  .pre.is-out .pre-body{
    transition:opacity 340ms var(--ease),transform 520ms var(--ease);
  }
}

/* the reduced motion exit: no grid, no ripple, no circle */
.pre.is-out-still{opacity:0;transition:opacity 300ms linear}

@media (max-width:600px){
  .pre-mark{width:80px;height:80px}
  .pre-word{width:168px}
  .pre-bar{width:148px}
}


/* ==========================================================================
   THE DOT, AND THE HEADINGS IT LEADS

   One mark, used twice: on hover for anything clickable, and permanently in
   front of every section title. Both read it from --dot, which each ground
   declares for itself, so the mark is a property of the surface rather than of
   whatever colour the text under it happens to be. That is the whole reason it
   is a token and not currentColor: on the mega panel the link's own colour is
   60% white, and a 6px dot at that value is not an affordance, it is a smudge.
   ========================================================================== */
:root{
  --dot:var(--accent);
  /* The pip's two colours, read per ground exactly as --dot is. On paper the
     fill is the deep gold, because bright gold on near-white is not a mark;
     on ink it is the bright one. The track is always the fill held down far
     enough to be a silhouette rather than a second state. */
  --pip-fill:var(--accent);
  --pip-track-on:rgba(139,50,67,.26);
  /* The pip's geometry. It lived on .dp-dots while the craft rail was the only
     thing using it; the hero's indicator is the same primitive and sits
     nowhere near that element, so a token scoped to one of its two users is a
     token one of them cannot read. */
  --pip-r:7px;
  --pip-pill:22px;
  /**
   * And the two dwell times, up here for the same reason.
   *
   * --roll-ms lived on .roll and --dp-ms on .dp-dots, which was safe only
   * while each indicator sat inside the element that declared its own. The
   * hero's moved onto the picture and its fill went dead on the spot: an
   * `animation` shorthand whose duration is an unresolvable var is invalid at
   * computed-value time, so the whole declaration was dropped and the pill sat
   * permanently full instead of counting. A dwell is a property of the
   * carousel, not of the box the tabs happen to live in.
   *
   * Both scripts read them back off their own elements, which inherit from
   * here, so this stays one declaration per number.
   */
  --roll-ms:4800ms;
  --dp-ms:3600ms;
  --dot-size:6px;
  /* how far the text moves, and therefore how far left of it the dot sits.
     Every instance derives from this; nothing hard codes an offset. */
  --dot-shift:14px;
}
/* THE TITLE COLOUR IS CONTEXTUAL, FOR THE SAME REASON THE DOT IS.
   Wine is the title colour on a LIGHT ground, and Beige does that job on a
   dark one. That was written down and then only half implemented: the dot got
   this list and the title got an ad hoc `.is-dark h3` override instead, so
   every dark context that is not a section missed out. The tiles are where it
   showed. `.tile-image .tile-body` and `.tile-invert` both set their own
   context colour to Beige correctly, and the global `h3{color:var(--title)}`
   stomped it straight back to Wine: measured, "See the full range" on --ink
   was 2.05:1, and the two headings over photography were Wine on a jewellery
   photograph with the scrim at its weakest, which is where a heading sits.
   One token on the list that already exists, rather than a fifth override. */
.is-dark,.nav,.foot,.mega,.tile-invert,.tile-image .tile-body{
  --dot:var(--accent-soft);
  --title:var(--accent-soft);
}
.is-dark,.nav,.foot,.mega{--pip-fill:var(--accent-soft);--pip-track-on:rgba(255,255,255,.24)}
/* On the picture the track is Ivory rather than tinted, and the fill is Garnet
   rather than Blush. A tinted track under a same-family fill is a silhouette
   only where the ground is dark and even; over photography it disappears into
   whatever is behind it, and the pill has to be a pill on every frame of three
   rotating images. Garnet is the dark member of the pair, so it reads against
   the Ivory track whatever the photograph is doing. */
.hero-aperture{--pip-fill:var(--accent);--pip-track-on:rgba(255,255,255,.42)}
.btn-primary,.btn-onink{--dot:var(--on-gold)}
.btn-ghost{--dot:var(--on-beige)}

/**
 * In the flow, immediately before the first letter.
 *
 * It was absolutely positioned and measured against the first word, so that
 * the type would never move for it. On a phone that has nowhere to go: the
 * text starts at the page gutter, so the only space outside the text is
 * outside the section, and the dot ended up clamped against the viewport edge
 * detached from its own heading. In flow it is inside the text container by
 * construction and inside the section's margins for the same reason
 * everything else is.
 *
 * Sized in em, so it tracks the heading it leads across every step of the
 * type scale with no second set of numbers and no measuring pass. And
 * vertical-align:middle is defined as the baseline plus half the x-height,
 * which is exactly the optical centre a dot beside a line of type wants;
 * the box's own middle would sit low, because a display serif's line box is
 * much taller than its letters.
 */
.tw-dot{
  display:inline-block;
  width:.19em;height:.19em;
  /* nothing separates it from the first letter in the markup, so this gap is
     the only thing between them and there is no break opportunity to find */
  margin-right:.34em;
  vertical-align:middle;
  border-radius:var(--radius-round);
  background:var(--dot);
  pointer-events:none;
}

@media (prefers-reduced-motion:no-preference){
  /* pulsing while the line is still arriving, solid once it has landed */
  .d-xl.is-typing .tw-dot,
  .d-lg.is-typing .tw-dot{animation:dotPulse 820ms var(--ease-soft) infinite}
}

@keyframes dotPulse{
  0%,100%{opacity:1;transform:scale(1)}
  50%{opacity:.34;transform:scale(.68)}
}

/* The letters. Opacity only, and every one of them holds its space from the
   first frame, so the line never reflows and nothing on the page shifts. */
@media (prefers-reduced-motion:no-preference){
  .js-tw .d-xl .tw,
  .js-tw .d-lg .tw{opacity:0}

  .js-tw .d-xl.is-typed .tw,
  .js-tw .d-lg.is-typed .tw{
    opacity:1;
    /* ease-out, not linear. Linear holds a letter at half strength through
       the middle of its fade, so a long run of them hangs there looking
       unresolved; an ease-out puts most of the opacity in the first third, so
       the leading edge of the sweep is crisp and the tail is what is soft. */
    transition:opacity var(--tw-run) var(--ease-soft)
               calc(min(var(--i), var(--tw-cap)) * var(--tw-step));
  }
}

/* the heading's own reveal is switched off where the words do the arriving,
   or the two fades compound and the line comes in twice */
.js-reveal .reveal.d-lg,
.js-reveal .reveal.d-lg.is-in{opacity:1;transform:none}


/* ==========================================================================
   THE CROWN RISES OUT OF THE FOOTER

   It starts translated down by its own height, which puts it entirely below
   the footer line, where it is ink on ink and therefore invisible. Rising, it
   crosses that line and appears against the paper. So nothing has to be masked
   or clipped: the footer's own fill is the occluder, and the shape genuinely
   reads as coming up out of it.

   The facts inside it are the one thing that would give the trick away, being
   a different colour from the ground they are sitting on while they are still
   below the line. They are held back and faded in once the shape has arrived.
   ========================================================================== */
@media (prefers-reduced-motion:no-preference){
  .js-reveal .reveal.foot-crown{opacity:1;transform:translateY(100%)}
  .js-reveal .reveal.foot-crown.is-in{
    opacity:1;transform:none;
    transition:transform 1000ms var(--ease-soft);
  }
  .js-reveal .reveal.foot-crown .foot-crown-body{opacity:0}
  .js-reveal .reveal.foot-crown.is-in .foot-crown-body{
    opacity:1;
    transition:opacity 620ms var(--ease) 340ms;
  }
}


/* the collection tiles take the same mark, on the tile's own hover rather than
   the title's, because the whole card is the target. Everything else about it
   is the shared definition above. */
.tile-body h3{position:relative}


/* ==========================================================================
   THE STAGE CAPTION, INSIDE ITS OWN PHOTOGRAPH

   The stage used to be named above the rail so the receding cards could stay
   clean photographs. With two neighbours instead of three the focused card is
   large enough to hold its own name, which is better: the words and the
   picture they describe are one object, and the eye stops travelling between
   them.

   The blur behind it is progressive, not a single pane. One backdrop-filter
   with one radius has an edge, and an edge across a photograph is a bar laid
   over it; four layers of increasing radius, each masked to start lower than
   the last, have no edge anywhere. That is the whole trick, and it is why
   there are four of them.

   backdrop-filter reads the nearest backdrop root, and every card already
   carries a filter for its grayscale recede, so each card IS its own root.
   The blur therefore sees the card's own photograph and nothing behind it,
   which is what keeps the page from showing through the caption.
   ========================================================================== */
.dp-cap{
  position:absolute;inset:auto 0 0 0;
  /* Lifted off the bottom edge. Sitting on it, the description ran out of the
     card and was cut by its overflow, and the words were below the gradient
     rather than inside it. The fade exists to carry the text, so the text has
     to sit in it. */
  padding:var(--s-2xl) var(--s-xl) var(--s-2xl);
  color:var(--on-ink);
  text-align:center;
  opacity:0;
  pointer-events:none;
}

@media (prefers-reduced-motion:no-preference){
  .dp-cap{transition:opacity 420ms var(--ease)}
}

.dp-c.is-front .dp-cap{opacity:1}

/**
 * The fade, sized against the card and aimed at the words.
 *
 * Three things were wrong with it and they compounded.
 *
 * z-index:-1 put it behind the card's own background, so the photograph
 * painted over it and nothing showed at all. It needs no z-index: it is a
 * child of the card, before the caption in source order, so it already paints
 * over the image and under the words.
 *
 * It was inside the caption, so it inherited that box, and the caption's 48 of
 * bottom padding pushed the whole gradient below the text. Measured: the words
 * occupy 48 to 149 above the card's bottom edge, and the scrim reached 0.29
 * alpha at the lowest of them and zero across the title. It is a child of the
 * card now and covers the bottom 62% of it, so the stops can be placed where
 * the words are rather than under them.
 *
 * And it was too weak regardless. These photographs are not dark: several are
 * pale concrete and bright metal, and a scrim tuned against a dark image
 * disappears on those. The stops below hold at least 0.78 across the whole
 * text zone, which keeps the description above 4.5:1 even against a white
 * frame, and fade out well above it so there is no edge to see.
 *
 * One backdrop-filter, not three, and only on the focused card. Three stacked
 * blurs of a moving, transformed 780px card, on all twelve cards, was the cost
 * this section was paying; a transparent caption still composites its
 * backdrop. The mask is what makes one blur read as progressive.
 */
.dp-blur{
  position:absolute;inset:auto 0 0 0;height:62%;
  overflow:hidden;border-radius:inherit;
  pointer-events:none;
}
.dp-blur i{position:absolute;inset:0;display:block}

.dp-c.is-front .dp-blur i:nth-child(1){
  -webkit-backdrop-filter:blur(10px);backdrop-filter:blur(10px);
  -webkit-mask:linear-gradient(to top,#000 0,#000 46%,transparent 78%);
          mask:linear-gradient(to top,#000 0,#000 46%,transparent 78%);
}

/* the scrim, because a blur moves the background around without darkening it
   and contrast is not a blur's job */
.dp-blur i:nth-child(2){
  background:linear-gradient(to top,
    rgba(0,0,0,.95) 0,
    rgba(0,0,0,.90) 20%,
    rgba(0,0,0,.78) 50%,
    rgba(0,0,0,.34) 72%,
    rgba(0,0,0,0) 100%);
}

.dp-cap .n{
  display:block;font-family:"Cormorant Garamond",Georgia,serif;
  font-size:15px;letter-spacing:.16em;color:var(--accent-soft);
  font-variant-numeric:lining-nums tabular-nums;
}
.dp-cap h3{
  font-family:"Cormorant Garamond",Georgia,serif;font-weight:400;
  font-size:clamp(24px,2.6vw,34px);line-height:1.15;
  margin-top:var(--s-xs);color:var(--on-ink);
}
.dp-cap p{
  color:rgba(255,255,255,.82);font-size:15px;line-height:1.6;
  margin-top:var(--s-sm);max-width:46ch;margin-inline:auto;
}

@media (max-width:900px){
  .js-dp .dp-stage{height:420px;--dp-fw:520px;--dp-fh:380px;--dp-sw:280px;--dp-sh:380px}
  .dp-cap{padding:var(--s-xl) var(--s-lg) var(--s-xl)}
}
@media (max-width:560px){
  .js-dp .dp-stage{height:376px;--dp-fw:290px;--dp-fh:340px;--dp-sw:180px;--dp-sh:280px}
  .dp-cap p{display:none}
}

/* The heading block sits closer to the rail than it does to a card grid: the
   rail already reads as one object, so the title does not need a full section
   step to separate from it. */
/* Tight to the rail below it, which was asked for, but not tight to the
   section above: the quality band's own closing padding and this heading were
   meeting with nothing between them, so the two sections read as one. From the
   1920 handoff. */
#craft .center{padding-top:var(--s-4xl);margin-bottom:var(--s-2xl)}
