/* Cascabel — UI tweaks
 *
 * Loads after each page's inline <style>, so these win on ties. Everything here
 * is a site-wide adjustment asked for by the client; anything that belongs to a
 * single section stays in that page.
 */

/* ── Buttons: a hint of a curve ───────────────────────────────────────────
 * The design shipped at 2px, which reads as a hard square. 6px softens the
 * corner without turning it into a pill — the frame stays filmic.
 */
.btn,
.btn.ghost,
.btn.ink,
.navcta,
.cta a,
button,
input[type="submit"] {
  border-radius: 6px;
}

/* Anything shaped like a button in a form keeps the same corner */
.field input,
.field button,
form input[type="email"],
form input[type="text"] {
  border-radius: 6px;
}

/* ── The plate ────────────────────────────────────────────────────────────
 * Almost everything on this site is a box on a hairline: the grids fake their
 * dividers with a 1px gap over a line-coloured background, and the cards sit
 * inside them. Squared off, a page of them reads as a spreadsheet. One curve,
 * used everywhere, turns them into plates — the shape of a film gate or the
 * badge on a truck door — and gives the page an edge it did not have.
 *
 * Three things worth knowing before touching this:
 *
 * 1. overflow:hidden is not decoration here. On the gap-grids the children
 *    fill the container, so without it they keep their square corners and
 *    poke straight through the curve.
 * 2. That clipping is safe on this list: nothing inside any of them is
 *    position:sticky, which an overflow ancestor would silently kill. The
 *    only sticky element on the site is the home intro panel, and it is not
 *    in a card. Check again before adding to this list.
 * 3. Only the outer plate curves. Cells inside a grid stay square, which is
 *    what keeps the hairlines reading as a ruled sheet instead of a bag of
 *    lozenges.
 */
.gl-grid, .rat-grid, .deal-grid, .crew-grid, .metric-grid, .inv-grid,
.rel-stats, .tt-media, .yr-wrap, .sheet, .calc-wrap, .plot-wrap, .intro-stats,
.loc-in, .scale-wrap, .studios-split, .room-grid, .div-grid, .b-grid,
.br-grid, .built-grid, .buy-grid, .el-wrap, .fit-wrap, .gf-grid,
.incl-grid, .nf-grid, .tours3, .bag-wrap, .pb-wrap, .spot-in, .perks,
.docs, .faq-list, .floors, .map-wrap, .map-list, .bracket-box, .bracket,
.cmp, .cmp-box, .truck, .tour-frame, .frame, .tile, .portrait, .slate,
.roll, .ticket, .receipt, .stampbox, .yr-rows, .scale-rows, .loc-rows,
.fit-rows, .mat-rows, .plot-rows, .sheet-data, .v-grid, .calc-out, .caps {
  border-radius: 18px;
  overflow: hidden;
}

/* The plates the first pass missed. Same rule, same reason: every one of these
   is an outer container with its own surface or its own full border, and left
   square it reads as a spreadsheet cell next to neighbours that curve. Found by
   measuring rather than by looking — `node _tools/_boxes.js` lists every box
   over 90x60 that has a surface and square corners. Cells *inside* a curved
   grid stay square on purpose; they are not in this list. */
.door-rail, .gb-visual, .serv, .proc-track, .onsite-facts, .cap-table,
.sheet-board, .t-grid, .day-strip, .tl, .sb-strip, .ls, .callsheet, .rt,
.store-card, .pcard, .fl-list, .st-ph {
  border-radius: 18px;
  overflow: hidden;
}

/* The small data strips take a tighter curve — an 18px radius on a 40px-tall
   row eats the row. */
.yr-rows, .scale-rows, .loc-rows, .fit-rows, .mat-rows, .plot-rows,
.sheet-data, .calc-out, .caps, .stampbox {
  border-radius: 12px;
}

/* The little readout chips and the material swatches take the tighter curve
   too — an 18px radius on a 90px box swallows the corner. */
.scale-info .c, .calc-side .co, .sw-grid .chip { border-radius: 10px }

/* A hairline of venom traces the curve when you are on a card. It rides a
   pseudo-element rather than the card itself, so it cannot stamp on the
   transitions these cards already run — .rat-card animates its background,
   the trucks their photograph. Only cards with a free ::after are listed;
   the inventory and the store tiles already use theirs, and they have their
   own hover anyway. */
/* .metric is deliberately NOT in this list. The stat cells are four numbers on
   one hairline-divided strip, and outlining the one under the pointer read as a
   little red box drawn around a number rather than as the strip responding. */
.rat-card, .deal, .cr, .truck { position: relative }
.rat-card::after, .deal::after, .cr::after, .truck::after {
  content: "";
  position: absolute;
  inset: 0;
  z-index: 3;
  border-radius: inherit;
  pointer-events: none;
  box-shadow: inset 0 0 0 1px rgba(194, 38, 29, 0);
  transition: box-shadow .45s cubic-bezier(.2, .7, .2, 1);
}
.rat-card:hover::after, .deal:hover::after,
.cr:hover::after, .truck:hover::after {
  box-shadow: inset 0 0 0 1px rgba(194, 38, 29, .55);
}

/* ── The light sections read as grey, not white ───────────────────────────
 * The pale sections were close enough to white to break the film look. They
 * move to a definite light grey. --bone stays untouched: it is the text colour
 * on every dark section, so changing it would repaint the whole site.
 */
:root {
  --light: #D2D2CC;
  --light-2: #DCDCD6;
  --light-card: #D9D9D3;
}

/* the few places --bone was used as a surface rather than as ink */
.venom-strip,
.store-marquee,
.ticker,
.receipt,
.btn.bone { background: var(--light-2) }
.btn.bone:hover { background: var(--light) }

/* ── .on-light — turn a dark section light ────────────────────────────────
 * The site is built on tokens, so redefining them inside one section flips
 * everything it contains: text, hairlines, panels, secondary greys. That is
 * why this is a class and not a pile of per-element overrides.
 */
.on-light {
  --black: #D2D2CC;
  --panel: #D9D9D3;
  --panel-2: #DCDCD6;
  --bone: #0A0A0A;
  /* the secondary grey is ink, not a hairline: 55% measured 4.0:1 on this
     surface, 68% measures 5.8:1 and still reads as the quieter voice */
  --grey: rgba(10, 10, 10, .68);
  --line: rgba(10, 10, 10, .14);
  /* Yellow is NOT remapped here. An earlier version darkened it to an ochre so
     it would clear contrast on this grey — it turned the brand accent gold,
     which is not a call a contrast check gets to make. Where yellow needs to
     read on a light section it gets a dark chip behind it instead (below). */
  background: var(--light) !important;
  color: #0A0A0A;
}

/* Copy written as light-on-dark rgba literals has to be told to flip. The page
   rules that set those literals are more specific than a bare descendant
   selector, hence the !important — scoped to this helper only. */
.on-light h1, .on-light h2, .on-light h3,
.on-light h4, .on-light h5, .on-light strong { color: #0A0A0A !important }
.on-light p, .on-light li, .on-light td, .on-light dd,
.on-light .lede, .on-light .lead,
.on-light button:not(.btn):not(.on):not(.active) { color: rgba(10, 10, 10, .66) !important }
.on-light .eyebrow { color: var(--venom) !important }
.on-light .sec-num, .on-light th, .on-light .lbl { color: rgba(10,10,10,.68) }
.on-light a:not(.btn) { color: inherit }
.on-light .btn.ghost { border-color: rgba(10, 10, 10, .22); color: #0A0A0A }
/* El boton de contorno amarillo lleva su tinta en --bone, y --bone es negro
   dentro de este helper... salvo cuando la pagina lo escribe literal. En la
   seccion "the reel", recien pasada a clara, quedo texto casi blanco sobre gris
   claro: 1.39:1. Sobre fondo claro el contorno se vuelve venom y la tinta
   negra, que es lo que ya hace el resto de los botones de esta familia. */
.on-light .btn.line-yellow {
  border-color: var(--venom);
  color: #0A0A0A;
}
.on-light .btn.line-yellow:hover { background: var(--venom); color: #F5F5F2 }

.on-light .btn.ghost:hover { border-color: var(--venom) }
/* A filled button keeps light type whatever the section does. .btn takes its
   colour from --bone, and --bone is remapped to black inside this helper — so
   flipping a section to light quietly turned every venom button into black
   text on red, 3.4:1. The button's own ground has not changed; its ink should
   not either. */
/* .line-yellow tambien queda fuera: es un boton de CONTORNO, no relleno, asi
   que sobre claro necesita tinta oscura como el resto de su familia. Cada
   :not() suma especificidad, y esta regla le ganaba a la suya. */
.on-light .btn:not(.ghost):not(.bone):not(.ink):not(.line-yellow) { color: #F5F5F2 }

.on-light em, .on-light .venom { color: var(--venom) }

/* Yellow keeps its own dark plate on a light section.
 * The step numbers and the process icons are painted in --yellow, which cannot
 * survive on pale grey — measured at 1.05:1. The wrong fix is to darken the
 * yellow, which turns the brand accent to gold. The right one is to give it
 * back the dark ground it was designed against: a black disc under the icon
 * and under the numbered chip. Yellow stays yellow, and reads. */
.on-light .dot { background: #0A0A0A }

/* Type that floats over a photograph does not flip with the section. These are
   pictures under a dark gradient, not surfaces — turning the section light
   turned their titles black on a black photograph, which is invisible. Their
   ink stays light whatever the section around them does. */
.on-light .div-card, .on-light .door, .on-light .gl-card,
.on-light .gf, .on-light .tile, .on-light .rm { color: #F5F5F2 }
.on-light .div-card h2, .on-light .div-card h3, .on-light .div-card h4,
.on-light .door h2, .on-light .door h3, .on-light .door h4,
.on-light .gl-card h3, .on-light .gl-card h4,
.on-light .gf b, .on-light .gf strong,
.on-light .tile b, .on-light .rm b { color: #F5F5F2 !important }
.on-light .div-card p, .on-light .door p, .on-light .gl-card p,
.on-light .gf p, .on-light .tile p {
  color: rgba(245, 245, 242, .72) !important;
}

/* ── The snake, drawn rather than stamped ─────────────────────────────────
 * The watermark is a real illustration — a coiled rattlesnake with yellow
 * lightning — and it was being used eleven different ways. Opacities ran from
 * 4% to 17% page to page, and on the pale sections a filter flattened it to a
 * solid black shape: a silhouette, not the artwork. It read as a blob.
 *
 * One treatment now, everywhere. On the black it keeps its own colours at a
 * strength where the coils and the lightning are legible as drawing. On the
 * pale sections it is deepened and cooled rather than crushed, so it still
 * reads as the same animal on a lighter ground.
 */
.snake-ghost {
  opacity: .13 !important;
  /* The yellow of the lightning is the one part that punches through at this
     strength and starts competing with copy laid over it. Pulled back, the
     drawing still reads and the words stay first. */
  filter: saturate(.7);
  mix-blend-mode: normal;
}
.on-light .snake-ghost {
  /* On a pale ground the artwork is a big mid-grey mass, and at any opacity that
     reads as a smudge rather than as a drawing — which is what the client kept
     seeing. Pushing contrast hard separates the linework from the fill, so what
     survives at low opacity are the COILS and the lightning, not the blob. */
  filter: grayscale(.75) contrast(2.1) brightness(.62);
  opacity: .09 !important;
}
/* Over the venom and gold bands the artwork has to sit back further, or it
   fights a saturated ground it can never win against. */
/* Mismo arreglo que en la tarjeta roja de Rattling: brightness(0) las dejaba
   como siluetas negras. Con contraste alto sobre el color, el trazo sobrevive. */
.venom-band .snake-ghost,
.urgent .snake-ghost,
.promise .snake-ghost,
.onsite .snake-ghost {
  opacity: .18 !important;
  filter: grayscale(1) contrast(1.7) brightness(1.45);
  mix-blend-mode: soft-light;
}

/* Give Back's storyboard panels paint their own near-black surface, hard-coded
   rather than from a token, so the section flip could not reach them. Anything
   sitting on that surface keeps light ink. */
.on-light .panel { color: #F5F5F2 }
.on-light .panel h1, .on-light .panel h2, .on-light .panel h3,
.on-light .panel h4, .on-light .panel strong { color: #F5F5F2 !important }
.on-light .panel p, .on-light .panel li { color: rgba(245, 245, 242, .66) !important }

/* Everywhere else on a pale section the icon takes venom instead of yellow. The
   client asked for this on the Woods shop grid and it is right across the board:
   yellow line-work on light grey is a smudge, and red is the other half of the
   brand rather than a compromise colour. The two sections below keep their
   yellow because they were given a black disc to stand on. */
.on-light .ico:not(.venom):not(.ink) { color: var(--venom) }
/* The two exceptions have to out-specify the rule above, and :not() counts as
   a class each — the first version of this lost by one and quietly turned the
   home pipeline red. */
.on-light.pipeline .ico:not(.venom):not(.ink),
.on-light.built .ico:not(.venom):not(.ink) { color: var(--yellow) }

/* Only where the icon is actually yellow. Give Back's board icons inherit the
   section's ink instead, and a black disc would bury them — the same
   dark-on-dark mistake, one layer down. */
.on-light.pipeline .ico::before,
.on-light.built .ico::before {
  background: #0A0A0A;
  border-color: rgba(245, 245, 242, .14);
}
.on-light.pipeline .hoverable:hover .ico::before,
.on-light.built .hoverable:hover .ico::before { border-color: currentColor }

/* ── The icon ring: full colour on the way in, fade on the way out ────────
 * The ring was set to `border-color: currentColor; opacity: .55` on hover, but
 * only border-color was in the transition. So opacity snapped to .55 the
 * instant you arrived — the colour crept in already dimmed — and snapped back
 * to 1 the instant you left, which flared the ring to full strength on the way
 * OUT and only then faded. That is the double effect: the brightest moment was
 * the goodbye.
 *
 * Now the ring goes straight to full colour, fast, and settles out slowly:
 * a strike in, a coil out, which is the brand's own timing.
 */
.ico::before {
  transition: border-color .5s cubic-bezier(.2, .7, .2, 1),
              opacity      .5s cubic-bezier(.2, .7, .2, 1);
}
.hoverable:hover .ico::before,
.hoverable:focus-visible .ico::before {
  opacity: 1;
  transition: border-color .16s cubic-bezier(.7, 0, .2, 1),
              opacity      .16s cubic-bezier(.7, 0, .2, 1);
}

/* ── Shot with Cascabel — same move, less lag ─────────────────────────────
 * Greyscale-to-colour ran over 1.1s, long enough to feel like the page was
 * thinking. The picture now answers in .45s and still settles back slowly, so
 * it reads as quick rather than twitchy. The border keeps up instead of
 * trailing the photograph.
 */
.tile {
  transition: transform .5s cubic-bezier(.2, .7, .2, 1),
              border-color .5s cubic-bezier(.2, .7, .2, 1);
}
.tile:hover {
  transition: transform .3s cubic-bezier(.7, 0, .2, 1),
              border-color .16s cubic-bezier(.7, 0, .2, 1);
}
.tile .art {
  transition: filter .8s cubic-bezier(.2, .7, .2, 1),
              transform .8s cubic-bezier(.2, .7, .2, 1);
}
.tile:hover .art {
  transition: filter .45s cubic-bezier(.7, 0, .2, 1),
              transform .55s cubic-bezier(.2, .7, .2, 1);
}
/* ── Home ─────────────────────────────────────────────────────────────── */

/* The pipeline icon plates get a soft corner instead of a hard square. */
.ico.square::before { border-radius: 12px }

/* The wordmark is already black artwork on its own yellow field, sitting on a
   yellow band. brightness(0) flattened the field to black too, which is why
   the top of MARTINI turned into a black slab. No filter: the file is right. */
.store-logo img { filter:none}

/* The manifesto quote highlights words in yellow, which cannot survive on the
   pale ground — it takes the venom red the rest of the light sections use. */
.on-light.manifesto p em { color: var(--venom) }

/* Grip & Lighting on the home page. Built on its own class names so it cannot
 * collide with the page's existing components, and shaped like the door rail
 * so the two read as one family: hover takes room from the neighbours and the
 * copy fades up.
 */
.griplight { background: var(--black) }
.gl-grid {
  display: grid;
  grid-template-columns: repeat(4, 1fr);
  gap: 2px;
  background: var(--line);
  border: 1px solid var(--line);
}
.gl-card {
  position: relative;
  min-height: 380px;
  overflow: hidden;
  display: flex;
  flex-direction: column;
  justify-content: flex-end;
  padding: clamp(24px, 2.8vw, 38px);
  background: var(--panel);
  text-decoration: none;
  color: inherit;
}
.gl-bg {
  position: absolute; inset: 0; z-index: 0;
  background-size: cover; background-position: center;
  filter:grayscale(0.18) brightness(0.85);
  transform: scale(1.04);
  transition: transform 1.2s cubic-bezier(.2, .7, .2, 1),
              filter 1.2s cubic-bezier(.2, .7, .2, 1);
}
.gl-card:hover .gl-bg { transform: scale(1.1); filter:grayscale(0) brightness(1)}
.gl-card::after {
  content: "";
  position: absolute; inset: 0; z-index: 1;
  background: linear-gradient(180deg, rgba(10,10,10,.2) 0%, transparent 32%, rgba(10,10,10,.94) 100%);
}
.gl-in { position: relative; z-index: 2 }
.gl-n {
  position: absolute;
  top: calc(-1 * clamp(24px, 2.8vw, 38px) - 0px);
  display: block;
  font-size: .66rem; letter-spacing: .2em;
  color: var(--yellow); font-weight: 900;
}
.gl-card h3 { font-size: clamp(1.25rem, 2.1vw, 1.8rem); margin-bottom: 10px }
.gl-card p { color: rgba(245,245,242,.7); font-size: .88rem; line-height: 1.55; max-width: 32ch }
.gl-card .go {
  margin-top: 18px;
  font-size: .62rem; letter-spacing: .24em; text-transform: uppercase;
  font-weight: 900;
  display: inline-flex; align-items: center; gap: 10px;
  color: var(--yellow);
}
.gl-foot {
  margin-top: clamp(22px, 3vw, 34px);
  display: flex; align-items: center; justify-content: space-between;
  gap: 24px; flex-wrap: wrap;
}
.gl-foot p { color: var(--grey); font-size: .84rem; max-width: 56ch }

@media (min-width: 901px) {
  .gl-grid { display: flex; gap: 2px }
  .gl-card {
    flex: 1 1 0; min-width: 0;
    transition: flex-grow .85s cubic-bezier(.2, .7, .2, 1);
  }
  .gl-card:hover, .gl-card:focus-visible { flex-grow: 2.4 }
  .gl-card p, .gl-card .go {
    opacity: 0; transform: translateY(12px);
    transition: opacity .5s cubic-bezier(.16, 1, .3, 1) .06s,
                transform .5s cubic-bezier(.16, 1, .3, 1) .06s;
  }
  .gl-card:hover p, .gl-card:focus-visible p,
  .gl-card:hover .go, .gl-card:focus-visible .go { opacity: 1; transform: none }
}
@media (max-width: 900px) {
  .gl-grid { grid-template-columns: 1fr }
  .gl-card { min-height: 300px }
}

/* ── Grip & Lighting ──────────────────────────────────────────────────── */

/* Each inventory box carries its own photograph, revealed on hover. The four
 * plates that used to sit under the list are gone: they showed four pictures
 * for eight categories and said nothing about which was which.
 */
.inv-item { position: relative; overflow: hidden; isolation: isolate }
.inv-item > *:not(.iv-bg) { position: relative; z-index: 2 }
.iv-bg {
  position: absolute; inset: 0; z-index: 0;
  background-size: cover; background-position: center;
  opacity: 0;
  transform: scale(1.04);
  transition: opacity .55s cubic-bezier(.2, .7, .2, 1),
              transform 1.4s cubic-bezier(.2, .7, .2, 1);
}
/* a scrim, so white type holds on any photograph */
.inv-item::after {
  content: "";
  position: absolute; inset: 0; z-index: 1;
  background: linear-gradient(180deg, rgba(10,10,10,.42), rgba(10,10,10,.74));
  opacity: 0;
  transition: opacity .55s cubic-bezier(.2, .7, .2, 1);
  pointer-events: none;
}
.inv-item:hover .iv-bg { opacity: 1; transform: scale(1.1) }
.inv-item:hover::after { opacity: 1 }
.inv-item:hover .n,
.inv-item:hover h4 { color: #F5F5F2 }
.inv-item:hover p { color: rgba(245, 245, 242, .78) }
.inv-item:hover .ico { color: #F5F5F2 }
.inv-item:hover .ico::before { border-color: rgba(245, 245, 242, .3) }

/* Each truck is its own plate now, so the gap between them has to read as a gap
   and not as a seam — 10px looked like a mistake in the hairline. */
.truck { margin-bottom: clamp(20px, 3vw, 40px) }
.truck:last-of-type { margin-bottom: 0 }

/* "Tick what you need" turned into a light section, but its result panel is
   meant to stay black — it is the readout, and the contrast against the picker
   is the point. --black is remapped inside .on-light, so it is spelled out. */
.on-light .pb-out { background: #0A0A0A; color: #F5F5F2 }
.on-light .pb-out h1, .on-light .pb-out h2, .on-light .pb-out h3,
.on-light .pb-out h4, .on-light .pb-out strong { color: #F5F5F2 !important }
.on-light .pb-out p, .on-light .pb-out li { color: rgba(245, 245, 242, .72) !important }
.on-light .pb-out .kick { color: var(--yellow) }
/* and the ghost button inside it goes back to a light outline, or it is a black
   border and black label on a black panel */
.on-light .pb-out .btn.ghost {
  border-color: rgba(245, 245, 242, .28);
  color: #F5F5F2;
}
.on-light .pb-out .btn.ghost:hover { border-color: var(--venom) }

/* ── The three trucks, drawn properly ─────────────────────────────────────
 * A technical side elevation, not a diagram of one: chassis under the body,
 * corrugation on the box, the roll-up door and its slats, wheel arches cut
 * into the shell, tyres with rims and hubs. All three at one scale on one
 * ground line, because comparing them by eye is what the drawing is for.
 */
.trucksvg { color: var(--bone); overflow: visible }

.trucksvg .ground { stroke: url(#tk-floor); stroke-width: 2; fill: none }

.trucksvg .shell {
  fill: rgba(245, 245, 242, .045);
  stroke: var(--bone);
  stroke-width: 2.2;
  stroke-linejoin: round;
  transition: fill .5s cubic-bezier(.2, .7, .2, 1), stroke .4s;
}
.trucksvg .glass { fill: rgba(194, 38, 29, .16); stroke: var(--bone); stroke-width: 1.6 }
.trucksvg .chassis { stroke: rgba(245, 245, 242, .5); stroke-width: 4; stroke-linecap: round; fill: none }
.trucksvg .seam,
.trucksvg .door,
.trucksvg .rolldoor,
.trucksvg .gate { stroke: rgba(245, 245, 242, .42); stroke-width: 1.5; fill: none }
.trucksvg .gate { stroke: var(--yellow); stroke-width: 3; stroke-linecap: round }
.trucksvg .rib { stroke: rgba(245, 245, 242, .16); stroke-width: 1.1; fill: none }
.trucksvg .slat { stroke: rgba(245, 245, 242, .26); stroke-width: 1.1; fill: none }
.trucksvg .arch { stroke: rgba(245, 245, 242, .34); stroke-width: 1.6; fill: none }
.trucksvg .handle,
.trucksvg .mirror { stroke: rgba(245, 245, 242, .55); stroke-width: 2; stroke-linecap: round; fill: none }

.trucksvg .tyre { fill: #0E0E0E; stroke: var(--bone); stroke-width: 2 }
.trucksvg .tyre.back { fill: #0B0B0B; stroke: rgba(245, 245, 242, .34); stroke-width: 1.4 }
.trucksvg .rim { fill: none; stroke: rgba(245, 245, 242, .38); stroke-width: 1.4 }
.trucksvg .hub { fill: var(--bone); stroke: none }

/* Labels live in a clear band above the roofline. Nothing crosses a line. */
.trucksvg .lbl {
  fill: var(--bone);
  font: 900 21px var(--sans);
  font-variation-settings: 'wdth' 118;
  letter-spacing: -.01em;
}
.trucksvg .t-y {
  fill: var(--yellow);
  font: 900 10px var(--sans);
  font-variation-settings: 'wdth' 125;
  letter-spacing: .22em;
}
.trucksvg .t {
  fill: rgba(245, 245, 242, .38);
  font: 900 10px var(--sans);
  font-variation-settings: 'wdth' 125;
  letter-spacing: .26em;
}

/* the rig under the pointer comes forward, the others stand down */
.trucksvg .rig { cursor: pointer; transition: opacity .45s cubic-bezier(.2, .7, .2, 1) }
.trucksvg:hover .rig { opacity: .34 }
.trucksvg .rig:hover,
.trucksvg .rig:focus-visible,
.trucksvg .rig.on { opacity: 1 }
.trucksvg .rig:hover .shell,
.trucksvg .rig.on .shell { fill: rgba(194, 38, 29, .14); stroke: var(--venom) }
.trucksvg .rig:hover .hub,
.trucksvg .rig.on .hub { fill: var(--yellow) }
.trucksvg .rig:focus-visible { outline: none }

/* ── Woods: the drawing-to-build slider ───────────────────────────────────
 * The design faked the drawing: it took the finished photograph and ran
 * invert(1) over it, with a yellow tint and a plotter grid on top, so one
 * picture could pass for two. Now that a real sketch sits in the before layer,
 * inverting it turns a white drawing into a black negative — which is the
 * error. The fake blueprint treatment comes off and the drawing is shown as
 * drawn.
 */
.cmp-before img { filter: none }
.cmp-before .tint,
.cmp-before .plot { display: none }

/* The closing band washed its photograph in venom red — a radial at .22 over
 * the whole frame, which on Woods' warm night shot turned the plywood pink.
 * Down to .09, enough to tie the band to the brand without recolouring the
 * wood, and the surrounding black lifted a little so the picture reads. */
.cta-final::after {
  background: radial-gradient(circle at 50% 50%,
              rgba(194, 38, 29, .09), rgba(6, 6, 6, .82));
}

/* ── Woods hero: 30% less treatment ───────────────────────────────────────
 * The photograph sat at 42% opacity under three stacked gradients. Opacity up
 * to .60 — 30% of the way to full — the greyscale off, and every layer of the
 * scrim pulled back by the same 30%, so the reduction is even rather than one
 * thing changing while the rest holds.
 */
.wd-hero .photo { opacity: .82; filter: contrast(1.04) brightness(1.06) }
/* The client asked for it lighter twice. The photograph now carries the hero
   and the scrim only does what it has to: hold the headline on the left and
   land the section into the black at the bottom. */
.wd-hero .scrim {
  background:
    radial-gradient(64% 56% at 22% 42%, rgba(194, 38, 29, .10), transparent 64%),
    linear-gradient(100deg, rgba(10,10,10,.8) 0%, rgba(10,10,10,.42) 34%,
                    transparent 58%),
    linear-gradient(180deg, rgba(10,10,10,.34), transparent 26%,
                    transparent 74%, rgba(10,10,10,.72) 100%);
}

/* ── Store, "every shirt on a call sheet": 80% of the dimming removed ─────
 * It was at brightness .48, so it was throwing away 52% of the light. Keeping
 * a fifth of that leaves .90. The scrim behind the headline is eased in step,
 * and the type is white on a crowd shot that is dark to begin with.
 */
/* Segunda pasada, a pedido del cliente: la foto sale casi entera y el velo solo
   hace falta para que el titular encima siga leyendose. */
.worn .bg { filter: brightness(1) saturate(1.04) }
.worn .scrim {
  background: radial-gradient(circle at 50% 45%,
              rgba(10, 10, 10, .04), rgba(10, 10, 10, .56));
}

/* ── Studios ──────────────────────────────────────────────────────────── */

/* "Every room, photographed" goes back to a contact sheet: black and white at
 * rest, full colour only under the pointer. The site-wide pass that lifted
 * every greyscale to at most .18 had flattened this one along with the others,
 * and here the greyscale is the point. */
.rm .im > span,
.rm .im::before { filter: grayscale(1) brightness(.62) !important }
.rm:hover .im > span,
.rm:focus-visible .im > span,
.rm:hover .im::before,
.rm:focus-visible .im::before { filter: grayscale(0) brightness(1) !important }

/* Index strips: every destination on one line. Two pages carry one under a
   different name, so both are sized together. */
.jump .wrap,
.ruler .wrap {
  gap: clamp(5px, 0.8vw, 12px);
  padding-left: 20px;
  padding-right: 20px;
  flex-wrap: nowrap;
  overflow-x: auto;              /* last resort on the narrowest phones */
  scrollbar-width: none;
}
.jump .wrap::-webkit-scrollbar,
.ruler .wrap::-webkit-scrollbar { display: none }

/* The type scales with the viewport so the row keeps fitting as the window
   narrows, instead of holding one size until it wraps. */
.jump a,
.ruler a {
  font-size: clamp(.385rem, .52vw, .575rem);
  letter-spacing: .08em;
  gap: 5px;
  white-space: nowrap;
}
.jump .lbl,
.ruler .lbl {
  white-space: nowrap;
  margin-right: 0;
  font-size: clamp(.42rem, .5vw, .58rem);
  letter-spacing: .18em;
}

/* The stats box on the light intro: pale surface, dark hairlines, and the
   figures in venom rather than yellow, which would vanish here. */
.on-light .intro-stats { background: var(--light-2); border-color: rgba(10, 10, 10, .28) }
.on-light .intro-stats .s { border-right-color: rgba(10, 10, 10, .28) }
.on-light .intro-stats .s b { color: var(--venom) }

/* "From call to camera" gets the home page's pipeline treatment: the four
 * steps sit on a single venom-to-yellow track instead of being four loose
 * boxes joined by arrows. Values mirror .rail on the home page so the two
 * sections read as the same device.
 */
.booking .pipe-track {
  position: relative;
  padding-top: 26px;
  display: grid;
  grid-template-columns: repeat(4, 1fr);
  gap: clamp(10px, 1.6vw, 22px);
  /* the section shipped as a hairline grid: a line-coloured background showing
     through 2px gaps, inside a border. Left in place under the rail it became
     grey bars between the steps and a slab across the top. */
  background: none;
  border: 0;
}
.booking .pipe-step {
  background: none;
  padding: 0;
  min-height: 0;
}
.booking .pipe-step:hover { background: none }
.booking .pipe-track::before {
  content: "";
  position: absolute;
  top: 60px; left: 6%; right: 6%;
  height: 2px;
  background: linear-gradient(90deg, transparent, var(--venom) 12%, var(--yellow) 88%, transparent);
  opacity: .55;
}
.booking .pipe-step {
  position: relative;
  z-index: 1;
  text-align: center;
  display: flex;
  flex-direction: column;
  align-items: center;
  /* the step carried its own top spacing, which pushed the numbers 39px below
     the track; zeroed so the geometry matches the home rail exactly */
  padding-top: 0;
  margin-top: 0;
}
.booking .pipe-step .n {
  width: 34px; height: 34px;
  border-radius: 50%;
  background: var(--black);
  border: 2px solid var(--venom);
  display: grid; place-items: center;
  font-size: .56rem; font-weight: 900;
  font-variation-settings: 'wdth' 125;
  color: var(--yellow);
  margin-bottom: 22px;
  transition: background .4s, border-color .4s,
              transform .5s cubic-bezier(.7, 0, .2, 1);
}
.booking .pipe-step:hover .n {
  background: var(--yellow);
  border-color: var(--yellow);
  color: #0A0A0A;
  transform: scale(1.14);
}
.booking .pipe-step .ico { margin-bottom: 18px }
.booking .pipe-step p { max-width: 24ch }
/* the track replaces them */
.booking .pipe-step .arrow { display: none }

@media (max-width: 900px) {
  .booking .pipe-track { grid-template-columns: 1fr; gap: 26px; padding-left: 52px }
  .booking .pipe-track::before {
    top: 0; bottom: 0; left: 16px; right: auto;
    width: 2px; height: auto;
    background: linear-gradient(180deg, transparent, var(--venom) 10%, var(--yellow) 90%, transparent);
  }
  .booking .pipe-step { text-align: left; align-items: flex-start }
}

/* "Every room, photographed": the grid reads as a contact sheet — dark and
   desaturated — and only the one under the pointer comes up in full colour. */
/* The photograph is an inline-styled <span> inside .im, not the ::before I
   first aimed at — which is why the grid stayed in colour. */
.rm .im > span,
.rm .im::before {
  filter:grayscale(0.18) brightness(0.85);
  transform: scale(1.02);
  transition: filter .8s cubic-bezier(.2, .7, .2, 1),
              transform 1s cubic-bezier(.2, .7, .2, 1);
}
.rm:hover .im > span,
.rm:focus-visible .im > span,
.rm:hover .im::before,
.rm:focus-visible .im::before {
  filter:grayscale(0) brightness(1);
  transform: scale(1.07);
}

/* ── Type over drawings ───────────────────────────────────────────────────
 * Labels in the infographics were crossing the lines they label. Rather than
 * move every one by hand, each glyph is painted with a halo of the surface
 * colour behind it: the line is interrupted exactly where the text sits and
 * nowhere else. It is what a draughtsman does by breaking the leader line.
 */
.trucksvg text,
.teesvg text {
  paint-order: stroke fill;
  stroke: var(--black);
  stroke-width: 5px;
  stroke-linejoin: round;
}
/* These sheets are drawn on --light-2, not --light. The halo was a shade
   darker than the paper it was meant to disappear into, so every label wore a
   faint grey outline instead of simply interrupting the lines behind it.
   Labels that sit on a block rather than on the paper override this with
   their own surface colour — see .siteplan .lot in studios.html. */
.siteplan text,
.calcsvg text,
.anatsvg text {
  paint-order: stroke fill;
  stroke: var(--light-2);
  stroke-width: 5px;
  stroke-linejoin: round;
}
/* the chips the tee used to need are redundant now */
.teesvg #mdims rect { display: none }

/* ── About: the EXPERIENCE card was being cut off ─────────────────────────
 * `repeat(3, 1fr)` means `minmax(auto, 1fr)`, so a column whose content cannot
 * shrink takes more than its third. EXPERIENCE, set outlined and extra-wide,
 * measured 437px against a 377px column: it pushed the grid 1px past the
 * viewport and its own card off the right edge. Measured, not guessed.
 *
 * minmax(0, 1fr) makes the three columns genuinely equal, and the word is
 * sized so the longest of the three fits inside one.
 */
.v-grid { grid-template-columns: repeat(3, minmax(0, 1fr)) }
.v .word { font-size: clamp(1.35rem, 2.15vw, 2.05rem) }

/* ── The door rail, everywhere ────────────────────────────────────────────
 * The home page's "One family. Four doors." is a rail: hovering a panel makes
 * it take the room from its neighbours and its copy fades up. Every other page
 * ends with the same idea — the three or four cross-links to the rest of the
 * house — but laid out as a plain static grid.
 *
 * Turning that grid into the same rail is what the client asked for, and it
 * also fixes a real problem: the paragraph inside a narrow card had a 34ch
 * max-width it could never use. Holding the copy back until the panel opens
 * gives it the width it was written for.
 *
 * Desktop only. Below 900px the pages already stack these cards one per row,
 * where there is nothing to expand into.
 */
@media (min-width: 901px) {
  .div-grid { display: flex; gap: 2px }

  .div-card {
    flex: 1 1 0;
    min-width: 0;
    transition: flex-grow .85s cubic-bezier(.2, .7, .2, 1),
                background .5s;
  }
  .div-card:hover,
  .div-card:focus-visible { flex-grow: 2.4 }

  .div-card p {
    opacity: 0;
    transform: translateY(12px);
    transition: opacity .5s cubic-bezier(.16, 1, .3, 1) .06s,
                transform .5s cubic-bezier(.16, 1, .3, 1) .06s;
  }
  .div-card .go {
    opacity: 0;
    transform: translateY(12px);
    transition: opacity .5s cubic-bezier(.16, 1, .3, 1) .12s,
                transform .5s cubic-bezier(.16, 1, .3, 1) .12s,
                gap .4s cubic-bezier(.7, 0, .2, 1),
                color .4s;
  }
  .div-card:hover p, .div-card:focus-visible p,
  .div-card:hover .go, .div-card:focus-visible .go {
    opacity: 1;
    transform: none;
  }
  .div-card:hover .go, .div-card:focus-visible .go { opacity: 1; gap: 16px }

  /* ── The name, on its side ──────────────────────────────────────────────
   * The home page's rail turns each door's name ninety degrees while the panel
   * is closed and swaps it for the full copy when it opens. Every other page
   * ended with the same cross-links but showed the title flat, so the device
   * only existed in one place. Now they all carry it.
   *
   * The span is written by motion.js from the card's own heading — see the
   * note there. The heading it duplicates goes with the rest of the body.
   */
  .div-card.has-spine .inner { opacity: 0; transform: translateY(14px) }
  .div-card.has-spine .inner h3,
  .div-card.has-spine .inner h4 { transition: opacity .45s var(--coil) }
  .div-card.has-spine:hover .inner,
  .div-card.has-spine:focus-visible .inner { opacity: 1; transform: none }

  .cb-spine {
    position: absolute;
    bottom: clamp(20px, 2.4vw, 34px);
    left: clamp(20px, 2.4vw, 34px);
    z-index: 3;
    transform-origin: left bottom;
    transform: rotate(-90deg);
    white-space: nowrap;
    pointer-events: none;
    font: 900 clamp(.95rem, 1.5vw, 1.32rem)/1 var(--sans);
    font-variation-settings: 'wdth' 118;
    text-transform: uppercase;
    letter-spacing: -.02em;
    color: #F5F5F2;
    text-shadow: 0 2px 14px rgba(10, 10, 10, .8);
    transition: opacity .45s var(--coil), letter-spacing .5s var(--coil);
  }
  .div-card:hover .cb-spine,
  .div-card:focus-visible .cb-spine { opacity: 0; letter-spacing: .06em }
}
/* Stacked on a phone there is nothing to expand into, so the cards read open
   and the vertical name would just be a word lying on its side. */
@media (max-width: 900px) {
  .cb-spine { display: none }
  .div-card.has-spine .inner { opacity: 1; transform: none }
}

.tile .play { transition: opacity .5s cubic-bezier(.2, .7, .2, 1),
                          transform .5s cubic-bezier(.2, .7, .2, 1),
                          border-color .5s, background .5s }
.tile:hover .play { transition: opacity .2s cubic-bezier(.7, 0, .2, 1),
                                transform .3s cubic-bezier(.7, 0, .2, 1),
                                border-color .16s, background .16s }

/* ── Venom as ink ─────────────────────────────────────────────────────────
 * Measured across the eleven pages: #C2261D reads 3.38:1 on the black, 2.85:1
 * on the grey sections and 3.85:1 on the pale ones. Every one of those is a
 * fail for text under 24px, and venom is exactly what the small uppercase
 * labels are painted in — the eyebrows, the index strips, the year chips, the
 * step numbers. The labels the eye is meant to catch first were the hardest
 * ones to read.
 *
 * The fix was not to retire the accent but to lighten it where venom is
 * *lettering* rather than fill: #EA5544 on the dark ground (5.5:1 · 4.7:1) and
 * a deeper #8E1B14 on the pale one (6:1).
 *
 * THE CLIENT HAS OVERRULED BOTH ENDS OF THAT, AND IT IS THEIR CALL TO MAKE.
 * First the deep tone on the pale sections — it read as maroon on grey and
 * stopped looking like Cascabel. Then, on 2026-08-18, the light tint on the
 * dark ground for the same reason: two reds on one page read as a mistake, and
 * the brand has one red. So the ink token now resolves to the brand red
 * everywhere and the site is painted in a single #C2261D.
 *
 * The token STAYS, rather than being deleted and its ~60 selectors rewritten
 * to var(--venom). It is the one place the decision is recorded, and the one
 * place it can be undone.
 *
 * What that costs, on the record: 3.38:1 on black, 2.85:1 on the grey bands
 * and 3.85:1 on the pale ones — all below the 4.5:1 line for text under 24px.
 * Display type is unaffected: the venom words inside an h2 clear the 3:1
 * threshold that applies at that size. Worth knowing, not worth overruling.
 */
:root { --venom-ink: var(--venom) }
.on-light { --venom-ink: var(--venom) }
.on-light .panel, .on-light .proc-step { --venom-ink: var(--venom) }

.eyebrow, .eyebrow.venom, .on-light .eyebrow,
.jump .lbl, .ruler .lbl,
.big-num .plus, .metric .plus,
.br .n, .sb .n, .pipe-step .n, .proc-step .n, .step .num,
.bracket-box .bh span, .ed-row .st,
.cr span, .credit .role,
.day-strip .d .yr, .ed .yr, .triptych .amp,
.deal .tag, .rel-photo .tag, .rat-body .cat, .st-cat, .idx-row .cat,
.el-out .kick, .scale-info .kick, .yr-info .kick,
.mat-info .kick, .plot-info .kick,
.mh-top .mid, .inv-head-note, .map-list span, .nf-code,
.drawer a span, .story:hover h3,
.fld .err, .foot-news .nl-msg.bad,
.receipt .total b, .sent b, .cs-summary .txt b,
.on-light .intro-stats .s b,
.pcard:hover .meta .price, .store-card:hover .meta span,
.on-light .rat-card:hover .go { color: var(--venom-ink) }

/* A handful of pale panels are built light by their own page CSS rather than by
   the .on-light helper, so the token has to be handed to them directly. */
.plot-info, .mat-info, .inv-head-note,
.receipt, .apply .step, .press .story { --venom-ink: var(--venom) }

/* The eyebrow on a pale section was pinned to the flat accent with !important
   back when the helper was written, which outranked the ink token. It reads
   from the token now, like every other label. */
.on-light .eyebrow { color: var(--venom-ink) !important }

/* The room numbers on Studios float over a photograph, not over a surface, so
   their legibility depends on whichever frame is underneath. A shadow of the
   ground colour keeps them readable on the pale shots without touching the
   dark ones. */
.rm .no { text-shadow: 0 1px 3px rgba(10, 10, 10, .85) }

/* ── The page stops jumping when the font arrives ─────────────────────────
 * Measured on a throttled connection, four pages shifted while they loaded —
 * Rattling 0.096, Grip 0.091, Store 0.038, About 0.031 — and the things moving
 * were paragraphs and headings, not photographs. Archivo comes from Google
 * Fonts with display:swap, so the first paint is drawn in Segoe UI or Arial and
 * the whole page re-flows the instant the real face lands. On Rattling that
 * threw the lead story 144px down the page, under the reader's eye.
 *
 * The stand-in is now told to occupy Archivo's space exactly. The numbers are
 * measured, not guessed: `node _tools/font_metrics.js` renders the same string
 * in each face and prints these four values. Two faces, because Windows falls
 * back to Segoe UI and everything else to Arial, and their metrics differ
 * enough to matter.
 *
 * The swap still happens — it just changes the letterforms and nothing else.
 */
@font-face {
  font-family: 'Archivo Fallback';
  font-weight: 100 900;
  src: local('Segoe UI'), local('Segoe UI Regular');
  size-adjust: 102.9%;
  ascent-override: 85.6%;
  descent-override: 20.4%;
  line-gap-override: 0%;
}
@font-face {
  font-family: 'Archivo Fallback';
  font-weight: 100 900;
  src: local('Arial'), local('Helvetica Neue'), local('Liberation Sans');
  size-adjust: 99.6%;
  ascent-override: 88.4%;
  descent-override: 21.1%;
  line-gap-override: 0%;
}
:root {
  --sans: 'Archivo', 'Archivo Fallback', -apple-system, 'Segoe UI', system-ui, sans-serif;
}

/* The headings need a stand-in of their own. They are set at weight 800 and
 * wdth 110-118, where Archivo runs about a third wider than Segoe UI and about
 * a fifth wider than Arial. That gap is what dropped Rattling's lead story
 * 144px down the page: the masthead headline fitted on one line in the
 * stand-in and broke onto two when the real face landed.
 *
 * The numbers come from `node _tools/font_metrics.js`. They are struck at wdth
 * 114, between the 110 of the base heading rule and the 118 a few headlines
 * override to — matching either one exactly would misfit the other.
 */
@font-face {
  font-family: 'Archivo Display Fallback';
  /* Archivo is one variable face covering 100-900. Without the same range here
     the stand-in declares itself weight 400, the browser synthesises the bold,
     and the match comes out about 12% short — the width of the synthesis. */
  font-weight: 100 900;
  src: local('Segoe UI'), local('Segoe UI Regular');
  size-adjust: 129.7%;
  ascent-override: 67.8%;
  descent-override: 16.2%;
  line-gap-override: 0%;
}
@font-face {
  font-family: 'Archivo Display Fallback';
  font-weight: 100 900;
  src: local('Arial'), local('Helvetica Neue'), local('Liberation Sans');
  size-adjust: 125.6%;
  ascent-override: 70.1%;
  descent-override: 16.7%;
  line-gap-override: 0%;
}
h1, h2, h3, h4 {
  font-family: 'Archivo', 'Archivo Display Fallback', -apple-system, 'Segoe UI',
               system-ui, sans-serif;
}

/* ═════════════════════════════════════════════════════════════════════════
   THE FINISH PASS
   Four things, applied across the site: the icons stop being line art and
   start being lit, a few surfaces become glass, the scroll behaves, and every
   hover moves on the same two curves instead of eleven different ones.
   ═════════════════════════════════════════════════════════════════════════ */

/* ── One set of curves ────────────────────────────────────────────────────
 * The site had a dozen ad-hoc durations. Two curves cover everything here: a
 * strike for arrivals (fast in, settles) and a coil for departures (unhurried).
 * That is the brand's own timing — a snake goes out quicker than it comes back.
 */
:root {
  --coil: cubic-bezier(.2, .7, .2, 1);
  --strike: cubic-bezier(.7, 0, .2, 1);
  --t-in: .18s;
  --t-out: .5s;
}

/* ── Icons ────────────────────────────────────────────────────────────────
 * No glow. A previous pass lit them with a drop-shadow in currentColor and the
 * client was right to send it back: on the pale sections it made pink discs,
 * and on the dark ones it turned precise line art into something soft. The
 * drawing is the drawing.
 *
 * What happens on hover is the whole move: the ring takes the icon's colour —
 * it is a neutral hairline the rest of the time — and both grow a little. That
 * reads as the icon answering, which is all it needed to do.
 */
.ico svg { transition: transform .55s var(--coil) }
.ico::before {
  transition: border-color var(--t-out) var(--coil),
              opacity var(--t-out) var(--coil),
              transform var(--t-out) var(--coil);
}
.hoverable:hover .ico svg,
.hoverable:focus-visible .ico svg,
.inv-item:hover .ico svg,
a:hover .ico svg,
button:hover .ico svg {
  transform: scale(1.12);
  transition-duration: var(--t-in);
  transition-timing-function: var(--strike);
}
.hoverable:hover .ico::before,
.hoverable:focus-visible .ico::before,
.inv-item:hover .ico::before,
a:hover .ico::before,
button:hover .ico::before {
  border-color: currentColor;
  transform: scale(1.08);
  transition-duration: var(--t-in);
  transition-timing-function: var(--strike);
}

/* The rattle dot in the wordmark is the smallest piece of brand on the page,
   and a dot is meant to read as a light. That one keeps its halo. */
.brand .dot { box-shadow: 0 0 10px -1px currentColor }

/* ── Glass ────────────────────────────────────────────────────────────────
 * Only where something genuinely sits ON something else: the index strips that
 * ride over a section, the little plates that float on a photograph, and the
 * chrome of the lightboxes. Glass on a panel that has nothing behind it is a
 * grey box with extra steps.
 */
.jump, .ruler {
  background: rgba(18, 18, 18, .66);
  backdrop-filter: blur(14px) saturate(1.3);
  -webkit-backdrop-filter: blur(14px) saturate(1.3);
  border-block: 1px solid rgba(245, 245, 242, .1);
}
/* Grip's index strip is a pale band — the client's call. Written out here
   rather than reached for with .on-light: that helper remaps --bone to black
   across everything it contains, and this strip is four rules wide. The yellow
   hover goes with it; yellow on bone is not a hover, it is a disappearance. */
.jump.jump-light {
  background: rgba(245, 245, 242, .95);
  border-block-color: rgba(10, 10, 10, .16);
}
.jump.jump-light .lbl { color: var(--venom) }
.jump.jump-light a { color: rgba(10, 10, 10, .62) }
.jump.jump-light a::before { background: rgba(10, 10, 10, .2) }
.jump.jump-light a:hover { color: var(--venom) }
.jump.jump-light a:hover::before { background: var(--venom) }
.tile .song, .gf .cap, .rm .no, .rm .meta, .fr-no, .fr-type, .pcard .flag,
.pcard .cat, .spot-ph .tag, .spot-ph .zhint, .ring-hint, .st-ph .st-flag,
.deal .tag, .ol-slide figcaption {
  backdrop-filter: blur(6px);
  -webkit-backdrop-filter: blur(6px);
}
.pcard .flag, .spot-ph .tag, .spot-ph .zhint, .fr-type {
  border-radius: 5px;
  border: 1px solid rgba(245, 245, 242, .14);
}
.lb-top, .lb-bot, .lb-nav, .lb-close, .ring-close, .shot-close, .ol-btn {
  backdrop-filter: blur(12px) saturate(1.2);
  -webkit-backdrop-filter: blur(12px) saturate(1.2);
}

/* ── Nothing runs behind an open viewer ───────────────────────────────────
 * Measured: opening a gallery took the page from 36ms a frame to 80, with a
 * worst frame of 150. The cause was not the photograph. It was everything
 * still being composited behind the dialog — thirty-two elements carrying a
 * blur, eleven animations running, and a full-screen backdrop-filter on the
 * dialog itself.
 *
 * The backdrop is already 94–96% opaque, so the blur under it was buying
 * almost nothing and costing a full-viewport filter every frame. It comes off.
 * Everything decorative behind it stops. The page under a viewer is not
 * visible; it does not need to be drawn.
 */
/* Every full-screen viewer on the site: the contact sheet (.lb), the Grip
   frames (.glb), the Studios rooms (.rlb) and the drum (.ring-shot). */
.lb.on, .glb.on, .rlb.on, .ring-shot.on {
  backdrop-filter: none !important;
  -webkit-backdrop-filter: none !important;
  /* the last 3% of ink replaces what the blur was hiding */
  background: rgba(4, 4, 4, .975) !important;
}
/* Their own chrome keeps its glass: a few small boxes, not the whole viewport. */
body.locked .cb-field,
body.locked .cb-glow,
body.locked .snake-ghost,
body.locked .marquee,
body.locked .ticker { display: none !important }
body.locked .reveal { transition: none !important }
/* The strips and floating chips lose their glass too — all of it is behind the
   dialog and none of it is being looked at. */
body.locked .jump, body.locked .ruler,
body.locked .tile .song, body.locked .gf .cap, body.locked .rm .no,
body.locked .rm .meta, body.locked .fr-no, body.locked .fr-type,
body.locked .pcard .flag, body.locked .deal .tag {
  backdrop-filter: none !important;
  -webkit-backdrop-filter: none !important;
}

/* ── How things arrive ────────────────────────────────────────────────────
 * The site revealed everything the same way: 28px up, 0.95s, every element in
 * a section on its own clock. Twenty-eight pixels is a jump — you watch the
 * page assemble itself. The point of a reveal is that the reader never quite
 * catches it happening; they just find the page already there.
 *
 * So: a third of the travel, half again the time, and a curve that spends most
 * of it decelerating. It reads as a fade with a breath of lift behind it
 * rather than as a slide. Under 900px it is a straight fade — on a phone the
 * viewport is short enough that the travel is the only thing you see.
 *
 * The heavy blocks — photographs, plates, drawings — get a hair of scale as
 * well. Coming from 0.985 they settle INTO the layout instead of landing on
 * it, which is the difference between a page that assembles and a page that
 * develops.
 */
.reveal {
  transition: opacity 1.35s cubic-bezier(.16, 1, .3, 1),
              transform 1.35s cubic-bezier(.16, 1, .3, 1);
  transform: translateY(10px);
}
.reveal.in { transform: none }

/* A card can be BOTH a reveal target and a panel in an expanding rail, and the
   rule above sets `transition` wholesale — which quietly deleted the flex-grow
   animation those panels rely on. Measured: they went 409px to 409px on hover,
   no movement at all, so the photograph just appeared at full width instead of
   the panels pushing their neighbours aside. The home rail was never affected
   because there the reveal sits on the container, not on each door.
   One list, carrying all of it. */
.div-card.reveal, .gl-card.reveal, .door.reveal, .cr.reveal {
  transition: flex-grow .9s cubic-bezier(.2, .7, .2, 1),
              background .5s cubic-bezier(.2, .7, .2, 1),
              opacity 1.35s cubic-bezier(.16, 1, .3, 1),
              transform 1.35s cubic-bezier(.16, 1, .3, 1);
}

.sheet, .truck, .frame, .tile, .portrait, .slate, .board, .plot-wrap,
.scale-wrap, .cmp, .receipt, .callsheet, .ls, .gb-visual, .fit-wrap,
.spot-in, .bag-wrap, .tour-frame, .map-wrap, .teesvg, .sheet-board {
  transition-property: opacity, transform;
}
.reveal.sheet, .reveal.truck, .reveal.frame, .reveal.board, .reveal.plot-wrap,
.reveal.scale-wrap, .reveal.cmp, .reveal.receipt, .reveal.callsheet,
.reveal.ls, .reveal.gb-visual, .reveal.fit-wrap, .reveal.spot-in,
.reveal.bag-wrap, .reveal.map-wrap, .reveal.sheet-board {
  transform: translateY(14px) scale(.985);
  transform-origin: 50% 65%;
}
.reveal.sheet.in, .reveal.truck.in, .reveal.frame.in, .reveal.board.in,
.reveal.plot-wrap.in, .reveal.scale-wrap.in, .reveal.cmp.in,
.reveal.receipt.in, .reveal.callsheet.in, .reveal.ls.in,
.reveal.gb-visual.in, .reveal.fit-wrap.in, .reveal.spot-in.in,
.reveal.bag-wrap.in, .reveal.map-wrap.in, .reveal.sheet-board.in {
  transform: none;
}

/* The rule beside an eyebrow draws itself in rather than being there already.
   It is two hundred milliseconds of nothing much, and it is the first thing
   that moves in every section. */
.eyebrow::before, .sec-num::before {
  transform-origin: left center;
  transition: transform .8s cubic-bezier(.16, 1, .3, 1) .15s;
}
.reveal .eyebrow::before,
.reveal.eyebrow::before,
.reveal .sec-num::before { transform: scaleX(0) }
.reveal.in .eyebrow::before,
.reveal.in.eyebrow::before,
.reveal.in .sec-num::before { transform: scaleX(1) }

@media (max-width: 900px) {
  .reveal, .reveal.sheet, .reveal.truck, .reveal.frame, .reveal.board {
    transform: translateY(6px);
  }
}
@media (prefers-reduced-motion: reduce) {
  .reveal { transform: none !important; transition: none !important }
  .eyebrow::before, .sec-num::before { transform: scaleX(1) !important }
}

/* ── Scroll ───────────────────────────────────────────────────────────────
 * The index strips jump between sections. Sending the page there instantly
 * loses the reader; easing it there keeps them oriented. And every anchor
 * target gets clearance for the fixed navigation, which was landing on top of
 * the heading the visitor had just asked to see.
 */
html { scroll-behavior: smooth }
section[id], header[id], div[id].jump + section { scroll-margin-top: 96px }
@media (prefers-reduced-motion: reduce) { html { scroll-behavior: auto } }

/* ── Hover, everywhere, on the same clock ─────────────────────────────────
 * Arrivals are quick and settle; departures take their time.
 */
.btn, .navcta, .navlinks a, .foot-col a, .div-card, .door, .gl-card, .story,
.pcard, .store-card, .buy-card, .rat-card, .idx-row, .doc, .tour3, .b-item,
.inv-item, .proc-step, .cr, .metric, .deal, .truck, .perk, .v, .sv, .d {
  transition-timing-function: var(--coil);
  transition-duration: var(--t-out);
}
.btn:hover, .navcta:hover, .navlinks a:hover, .foot-col a:hover,
.div-card:hover, .door:hover, .gl-card:hover, .story:hover, .pcard:hover,
.store-card:hover, .buy-card:hover, .rat-card:hover, .idx-row:hover,
.doc:hover, .tour3:hover, .b-item:hover, .inv-item:hover, .proc-step:hover,
.cr:hover, .metric:hover, .deal:hover, .truck:hover, .perk:hover, .v:hover,
.sv:hover, .d:hover {
  transition-timing-function: var(--strike);
  transition-duration: var(--t-in);
}
/* A card lifting a little as it takes the pointer is the cheapest bit of
   quality on a page and the site was not spending it. */
.doc, .tour3, .buy-card, .store-card, .idx-row, .perk {
  transition-property: background, border-color, color, transform, box-shadow;
}
.doc:hover, .tour3:hover, .buy-card:hover, .store-card:hover, .perk:hover {
  transform: translateY(-3px);
}

/* ── The parts the browser draws ──────────────────────────────────────────
 * A page is not only what was designed. The scrollbar, the text caret, the
 * colour a selection turns, the checkbox tick, the gap under an underline —
 * these all ship with defaults that belong to no design system, and on a site
 * this dark they arrive as a strip of Windows grey down the right-hand edge
 * and a black insertion point inside a black field. The selection colour was
 * already claimed; the rest was not.
 */
html {
  scrollbar-color: #3A3A3A var(--black);   /* thumb, track */
  scrollbar-width: thin;
  accent-color: var(--venom);              /* checkboxes, radios, range */
}
::-webkit-scrollbar { width: 11px; height: 11px }
::-webkit-scrollbar-track { background: var(--black) }
::-webkit-scrollbar-thumb {
  background: #3A3A3A;
  border: 3px solid var(--black);          /* inset, so the thumb reads as a bar */
  border-radius: 6px;
}
::-webkit-scrollbar-thumb:hover { background: var(--venom) }
::-webkit-scrollbar-corner { background: var(--black) }

/* The caret is the one mark on the page the visitor puts there themselves. */
input, textarea, select, [contenteditable] { caret-color: var(--venom) }
.on-light input, .on-light textarea, .sub-form input { caret-color: var(--venom-deep) }

/* Underlines in running copy: the default sits on the baseline and cuts the
   descenders of every g, p and y. */
p a:not(.btn), li a:not(.btn), .lede a, dd a {
  text-underline-offset: .22em;
  text-decoration-thickness: 1px;
  text-decoration-color: var(--venom-ink);
  transition: text-decoration-color .3s;
}
p a:not(.btn):hover, li a:not(.btn):hover { text-decoration-color: currentColor }

/* ── Where the lines break ────────────────────────────────────────────────
 * Headlines here are two and three words to a line and set very large, so a
 * single word stranded on the last line is the failure mode. `balance` gives
 * the browser permission to even the lines out; `pretty` keeps a paragraph
 * from ending on one orphan word. Both respect any break already written in.
 */
h1, h2, h3, h4, .lede, .lead, .sec-head p, blockquote { text-wrap: balance }
p, li, dd, figcaption { text-wrap: pretty }

/* ── Thumb-sized on a phone ───────────────────────────────────────────────
 * Measured at 390px wide: the footer links stand 23px tall, the breadcrumbs and
 * the index strips 10px, the newsletter field 39px, the quantity steppers 38px.
 * All of that is comfortable under a mouse and mean under a thumb.
 *
 * The strips and the breadcrumbs grow by padding and give the same amount back
 * as negative margin, so the hit area doubles and the layout does not move an
 * inch. The form controls and the footer links simply get their height: they
 * sit in a stack where the extra room reads as air, not as drift.
 */
@media (max-width: 900px) {
  .crumbs a, .jump a, .ruler a, .rel-link, .brand {
    padding-block: 12px;
    margin-block: -12px;
  }
  .foot-col a { padding-block: 10px; margin-bottom: 0 }
  .foot-bot a { padding-block: 8px }

  .foot-news input, .foot-news button,
  .fld input, .fld textarea, .sub-form input, .sub-form button,
  .field input, .field button { min-height: 44px }
  .qty button { width: 44px; height: 44px }
}

/* ── Las cifras responden ─────────────────────────────────────────────────
 * Cada bloque de stats del sitio usa la misma estructura: un .num-row con la
 * cifra y el signo, o un .s con un <b>. Al pasar el mouse la cifra crece un
 * poco — el recuadro rojo que hacia esto antes se saco por pedido del cliente,
 * y esto lo reemplaza: responde el numero, que es lo que se estaba mirando.
 *
 * transform-origin abajo, no al centro: creciendo desde la base la cifra se
 * queda apoyada en su etiqueta en vez de flotar sobre ella.
 */
.metric .num-row, .t .num-row, .tt-facts b, .intro-stats .s b,
.rel-stats .s b, .big-num {
  transform-origin: 50% 100%;
  transition: transform .45s cubic-bezier(.2, .7, .2, 1);
  will-change: transform;
}
.metric:hover .num-row,
.t:hover .num-row,
.tt-facts div:hover b,
.intro-stats .s:hover b,
.rel-stats .s:hover b,
.big-num:hover {
  transform: scale(1.09);
  transition-duration: .18s;
  transition-timing-function: cubic-bezier(.7, 0, .2, 1);
}
/* Las celdas que no eran interactivas necesitan decir que reaccionan. */
.t, .tt-facts div, .intro-stats .s, .rel-stats .s { transition: background .4s }

/* ── El carrusel de Our Work ──────────────────────────────────────────────
 * Menos curva en las laminas: 0.14 sobre una lamina de 1.62 de ancho leia como
 * una pastilla. Sigue redondeada, pero ahora es un marco.
 */
