/* ===================================================================
   Section layouts, corrected against the Figma frames.
   Loaded after theme.css and overrides the first-pass geometry.
   =================================================================== */

/* --- hero — measured from the Figma frame (1440x780) --------------
   nav bar 120 tall (logo 40 at y=40) · copy column 500 wide starting at
   x=80 · eyebrow baseline y=336 · buttons y=570 · dots y=666 centred. */
.hero {
  position: relative;
  min-height: 780px;
  background: #000;
  display: block;
  padding: 0;
  overflow: hidden;
  isolation: isolate;
}
.hero::after { display: none; }

.hero__bg {
  position: absolute;
  inset: 0;
  z-index: -1;
}
.hero__bg img,
.hero__bg video {
  width: 100%;
  height: 100%;
  object-fit: cover;
  object-position: center;
  display: block;
}

/* `height: 100%`, not 780. The hero keeps the frame's 1440:780 above 1440,
   so on a 1940 screen it stands 1051 tall - but the container was pinned to
   780 and sat at the TOP of it, leaving 271px of dead space under the copy
   and the dots stranded in the middle. Milad spotted this on his own screen.

   The hero is `display: block` here (theme.css asks for flex, this file wins),
   so the container does the bottom alignment itself. */
.hero .container {
  position: relative;
  height: 100%;
  min-height: 780px;
  display: flex;
  flex-direction: column;
  justify-content: flex-end;
  max-width: var(--page-max);
  margin-inline: auto;
  padding-inline: 80px;
}

/* The homepage copy bottom-anchors inside the flex-end container, exactly like
   every inner hero - so it holds its distance from the floor (and stays with
   the buttons and dots) as the hero grows past 1440. `.hero--has-actions
   .hero__inner` supplies the 170px / 11.81vw bottom clearance and the dots
   scale with it. The old `position:absolute; top:336px` pinned the copy a fixed
   336 from the TOP, so on a screen taller than the 1440x780 frame - a 2K 16:9
   monitor makes the hero ~1387 tall - it drifted up into the top quarter, away
   from the buttons and dots. Milad caught it on his own 2K monitor.
   Relative (not absolute) also lets it ride the centred container's 80px gutter
   rather than the raw viewport edge, so it stays on the content grid on very
   wide screens. */
.hero__inner {
  position: relative;
  width: 500px;
  max-width: calc(100% - 160px);
  padding: 0;
}
.hero__eyebrow { color: var(--c-on-dark); margin: 0; }
.hero__title {
  font-family: var(--font-display);
  font-weight: var(--fw-black);
  font-size: var(--fs-32);
  line-height: var(--lh-32);
  color: var(--c-bg);
  /* The eyebrow sits directly on the title in the frame at both breakpoints -
     desktop 336+18=354 with the title at 354, mobile 326+18=344 with the title
     at 344. The 18 here was never in either. Mobile got this in run 15 through
     an override; the base rule kept the 18 and desktop wore it until now,
     which is the same way the 360 stage survived eleven runs after its mobile
     fix. The 16 below is right on desktop - the frame's title ends 438 and its
     lede starts 454. */
  margin: 0 0 16px;
}
/* #DDDEE1 on home, About, Contact and News. Two frames differ - Lighting
   Collection sets #F0F1F2 and Bespoke #FAFAFA - so they say so rather than
   the four of them being averaged into one near-white. */
.hero__lede { color: var(--c-on-dark); margin: 0 0 36px; }
body.page--collections .hero__lede { color: #F0F1F2; }
body.page--bespoke     .hero__lede { color: #FAFAFA; }

.hero__actions { display: flex; gap: 24px; align-items: center; }

.hero__dots {
  position: absolute;
  left: 0; right: 0;
  bottom: 100px;                    /* 780 - 666 - 14 */
  height: 14px;                     /* the frame's hit area; the dot inside is 8 */
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 12px;
  z-index: 2;
}
.hero__dots button {
  width: 8px; height: 8px; padding: 0;
  border: 0; border-radius: 50%;
  background: rgba(240, 241, 242, .4);
  transition: background var(--dur) var(--ease), transform var(--dur) var(--ease);
}
.hero__dots button[aria-current="true"] {
  background: var(--c-bg);
  transform: scale(1.25);
}

/* Nav bar: 120 tall (40px logo with 40 above and below). In the design the
   items sit 24px apart and the last one ends flush with the 80px gutter. */
.site-header { padding-block: 40px; }
.site-header .container { padding-inline: 80px; gap: 40px; align-items: center; }
.brand__mark svg, .brand__img { height: 40px; }

.nav__list {
  display: flex;
  align-items: center;
  gap: 24px;
  margin: 0;
  padding: 0;
  list-style: none;          /* a bare <li> otherwise shows a bullet */
}
.nav__list li { list-style: none; margin: 0; }
.nav__list li::marker { content: none; }
.nav__list a {
  display: inline-flex;
  align-items: center;
  height: 40px;
  padding-inline: 12px;
  white-space: nowrap;
}

/* The frame marks the page you are on with a dot before its label.
   WordPress already puts these classes on the <li>, so the markup needs
   nothing - current-menu-item is the page itself, current-menu-ancestor
   covers a post sitting under one of the menu entries. */
.nav__list li.current-menu-item > a::before,
.nav__list li.current_page_item > a::before,
.nav__list li.current-menu-ancestor > a::before,
.nav__list li.current-menu-parent > a::before {
  content: "";
  flex: 0 0 auto;
  width: 5px;
  height: 5px;
  margin-inline-end: 10px;
  border-radius: 50%;
  background: currentColor;
}

/* WordPress pushes the page down by the admin bar; a fixed header has to
   move with it or it hides under the bar. */
.admin-bar .site-header--sticky { top: 32px; }


/* --- collections — measured from F-S01 (1440x924) -----------------
   section padding 100/200 · head row 40 tall with 28px gaps ·
   36px to the grid · three 341-wide columns, 8px apart, 648 tall ·
   the middle column splits into two 320-tall cards. */
.section--collections {
  padding-block: 100px;
}
.section--collections .container {
  padding-inline: 200px;
  max-width: var(--page-max);
}

.head-row {
  display: grid;
  grid-template-columns: auto minmax(40px, 1fr) auto;
  align-items: center;
  gap: 28px;
  height: 40px;
  margin-bottom: 36px;
}
.head-row .h-section { margin: 0; }
.head-row__rule { height: 1px; background: var(--c-line); }

.collection-mosaic {
  display: grid;
  /* minmax(0, ...) so no tile can push its track wider than its share - a
     plain 1fr lets an item's min-content win, and the middle column came out
     345 against the outer two at 340.

     The rows are a RATIO, not 320px. Fixed rows only agree with the frame at
     exactly 1440: the columns grow with the window and the rows did not, so
     on a 1583-wide screen the tile was 389x648 - ratio 0.600 against the
     photograph's 0.526 - and `cover` scaled the chandelier up and cut ~46px
     off the top and the bottom of it. Milad saw this; I did not, because I
     had been measuring at 1440 where the two happen to match. The short tile
     states 341:320 and the tall one follows from two rows plus the gap, so
     the tile keeps the frame's proportion at every width. */
  grid-template-columns: repeat(3, minmax(0, 1fr));
  grid-template-rows: auto auto;
  gap: 8px;
}
.collection-mosaic > :nth-child(1) { grid-column: 1; grid-row: 1 / span 2; }
.collection-mosaic > :nth-child(2) { grid-column: 2; grid-row: 1; }
.collection-mosaic > :nth-child(3) { grid-column: 2; grid-row: 2; }
.collection-mosaic > :nth-child(4) { grid-column: 3; grid-row: 1 / span 2; }
/* The two short tiles are 341x320 - but only on the desktop frame. The 390
   frame stacks three SQUARE 350x350 tiles, and left unscoped this pulled
   cards two and three to 328 and the band came out 43 short. */
@media (min-width: 900px) {
  .collection-mosaic > :nth-child(2),
  .collection-mosaic > :nth-child(3) { aspect-ratio: 341 / 320; }
}

.collection-card {
  position: relative;
  display: block;
  height: 100%;
  aspect-ratio: auto;
  overflow: hidden;
  border-radius: 0;
  background: #111;
}
.collection-card img {
  width: 100%; height: 100%;
  object-fit: cover;
  transition: transform .8s var(--ease);
}
.collection-card:hover img { transform: scale(1.05); }

/* ---------- card hover swap ------------------------------------------
   A collection may carry a second photograph - the piece on its own, then
   the piece in a room. Both are in the markup; the second is stacked on the
   first and faded in, so there is no flash of empty card while a file
   downloads.

   `@media (hover: hover)` keeps it off touch screens, where :hover sticks
   after a tap and would leave the second photo showing until you tapped
   something else. */
.collection-card__media {
  position: relative;
  display: block;
  height: 100%;
  overflow: hidden;
}
.collection-grid .collection-card__media { height: auto; }

.collection-card .collection-card__media .collection-card__img--alt {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  object-fit: cover;
  opacity: 0;
  transition: opacity .5s var(--ease);
}
@media (hover: hover) {
  .collection-card:hover .collection-card__img--alt { opacity: 1; }
  /* A card that swaps does not also zoom. The homepage band zooms because it
     has one photograph; the Lighting Collection cards have two and cross-fade
     between them, and doing both at once read as a wobble. */
  .collection-card:hover .collection-card__media--swap img { transform: none; }
}


/* 28 inside a 341 tile on all three sides. Stated as padding, not as
   left/right/bottom in px: a percentage padding resolves against the WIDTH on
   every side including the bottom, so the inset keeps the frame's proportion
   as the tile grows - in px it read 25 frame-units on a 1583 screen. */
.collection-card__meta {
  position: absolute;
  left: 0;
  right: 0;
  bottom: 0;
  padding: 0 8.21% 8.21%;   /* 28 of 341 */
  display: flex;
  flex-direction: column;
  gap: 2px;
  background: none;
}
/* Plus Jakarta Sans 500 12/18 #BDBDBD, letterSpacing 0. The .04em tracking
   was ours, not the frame's - the file states 0 on both caption lines, and it
   is what made "COLLECTION" read wider than the design. `text-transform` stays
   because the field is free text; the frame's own string is already capitals. */
.collection-card__label {
  font-size: var(--fs-12);
  line-height: var(--lh-12);
  font-weight: var(--fw-medium);
  letter-spacing: 0;
  text-transform: uppercase;
  color: var(--c-muted-light);
}
/* Plus Jakarta Sans 500 16/24 #FFFFFF - the body face, not the serif.
   An earlier pass set this to PP Editorial Old on the reading that the file
   said so. It does not; read straight out of F-S01 (101:13169):

     TEXT x228 y774  Plus Jakarta Sans  w500  16/24  #FFFFFF  'Luminous Spiral'

   and the 390 frame agrees. Milad spotted it against the frame. */
.collection-card__title {
  font-family: var(--font-body);
  font-size: var(--fs-16);
  line-height: var(--lh-16);
  font-weight: var(--fw-medium);
  color: #fff;
  margin: 0;
}
/* No scrim. F-S01 has nothing behind the caption - two text nodes on the
   photograph and that is all; scanned the whole tile for a gradient or an
   overlay rectangle and there is none. Ours darkened the bottom 180px by 55%,
   which is the colour difference Milad saw. The photographs are shot on black
   anyway, so the caption needs no help. */

/* --- brand intro / team - measured from F-S02 (1440x776) -----------
   Twelve 140x160 photos in three clusters (four left, four across the
   middle, four right) with the copy sitting in the lower-centre gap the
   middle cluster leaves. Everything is a percentage of a 1440x552 stage
   so the composition scales as one piece. The 2026-09 rework filled the
   middle - it used to be empty with the copy floating in it - and dropped
   the grey band, so the section is now the page colour. The photos'
   vertical rhythm and the copy block are unchanged; only the horizontal
   grouping moved and a twelfth photo was added. */
.intro {
  background: var(--c-page);        /* was #F0F1F2; the reworked frame has no fill */
  text-align: center;
  padding-block: 100px;
}
/* The collage is positioned against the full 1440 frame, not against the
   80px-padded container, so this section opts out of the padding. */
.intro .container { padding-inline: 0; }

.intro__stage {
  position: relative;
  aspect-ratio: 1440 / 552;
  /* Cap at the design width. The photos are placed by percentage and the copy
     block by percentage-plus-fixed-height, so as the stage grows past 1440 the
     portraits' feet (60% of the height) drop FASTER than the paragraph's top
     and eventually meet it - on a 1920 screen they touch, and the copy sits on
     photos 4 and 9. Held at 1440 the whole composition keeps the frame's
     proportion at every width, so the ~60px gap the design has never closes.
     Milad was seeing this on his wide monitor. */
  max-width: 1440px;
  margin-inline: auto;
  padding: 0;
}

.intro__collage {
  position: absolute;
  inset: 0;
  display: block;
  pointer-events: none;
}
.intro__collage img {
  position: absolute;
  width: 9.722%;      /* 140 / 1440 */
  height: 28.986%;    /* 160 / 552  */
  object-fit: cover;
  border-radius: 2px;
}
/* Left cluster (four) */
.intro__collage img:nth-child(1)  { left: 8.194%;  top: 9.058%;  }  /* 118 / 162 */
.intro__collage img:nth-child(2)  { left: 8.194%;  top: 40.217%; }  /* 118 / 334 */
.intro__collage img:nth-child(3)  { left: 18.750%; top: 0.000%;  }  /* 270 / 112 */
.intro__collage img:nth-child(4)  { left: 18.750%; top: 31.159%; }  /* 270 / 284 */
/* Middle cluster (four across the top - this is what the rework filled) */
.intro__collage img:nth-child(5)  { left: 29.306%; top: 8.696%;  }  /* 422 / 160 */
.intro__collage img:nth-child(6)  { left: 39.861%; top: 0.000%;  }  /* 574 / 112 */
.intro__collage img:nth-child(7)  { left: 50.417%; top: 0.000%;  }  /* 726 / 112 */
.intro__collage img:nth-child(8)  { left: 60.972%; top: 9.058%;  }  /* 878 / 162 */
/* Right cluster (four) */
.intro__collage img:nth-child(9)  { left: 71.528%; top: 0.000%;  }  /* 1030 / 112 */
.intro__collage img:nth-child(10) { left: 71.528%; top: 31.159%; }  /* 1030 / 284 */
.intro__collage img:nth-child(11) { left: 82.083%; top: 9.058%;  }  /* 1182 / 162 */
.intro__collage img:nth-child(12) { left: 82.083%; top: 40.217%; }  /* 1182 / 334 */

/* Copy block: 547 wide at x=446, y=350 - it deliberately sits on top of
   the photos rather than beside them. */
.intro__body {
  position: absolute;
  left: 30.972%;      /* 446 / 1440 */
  top: 45.290%;       /* (350 - 100) / 552 */
  width: 37.986%;     /* 547 / 1440 */
  z-index: 2;
  text-align: center;
}
.intro__body .eyebrow {
  margin: 0;
  font-weight: var(--fw-regular);   /* 400 here, not the usual 510 */
  color: var(--c-muted);
}
.intro__body .h-section {
  /* 364 of the 547 available - the narrow measure is what breaks the
     headline onto two lines, exactly as the design has it */
  max-width: 66.545%;
  margin: 0 auto;
  /* no size here - see .projects__copy-head. The base .h-section rule owns
     28/42 desktop and 24/36 phone; two classes deep would beat both. */
  color: var(--c-ink);
}
.intro__body .body { margin: 16px 0 0; }
.intro__body .body + .body { margin-top: 12px; }

.intro__actions {
  display: flex;
  gap: 24px;
  justify-content: center;
  margin-top: 24px;
}
.intro__actions .btn {
  /* 193x40 filled #6B6E76 and 191x40 outlined with a 0.5 hairline #3B3D42,
     both labelled 16/24 - not 14/22. The label size was ours. */
  justify-content: center;
  min-width: 192px;
  height: 40px;
  padding: 0 24px;
  font-size: var(--fs-16);
  line-height: var(--lh-16);
}

/* --- projects - measured from F-05 (1440x1227) --------------------
   Two rows inside the usual 1280 content width. Every track is an `fr`
   share of the Figma widths, so the mosaic keeps its exact proportions
   at any viewport instead of drifting as it scales. */
/* F-05's frame fill is #F0F1F2, not the page white - it is a grey band
   between the white collections and white team sections. */
.projects { background: var(--c-bg); }
.projects .container { display: flex; flex-direction: column; gap: 16px; }

.projects__row { display: grid; gap: 16px; }

/* row 1: 670 + 594, 580 tall */
.projects__row--top {
  grid-template-columns: 670fr 594fr;
  aspect-ratio: 1280 / 580;
}
.projects__stack {
  display: grid;
  grid-template-rows: 272fr 288fr;
  gap: 16px;
  min-height: 0;                 /* let the rows shrink inside the grid */
}
.projects__pair {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: 16px;
  min-height: 0;
}

/* row 2: the copy block sits flush against the images, 431 tall */
.projects__row--bottom {
  grid-template-columns: 392fr 888fr;
  gap: 0;
  aspect-ratio: 1280 / 431;
}
.projects__pair--wide { grid-template-columns: 589fr 283fr; }

.projects__copy {
  display: flex;
  flex-direction: column;
  justify-content: center;
  gap: 32px;
  padding-inline: 16px;
}
.projects__copy-text { display: flex; flex-direction: column; gap: 16px; }
.projects__copy-head .eyebrow {
  margin: 0;
  font-weight: var(--fw-regular);
  color: var(--c-muted);
}
/* Size and line-height deliberately NOT set here. They were, and because
   this selector is two classes deep it beat the base `.h-section` at every
   width - including the phone rule, so "Our Projects" stayed at the desktop
   step on a 390 screen. Only the things that are actually specific to this
   heading live here now. */
.projects__copy-head .h-section {
  margin: 0;
  color: var(--c-ink);
}
.projects__copy-text .body { margin: 0; }
.projects__copy-action .btn {
  /* 173x40, hairline, label 16/24 #1E1F21. */
  justify-content: center;
  min-width: 173px;
  height: 40px;
  padding: 0 18px;
  font-size: var(--fs-16);
  line-height: var(--lh-16);
}

/* The phone frame centres the whole "Our Projects" copy column - eyebrow,
   heading, paragraph and button all on the middle of the page. Desktop keeps
   it as a left copy column beside the grid. */
@media (max-width: 720px) {
  .projects__copy { align-items: center; text-align: center; }
}

.project-tile {
  display: block;
  position: relative;
  overflow: hidden;
  min-height: 0;
  background: var(--c-line);
}
.project-tile img {
  width: 100%;
  height: 100%;
  object-fit: cover;
  display: block;
  transition: transform var(--dur) var(--ease);
}
.project-tile:hover img { transform: scale(1.03); }

/* --- stats - measured from "numbers" (1440x328) -------------------
   A photo with a flat #212121 wash at 35% over it. The solid colour stays
   underneath as the fallback while the photo is still loading. */
.stats {
  position: relative;
  background: var(--c-dark);
  isolation: isolate;
}
.stats__bg {
  position: absolute; inset: 0; z-index: -2;
  width: 100%; height: 100%; object-fit: cover;
}
.stats::before {
  content: ""; position: absolute; inset: 0; z-index: -1;
  background: rgba(33, 33, 33, .35);
}

/* --- hero video --------------------------------------------------- */
.hero__video {
  width: 100%;
  height: 100%;
  object-fit: cover;
  background: #000;
}


/* --- header variants ---------------------------------------------
   Over a hero the bar is transparent with light type; on a page with no
   hero it needs its own surface or the light type vanishes. */
.site-header--solid {
  position: relative;
  background: var(--c-dark-soft);
}
/* Sticky is about position, nothing else. The surface and the blur belong to
   the scrolled state only - carried here they painted a dark blurred band
   across the top of every hero the moment the page loaded, which is not in
   any frame. */
.site-header--sticky {
  position: sticky;
  top: 0;
}
.site-header--sticky.is-scrolled {
  backdrop-filter: blur(10px);
  -webkit-backdrop-filter: blur(10px);
  background: rgba(20, 20, 22, .72);
}

/* Past the first section the bar only comes back on the way up — see the note
   quoted in theme.js. It slides rather than blinks, and the transition is
   dropped for anyone who asked for less motion. */
.site-header {
  transition: transform var(--dur) var(--ease),
              background var(--dur) var(--ease),
              backdrop-filter var(--dur) var(--ease);
}
.site-header.is-hidden { transform: translateY(-100%); }

@media (prefers-reduced-motion: reduce) {
  .site-header { transition: none; }
}

.site-header--over.site-header--sticky { position: sticky; }

.header-search {
  background: none; border: 0; padding: 8px;
  color: var(--c-bg); display: grid; place-items: center;
  opacity: .85; transition: opacity var(--dur) var(--ease);
}
.header-search:hover { opacity: 1; }
.header-cta { padding: 10px 20px; font-size: var(--fs-14); }



/* --- hero slides --------------------------------------------------- */
.hero__slide {
  position: absolute;
  inset: 0;
  opacity: 0;
  transition: opacity 1s var(--ease);
  pointer-events: none;
}
.hero__slide.is-active { opacity: 1; }
.hero__slide img,
.hero__slide video { width: 100%; height: 100%; object-fit: cover; display: block; }

/* --- header: transparent over the hero, opaque once scrolled ------- */
.site-header--over {
  background: transparent;
  transition: background var(--dur) var(--ease), backdrop-filter var(--dur) var(--ease);
}
.site-header--over.is-scrolled {
  background: rgba(18, 18, 20, .82);
  backdrop-filter: blur(12px);
}
.site-header--over.site-header--sticky { position: fixed; inset: 0 0 auto 0; }

/* ===================================================================
   About page

   Five sections, in frame order: mission, story, founder, values, craft.
   The numbers band and the contact form below them are the homepage's own
   files, so they need nothing here.
   =================================================================== */

/* ---- mission: a quiet photo behind the copy, two dark cards on it ---- */
/* ---------- the founders (About) --------------------------------------
   Replaces the single-CEO band. Two 400-wide columns at x292 and x748 in a
   1440 frame - 856 across, centred, 56 between - and the RIGHT column starts
   100px lower than the left. The stagger is in the frame, not a rounding
   error: photo 1 is at y2768 and photo 2 at y2868, and each name follows its
   own photograph by the same 24px. */
/* Band y2548..3604, 1056 tall, on #F8F8F8 - one of the two tinted bands the
   About frame carries. Read off it:

     y100  eyebrow  Plus Jakarta Sans 400 14/22 #7D818A, centred, 529 wide
     y122  title    PP Editorial Old 400 28/42 #1E1F21
     y220  photo 1  x292 400x450        y320  photo 2  x748 400x450
     y694  name 1   PP Editorial Old 400 20/30        y794  name 2
     y726  role 1   14/22 #6B6E76                     y826  role 2
     y760  body 1   16/24 #6B6E76                     y860  body 2

   100 above the head and 100 below the second column's copy. */
.founders {
  background: #F8F8F8;
  padding-block: 100px;
}
.founders__head {
  /* 529 of the 1280 content column. A percentage, not 529px: the container is
     fluid, so a fixed width would read wider and wider than the design as the
     window narrows. */
  width: 41.33%;
  min-width: 260px;
  margin: 0 auto 56px;   /* 220 - 164 */
  text-align: center;
}
.founders__head .eyebrow { margin: 0 0 4px; }
.founders__head .h-section { margin: 0; }

.founders__grid {
  display: grid;
  grid-template-columns: 1fr;
  gap: 48px;
  width: 66.87%;   /* 856 of 1280 */
  margin: 0 auto;
}

.founder__media { margin: 0 0 24px; }
.founder__media img {
  display: block;
  width: 100%;
  aspect-ratio: 400 / 450;
  object-fit: cover;
}
.founder__name {
  margin: 0;
  font-family: var(--font-display);
  font-weight: var(--fw-regular);
  font-size: var(--fs-20);
  line-height: var(--lh-20);
  color: var(--c-ink);
}
.founder__role {
  margin: 2px 0 0;
  font-size: var(--fs-14);
  line-height: var(--lh-14);
  color: var(--c-body);      /* #6B6E76, not the lighter #7D818A */
}
.founder__text {
  margin: 12px 0 0;
  font-size: var(--fs-16);
  line-height: var(--lh-16);
  color: var(--c-muted);
}

@media (max-width: 899px) {
  .founders__head,
  .founders__grid,
  .media-band__head { width: auto; }
}

@media (min-width: 900px) {
  .founders__grid {
    grid-template-columns: 400fr 400fr;
    gap: 56px;
  }
  /* the stagger */
  .founders__grid > :nth-child(even) { margin-top: 100px; }
}

/* ---------- About: mission & vision ------------------------------------
   Rebuilt from the frame. The previous version was measured off a PNG render
   and got the band wrong in three ways at once: it washed the photograph to
   4% visibility, made the two cards solid dark with white copy, and gave each
   card its own half-photo. None of that is in the file.

     section            1440x750
     heading            y100  PP Editorial Old 400 28/42 #1E1F21, centred
     paragraph          y158  Plus Jakarta Sans 400 16/24 #6B6E76, 600 wide
     photograph         y250  1440x500, full bleed, NOT washed
     two cards          y408  400x184 at x298 and x742, 44 apart
                        fill transparent - they sit ON the photograph
     card title         +31   Plus Jakarta Sans 500 18/28 #1E1F21, centred
     card body          +74   Plus Jakarta Sans 400 16/24 #6B6E76, centred

   Everything is centred, and the copy is DARK because the photograph is pale
   where the cards land. */
.mission { position: relative; isolation: isolate; }
.mission__bg {
  position: absolute;
  left: 0;
  right: 0;
  bottom: 0;
  width: 100%;
  height: 66.67%;      /* 500 of 750 */
  object-fit: cover;
  z-index: -1;
}
.mission__head { text-align: center; max-width: none; margin-inline: auto; }
.mission__head .lede {
  margin: 16px auto 0;   /* 938 - 922 */
  max-width: 46.88%;     /* 600 of the 1280 container, not of the 1440 frame */
}

.mission__cards {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: 24px;
  margin-top: 40px;
}
.mission-card {
  position: relative;
  isolation: isolate;
  overflow: hidden;
  display: grid;
  align-content: start;
  text-align: center;
  /* Figma's GLASS effect is a FROSTED panel: it lightens AND blurs what is
     behind it, and that lightening is what makes the dark copy readable over a
     busy photo. A bare `backdrop-filter: blur()` only blurs - it does not
     lighten - so the cards read as unreadable as the picture. The panel carries
     a translucent white wash as well, which is the CSS stand-in for the frost. */
  background: rgba(255, 255, 255, .26);
  -webkit-backdrop-filter: blur(12px) saturate(1.05);
  backdrop-filter: blur(12px) saturate(1.05);
  color: var(--c-body);
}
/* No per-card photograph in the frame - one 1440x500 band photo carries the
   whole thing and the cards are transparent over it. Kept so a page that
   still has an image set does not break, but it is not the design. */
.mission-card__bg { display: none; }

@media (min-width: 900px) {
  /* The band is 1440x750 in the frame, and the cards are placed against it
     rather than flowed: x298/x742, y408, 400x184, 44 apart. Flowed they
     drifted with the heading's line count. */
  /* `.section` adds its own 100 top and bottom; the container carries the
     frame's 100 here, so the section's must go or the heading lands at 200. */
  .mission { aspect-ratio: 1440 / 750; overflow: hidden; padding-block: 0; }
  /* 100 of 1440, NOT of 750 - a percentage padding resolves against the
     containing block's WIDTH even in the block direction. */
  .mission > .container { padding-top: 6.94%; }
  .mission__cards {
    position: absolute;
    left: 20.69%;            /* 298 of 1440 */
    top: 54.40%;             /* 408 of 750 */
    width: 58.61%;           /* 844 of 1440 */
    grid-template-columns: 1fr 1fr;
    gap: 5.21%;              /* 44 of 844 */
    margin: 0;
  }
  .mission-card {
    aspect-ratio: 400 / 184;
    padding: 16.85% 6% 0;    /* 31 of 184, 24 of 400 */
    text-align: center;
  }
  /* `effects: [{ type: GLASS }]` on the card wrapper (117:14217). Rendered from
     the current file it is a clearly FROSTED white panel, not a near-invisible
     one - a stronger blur and the same white wash as the base rule. */
  .mission-card {
    background: rgba(255, 255, 255, .26);
    -webkit-backdrop-filter: blur(16px) saturate(1.05);
    backdrop-filter: blur(16px) saturate(1.05);
  }
}

.mission-card__title {
  margin: 0;
  font-size: var(--fs-18); line-height: var(--lh-18);
  font-weight: var(--fw-medium);
  color: var(--c-ink);       /* #1E1F21 - the cards are on a pale photo */
}
.mission-card__text {
  margin: 15px 0 0;          /* 1262 - 1247 */
  font-size: var(--fs-16); line-height: var(--lh-16);
  color: var(--c-body);      /* #6B6E76 */
}

/* ---- story: centred copy, then a photo edge to edge ---- */
/* ---------- About: our story --------------------------------------------
   Read off the frame (band y1530..2548, 1018 tall):

     y100  eyebrow    Plus Jakarta Sans 400 14/22 #7D818A, centred
     y122  title      PP Editorial Old 400 28/42 #1E1F21, centred
     y180  paragraph  Plus Jakarta Sans 400 16/24 #6B6E76, 863 wide
     y284  button     173x40, hairline #3B3D42, radius 2
     y368  photograph 1440x650, full bleed, closing the band

   Ours ran 834 against the frame's 1018 - the photo was 500 rather than 650,
   the paragraph column 660 rather than 863, and the gaps were guesses. */
.story { padding-bottom: 0; }
.story__head { text-align: center; }
.story__head .body {
  margin: 22px auto 0;   /* 1710 - 1688 */
  max-width: 863px;
}
.story__head .btn { margin-top: 34px; }   /* 1814 - 1780 */
.story__media { margin-top: 44px; }       /* 1898 - 1854 */
.story__image,
.story__video {
  display: block;
  width: 100%;
  /* 1440x650 in the frame. Stated as a ratio, not 650px: the band is full
     bleed, so a fixed height would drift from the frame's proportion the
     wider the window gets - the crop problem again. */
  aspect-ratio: 1440 / 650;
  height: auto;
  object-fit: cover;
}

/* ---- founder: copy beside the photo ---- */
/* Measured off d-about.png: the photo box is x 688..1282 and the copy runs
   from 160, so this band is inset 160 like the news listing rather than the
   usual 80 - copy 455, gap 73, photo 592. */
.ceo .container {
  display: grid;
  grid-template-columns: 455fr 592fr;
  gap: 73px;
  align-items: center;
}
/* The 160 inset is the desktop frame's and must stay there. Written global
   it left 55px of usable width at 390 - the section was crushed on every
   phone and tablet, and the page still reported no overflow, so nothing
   flagged it. */
@media (min-width: 1101px) {
  .ceo .container { padding-inline: 160px; }
}
.ceo__copy .body { margin-top: 16px; }
.ceo__image {
  display: block;
  width: 100%;
  height: auto;
  aspect-ratio: 592 / 458;   /* the frame's box, 1.29 not 1.48 */
  object-fit: cover;
}

/* ---- values: white text on a full-bleed photo ---- */
.values {
  position: relative;
  isolation: isolate;
  color: var(--c-bg);
  background: var(--c-dark-soft);   /* stands in until the photo arrives */
  text-align: center;
  padding-block: 80px 92px;         /* 3684 - 3604, and 4103 - 4011 */
}
.values__bg {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  object-fit: cover;
  z-index: -2;
}
/* No wash. The frame draws the photograph at full strength - `IMAGE
   533f3f75 op=1.00` and nothing over it. The 18% black here was worked out
   from the render, the same way the mission and craft washes were, and none
   of the three are in the file.

   Band y3604..4103, 499 tall:
     y80   title  PP Editorial Old 400 28/42 #F0F1F2, 781 wide, centred
     y178  row 1  titles 20/30, bodies 16/24 at y220
     y317  row 2  titles 20/30, bodies 16/24 at y359 */
.values__title { margin: 0 0 56px; color: var(--c-bg); }   /* 178 - 122 */
.values__grid {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: 25px 64px;      /* 317 - 292 */
  max-width: 781px;
  margin-inline: auto;
}
/* Each item's copy is capped at 274 and centred in its half. The frame's
   text boxes are auto-width - 274, 274, 283, 194 - which is the measured
   width of the words, not a column; what actually sets the band's height is
   that the first body wraps to three lines. Left to fill a 358 half it
   wrapped to two and the band came out 48 short. */
.values__grid > * {
  width: 100%;
  max-width: 274px;
  margin-inline: auto;
}
/* PP Editorial Old 400 20/30 #F0F1F2, body Plus Jakarta Sans 400 16/24 in
   the same colour - all four items, read off the values band (y3782/y3921).
   Ours was bold 18/28 with a dimmer body. */
.value__title {
  margin: 0;
  font-family: var(--font-display);
  font-weight: var(--fw-regular);
  font-size: var(--fs-20); line-height: var(--lh-20);
  color: var(--c-bg);
}
.value__text {
  margin: 12px 0 0;          /* 3824 - 3812 */
  font-size: var(--fs-16); line-height: var(--lh-16);
  color: var(--c-bg);
}

/* The three About eyebrows - "Our Story", "THE VISION BEHIND LORENZO" and
   "Craftsmanship" - are Plus Jakarta Sans 400 14/22 in the frame, a step up
   from the 12/18 the base `.eyebrow` sets. There was a desktop rule for this
   but it lived in the max-width:720 block, so only phones ever saw it - and
   it named `.ceo`, which this section stopped being called when it grew from
   one founder to two. */
@media (min-width: 721px) {
  .story .eyebrow,
  .founders__head .eyebrow,
  .craft .eyebrow {
    font-size: var(--fs-14);
    line-height: var(--lh-14);
    font-weight: var(--fw-regular);
  }
}

/* ---- craft: copy beside a five-photo mosaic ---- */
/* The frame puts a photograph behind this band, washed back the way the
   mission and values bands are. There was no field for it at all, so the
   section rendered on flat page background - Milad spotted it. */
/* ---------- About: craftsmanship ----------------------------------------
   Band y4103..5119, 1016 tall. Read off the frame:

     photograph  1440x1016 full bleed, fill opacity 0.05
     copy        x80  y279  287 wide - eyebrow, title, paragraph
     mosaic      x411 y150  949x716, three 306 columns and three 228 rows,
                 16 apart

   The photograph is drawn at FIVE PERCENT - that is a property of the fill,
   not a curtain laid over it. Ours painted the photo at full strength and
   then covered it with `rgba(240,241,242,.90)`, a figure worked out from the
   render. Same band, but the colour of the page shows through differently. */
.craft {
  position: relative;
  isolation: isolate;
  padding-block: 150px;   /* 4253 - 4103, and 5119 - 4969 */
}
.craft__bg {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  object-fit: cover;
  opacity: .05;
  z-index: -2;
}

/* Measured off d-about.png: the mosaic occupies x 411..1361 and the copy
   column runs from the 80 gutter, so it is copy 291 · gap 40 · mosaic 950.
   Ours was 300 · 48 · rest, which left the mosaic 33 short and the title
   wrapping where the frame keeps it on one line. */
.craft .container {
  display: grid;
  /* 287 : 44 : 949 - they add to the 1280 column exactly. Kept as a ratio
     rather than two fixed widths; as pixels they overflowed every viewport
     between 901 and 1400. */
  grid-template-columns: 287fr 949fr;
  gap: 44px;
  align-items: center;
}
.craft__copy .eyebrow { margin-bottom: 0; }   /* 4404 - 4382 - 22 = 0 */
.craft__copy .body { margin-top: 40px; }      /* 4528 - 4488 */

/* The arrangement is fixed and the slots are placed by name, so slot 3 is
   always the tall one on the left whatever order the files come back in. */
.craft-mosaic {
  display: grid;
  grid-template-columns: repeat(3, minmax(0, 1fr));
  grid-template-rows: repeat(3, 1fr);
  gap: 16px;                 /* 4497 - 4481, and 1054 - 1038 */
  aspect-ratio: 949 / 716;   /* three 306 columns, three 228 rows, 16 apart */
}
.craft-tile--1 { grid-column: 1 / span 2; grid-row: 1; }
.craft-tile--2 { grid-column: 3;          grid-row: 1 / span 2; }
.craft-tile--3 { grid-column: 1;          grid-row: 2 / span 2; }
.craft-tile--4 { grid-column: 2;          grid-row: 2; }
.craft-tile--5 { grid-column: 2 / span 2; grid-row: 3; }
.craft-tile { overflow: hidden; background: var(--c-warm-tint); min-width: 0; }
.craft-tile img { width: 100%; height: 100%; object-fit: cover; display: block; }


/* ===================================================================
   Feature cards + gallery  (Bespoke page)

   feature-cards: one photograph with translucent panels on it. The frame
   uses it twice - pale panels for "Designed Around Your Space", dark ones
   for "Wherever Your Project Begins". Panel pitch on the 390 frame is 159
   for the light set and 186 for the dark, which is the panel height plus
   the gap; both come out of the same box with different padding.
   =================================================================== */
.feature-cards {
  position: relative;
  isolation: isolate;
}
.feature-cards__bg {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  object-fit: cover;
  z-index: -2;
}
.feature-cards--dark { color: var(--c-bg); background: var(--c-dark-soft); }
.feature-cards--dark::before {
  content: "";
  position: absolute;
  inset: 0;
  z-index: -1;
  background: rgba(0, 0, 0, .35);
  pointer-events: none;
}

.feature-cards__head { text-align: center; max-width: 720px; margin-inline: auto; }
/* w400 in the frame - "WHY CHOOSING CUSTOM DESIGN" and "Design Intelligence"
   both read Plus Jakarta Sans 400 12/18 #7D818A. The base `.eyebrow` is 500. */
.feature-cards__head .eyebrow,
.media-band__head .eyebrow { font-weight: var(--fw-regular); }

.feature-cards__head .body { margin-top: 12px; }
.feature-cards--dark .feature-cards__head .body { color: var(--c-on-dark); }

.feature-cards__list {
  display: grid;
  gap: 8px;
  margin-top: 40px;
}
/* Both panels are **opaque** in the frame, not translucent. Sampled across
   all three panels of each band, sitting over different parts of the photo:
   the light one is a flat rgb(229,219,223) every time and the dark one a
   flat rgb(41,42,46), 78-83% of samples. A translucent panel over different
   photo areas could not read the same colour twice.
   We had them at .82 and .78 alpha, so the chandelier showed through every
   panel - and once the band's background photo was fixed to cover the whole
   section (it was rendering 488 of 837) that became much more visible. */
.feature-card {
  padding: 24px 20px;
  text-align: center;
  background: var(--c-panel-rose);
}
.feature-cards--dark .feature-card { background: var(--c-dark-soft); }

/* Phone frame: the light band's panels are a FROSTED GLASS over the chandelier
   photo behind them, not the solid rose the desktop band uses. Milad's note:
   "رنگ کارت‌ها متفاوت است، Effect Glass هم باید داشته باشند". Scoped to the
   phone (desktop keeps its rose panels). */
@media (max-width: 899px) {
  .feature-cards--light .feature-card {
    background: rgba(255, 255, 255, .28);
    -webkit-backdrop-filter: blur(16px) saturate(1.05);
    backdrop-filter: blur(16px) saturate(1.05);
  }
}

/* Plus Jakarta Sans 500 18/28 #1E1F21, body 400 16/24 - read off the light
   band's three panels. Ours was 16/24 medium with the body at 14/22, and both
   took the card's grey. The dark variant keeps its own light copy. */
.feature-card__title {
  margin: 0;
  font-size: var(--fs-18);
  line-height: var(--lh-18);
  font-weight: var(--fw-medium);
}
.feature-cards--dark .feature-card__title { font-weight: var(--fw-bold); }
.feature-cards--light .feature-card__title { color: var(--c-ink); }
.feature-card__text {
  margin: 8px 0 0;
  font-size: var(--fs-16);
  line-height: var(--lh-16);
}
.feature-cards--light .feature-card__text { color: var(--c-body); }
.feature-cards--dark .feature-card__text { color: var(--c-on-dark-soft); }

/* ===================================================================
   DESKTOP - measured off d-bespoke.png, not scaled up from the phone.

   These pages were built from the mobile frames only, so desktop was left to
   whatever the phone CSS did when it got wider: copy centred, panels stacked.
   The frame is a left copy column and a ROW of three panels.

   Panels 350x168 at x 160, 545 and 930, so 35 apart, inside a 160 inset.
   The photo is not a full-bleed background - it is a panel on the right that
   the cards overlap. It runs the full height of the band and ends 87 from the
   right edge, 610 wide. My first pass measured where the chandelier's dark
   strings are rather than where the image box is, and came out 520 wide
   starting 100 down - visibly thinner and shorter than the frame. The photo
   sits on white, so the visible content is smaller than the picture.

   This block sits AFTER the base rules on purpose. The first time I wrote it
   above them and `.feature-cards__list { gap: 8px }` came later and won, so
   the panels kept the phone's 8px gap at full width.
   =================================================================== */
@media (min-width: 900px) {
  .feature-cards .container { padding-inline: 160px; }
  .feature-cards__head {
    text-align: left;
    max-width: 560px;
    margin-inline: 0;
  }
  .feature-cards__list {
    grid-template-columns: repeat(3, 1fr);
    gap: 35px;
    margin-top: 57px;
  }
  /* title 28 + gap 8 + three lines of 22 + 33 top and bottom = 168 */
  .feature-card { padding: 33px 24px; }
  /* 100 below the photograph. The frame ends it at y1480 and opens the next
     section's eyebrow at y1680, and that section already carries 100 of its
     own - so this side owes 100, not the 199 left over from the crop-window
     pass, which was putting 323 between them. */
  .feature-cards--light {
    min-height: 0;
    padding-bottom: 100px;
    overflow: hidden;
  }

  /* The photograph is ONE node: `Frame 38401`, x80 y880, 1280x600, fill mode
     FILL - which is `object-fit: cover` and nothing more.

     An earlier pass decided the image was a 515x594 crop window with the
     chandelier hanging out of it on three sides, hid the <img> and painted a
     background at -383/-503 instead. That was wrong, and it is what Milad was
     looking at: a chandelier pinned to the right of the band with the real
     photograph nowhere. Read straight out of fig_full.json:

       node 101:13395  Frame 38401  x80 y880  1280x600  scaleMode FILL

     No crop, no window. The photo IS the band, and the copy and the panels
     sit on top of it. */

  .feature-cards--light > .container {
    position: relative;
    padding-block: 100px;   /* 980 - 880, and 1480 - 1380 */
  }
  /* `width` must be stated. On a replaced element `width: auto` resolves to
     the file's own width and the left/right pair is ignored. */
  .feature-cards--light .feature-cards__bg {
    display: block;
    position: absolute;
    top: 0;
    bottom: 0;
    left: var(--pad-80);
    width: calc(100% - 2 * var(--pad-80));
    height: 100%;
    object-fit: cover;
    z-index: -2;
  }

  /* The band must stay STATIC here. The <img> lives inside it, so making it a
     positioning context anchors the photo to the card row - 955x162, the size
     of the list - instead of to the container. Only the dark variant wants
     the band as the context, and it says so in its own rule. */
  .feature-cards--light .feature-cards__band { position: static; }
  .feature-cards--light .feature-cards__head { position: relative; }

  /* The head is a 572 column. Letting it run full width wrapped the paragraph
     to three lines instead of four, which pulled the cards up 57px and closed
     the photo to 546 instead of 600 - this copy sets the band's height.
     572 of the 1120 between x160 and x1280. */
  .feature-cards--light .feature-cards__head { max-width: 51.07%; }
  .feature-cards--light .feature-cards__list { margin-top: 60px; }   /* 1220 - 1160 */

  /* card 350x160, text at x+12 y+24. The fill is #500F28 at 15% OVER the
     photograph, so it has to stay translucent - flattening it against white
     is what made the panels read as solid pink. */
  .feature-cards--light .feature-card {
    padding: 24px 12px;
    min-height: 160px;
    background: rgba(80, 15, 40, .15);
  }
}


/* ---------- the booklet's submit ---------------------------------------
   Rendered by the mifa-forms shortcode, and that stylesheet loads AFTER the
   theme's - so these are scoped through `.booklet` to win on specificity
   rather than by order. Read off F-S07 (101:13350):

     button  x732 y296  178x40  fill #F0F1F2  radius 2
     label   Plus Jakarta Sans 500 16/24 #3B3D42

   The plugin's inline variant drops the label to 14/22 and paints the button
   in the deep accent with light text - the frame has it the other way round,
   a pale button with dark text, like the inner-page hero button. */
/* One row, not two. Read off F-S07 (101:13350):

     card    x316 y200  808x172   #212121 at 35%, GLASS
     title   x576 y236  PP Editorial Old 400 24/36 #F0F1F2
     input   x530 y296  195x40    hairline #F0F1F2, radius 2
     button  x732 y296  178x40    fill #F0F1F2, radius 2

   input + 7 + button = 380. The plugin's inline form is `flex-wrap: wrap`
   at 392 wide with the field stretching to 360, so the button had nowhere to
   go and dropped to a second line. Sizing the two tracks to the frame is
   what keeps them together. */
.booklet .mifa-form--inline {
  width: 380px;
  max-width: 100%;
  flex-wrap: nowrap;
  gap: 7px;                  /* 732 - 725 */
  justify-content: center;
  align-items: center;
}
.booklet .mifa-form--inline .mifa-form__grid {
  flex: 0 0 195px;
  width: 195px;
  min-width: 0;
}
.booklet .mifa-form--inline .mifa-field input { height: 40px; }
.booklet .mifa-form--inline .mifa-form__submit {
  flex: 0 0 178px;
  width: 178px;
  min-width: 0;
  height: 40px;
  margin-top: 0;
  /* 18 each side, not the plugin's 24: the label runs x750..892 inside a
     button at x732..910, and at 24 it no longer fitted on one line. */
  padding-inline: 18px;
  white-space: nowrap;
  border-radius: 2px;
  background: var(--c-btn-pale);
  color: var(--c-btn-pale-ink);
  font-size: var(--fs-16);
  line-height: var(--lh-16);
  font-weight: var(--fw-medium);
}
/* the note and any error sit under the row, not between the two controls */
.booklet .mifa-form--inline .mifa-form__note,
.booklet .mifa-form--inline .mifa-form__notice,
.booklet .mifa-form--inline .mifa-field__error { flex: 1 0 100%; text-align: center; }
.booklet .mifa-form--inline .mifa-form__submit:hover { background: #FFFFFF; }


/* --- gallery ------------------------------------------------------- */
/* One shown at a time. Three bands are worded differently on the 390 frame:
     collections   "Design That Makes Spaces Extraordinary" / "Design That Elevates Spaces"
     360           "Explore the Design Before It's Created" / "Explore Before It's Real"
     bespoke gal.  "Where Bespoke Design Comes to Life"     / "Bespoke Design, Brought to Life"
   Both go out in the markup and CSS picks; each is a heading, so hiding one
   costs nothing to fetch. */
.only-narrow,
/* Same idea as the gallery's phone title: the 390 frame runs its own, shorter
   copy here, so both are printed and CSS picks one. */
.feature-cards__text--mobile { display: none; }
.gallery__title--mobile { display: none; }
@media (max-width: 720px) {
  .only-wide,
  .gallery__title { display: none; }
  .only-narrow,
  .gallery__title--mobile { display: block; }
  /* only when a phone paragraph was actually written - `:has()` keeps the
     desktop one on screen for every band that has no phone variant. The
     phone paragraph deliberately does NOT carry `feature-cards__text`: with
     both classes it matched this rule too, at higher weight, and neither
     paragraph rendered. */
  .feature-cards__head:has(.feature-cards__text--mobile) .feature-cards__text { display: none; }
  .feature-cards__text--mobile { display: block; }
}


/* #F8F8F8, the same tint the steps band carries. The frame alternates:
   Bespoke runs white, F8F8F8 at y1580, white, F8F8F8 at y3470. Ours had no
   fill here, so two white bands ran together and the alternation was lost. */
.gallery { background: #F8F8F8; }
.gallery__head { text-align: center; }
/* #110E0B here, a shade deeper than the #1E1F21 every other heading uses. */
.gallery__head .h-section { color: #110E0B; }
.gallery__head .btn { margin-top: 24px; }
.gallery__grid {
  display: grid;
  grid-template-columns: repeat(3, 1fr);
  gap: 8px;
  margin-top: 40px;
}
.gallery__tile { overflow: hidden; background: var(--c-warm-tint); aspect-ratio: 1 / 1; min-width: 0; }
.gallery__tile img { width: 100%; height: 100%; object-fit: cover; display: block; }

/* ===================================================================
   DESKTOP - measured off d-bespoke.png.

   The frame runs a single head ROW here, the same shape as the homepage
   collections: title left, a rule across the middle, the button on the
   right. Below it a 2x2 grid of 549x349 tiles at x 161 and 729, rows at
   3633 and 3997 - so gaps of 19 across and 15 down, inside a 160 inset.

   Ours had a centred head with the button under it and a 3-wide grid of
   squares, because only the phone frame was ever built.
   =================================================================== */
@media (min-width: 900px) {
  .gallery .container { padding-inline: 160px; }
  .gallery__head {
    display: grid;
    grid-template-columns: auto minmax(40px, 1fr) auto;
    align-items: center;
    gap: 28px;
    text-align: left;
  }
  .gallery__head::after {
    content: "";
    order: 2;
    height: 1px;
    background: var(--c-line);
  }
  .gallery__head .h-section { order: 1; margin: 0; }
  .gallery__head .btn       { order: 3; margin-top: 0; }
  /* Measured off the frame: tiles at x 160..711 / 728..1279 and
     y 3636..3982 / 3999..4345 - so 552 x 347 with a 16px gutter both ways. */
  .gallery__grid {
    grid-template-columns: repeat(2, 1fr);
    gap: 16px;
    margin-top: 44px;
  }
  .gallery__tile { aspect-ratio: 552 / 347; }
}


/* ===================================================================
   Collection grid + project rail  (Lighting Collection page)

   Both measured off the 390 frame by scanning the render for where the
   tiles stop matching the section background - there is no text tree for
   this page and the API budget is spent.

     grid  columns x 20..191 and 199..370  -> two 171 columns, 8 apart
           rows 922..1150, 1186..1414, ... -> tile 228, pitch 264
           the name sits in the 36 between tiles, not on the photo
     rail  centre tile x 70..320 (250 wide), neighbours showing 42 each
           side, 8 apart, 300 tall
   =================================================================== */
.section--collections-grid .container { padding-inline: 20px; }
.collections__head { text-align: center; max-width: 720px; margin-inline: auto; }
.collections__head .body { margin-top: 12px; }

/* 100 above the heading and 100 below the last row. */
.section--collections-grid { padding-block: 100px; }

/* Band y780..2202, 1422 tall:
     y100  heading  "Our Collections" 28/42, centred
     y266  row 1    three 363x512 cards at x160, x539, x917
     y810  row 2    pitch 544, so 32 between the rows and 16 between columns
   The card is 512: a 363x480 photograph, 4, then the name at 18/28. */
.collection-grid {
  display: grid;
  grid-template-columns: repeat(3, minmax(0, 1fr));
  gap: 32px 16px;
  margin-top: 124px;   /* 266 - 142 */
}
.collection-grid .collection-card {
  position: static;
  display: block;
  height: auto;
  background: none;
  overflow: visible;
  /* A grid item defaults to min-width:auto, so it refuses to shrink below the
     intrinsic width of the photo inside it. With full-size uploads that made
     the two columns 265 wide inside a 390 screen and pushed the page out to
     558 - horizontal overflow with no element actually wider than the
     viewport, which is why a width scan came back clean. */
  min-width: 0;
}
.collection-grid .collection-card::after { display: none; }   /* no scrim under a caption that is not on the photo */
.collection-grid .collection-card img {
  width: 100%;
  aspect-ratio: 171 / 228;
  object-fit: cover;
  display: block;
}
.collection-grid .collection-card__meta {
  position: static;
  padding: 0;
  margin-top: 4px;   /* 750 - 746 */
}
/* w400 on the listing, where the homepage band's tiles are w500. Two
   different cards: the mosaic sets the name on the photograph in medium, the
   grid sets it underneath in regular. */
.collection-grid .collection-card__title {
  color: var(--c-ink);
  font-size: var(--fs-14);
  line-height: var(--lh-14);
  font-weight: var(--fw-regular);
}

/* ===================================================================
   Collections grid - DESKTOP, off d-lighting-collection.png.

   Everything above came from the 390 frame alone, so at full width the page
   was the phone layout stretched: the grid ran to 20px of the window edge
   and the tiles came out 410 wide against the frame's 363.

     container  the usual 160 inset - grid 160..1279
     columns    160..522 / 538..901 / 917..1279  -> 363 wide, 16 apart
     rows       1040..1519 / 1584..2063          -> 479 tall, pitch 544
     name       23 below the photo, so the item is 524 and the row gap 20
   =================================================================== */
@media (min-width: 900px) {
  .section--collections-grid .container { padding-inline: 160px; }

  /* The frame rules off the heading on both sides, the way the gallery head
     does on one. Centred, so the two rules are a matched pair. */
  .collections__head {
    max-width: none;
    display: grid;
    grid-template-columns: minmax(40px, 1fr) auto minmax(40px, 1fr);
    align-items: center;
    column-gap: 28px;
  }
  /* Rows stated, not left to auto-placement. Without them the pseudo-elements
     and the paragraph fought over the cells and the right-hand rule dropped
     to a row of its own. Frame: rules at 198..608 and 830..1243 inside a grid
     of 160..1279, so each is inset about 38 from its end. */
  .collections__head::before,
  .collections__head::after {
    content: "";
    height: 1px;
    background: var(--c-line);
    grid-row: 1;
  }
  .collections__head::before { grid-column: 1; margin-left: 38px; }
  .collections__head::after  { grid-column: 3; margin-right: 36px; }
  .collections__head .h-section { grid-column: 2; grid-row: 1; margin: 0; }
  .collections__head .body {
    grid-column: 1 / -1;
    grid-row: 2;
    max-width: 720px;
    margin: 12px auto 0;
  }

  .collection-grid {
    grid-template-columns: repeat(3, 1fr);
    gap: 20px 16px;
    margin-top: 44px;
  }
  .collection-grid .collection-card img { aspect-ratio: 363 / 479; }
  .collection-grid .collection-card__meta { margin-top: 23px; }
  /* 18/28 on the desktop frame; the 14/22 the base rule carries is the
     phone value. Card at x160 with the name at x168, so 8 in. */
  .collection-grid .collection-card__title {
    font-size: var(--fs-18);
    line-height: var(--lh-18);
    padding-inline-start: 8px;
  }
}

/* --- project rail --------------------------------------------------- */
/* #F0F1F2 in both frames - the band is a tinted one, not the page. */
.project-rail { background: var(--c-bg); }
.project-rail__head { text-align: center; max-width: 720px; margin-inline: auto; }
.project-rail__head .body { margin-top: 12px; }
/* 390 frame: title closes 96, the button opens 112 and is 32 tall at 14/22.
   Left to the site's default it was 40 tall at 16/24 and flush to the title,
   which pulled the whole rail 8 up and the band 8 short. */
@media (max-width: 720px) {
  .project-rail__head .btn {
    margin-top: 16px;
    height: 32px;
    padding-inline: 14px;
    font-size: var(--fs-14);
    line-height: var(--lh-14);
  }
}
.project-rail__track {
  display: flex;
  gap: 8px;
  margin-top: 32px;
  overflow-x: auto;
  scroll-snap-type: x mandatory;
  scrollbar-width: none;
  /* the first tile lands centred and its neighbours show at the edges */
  padding-inline: calc((100% - 250px) / 2);
}
.project-rail__track::-webkit-scrollbar { display: none; }
.project-rail__tile {
  flex: 0 0 250px;
  scroll-snap-align: center;
  display: block;
  overflow: hidden;
  background: var(--c-warm-tint);
}
.project-rail__tile img {
  width: 100%;
  height: 300px;
  object-fit: cover;
  display: block;
}

/* ===================================================================
   Project rail - DESKTOP.

   `padding-inline: calc((100% - 250px) / 2)` above centres ONE 250 tile,
   which is right on a phone and absurd at 1440: it put 595px of nothing to
   the left of the rail and pushed the tiles off the right edge. It was
   written without a media query, so it applied everywhere.

   Numbers now off the FILE (101:12126, 1440x636) rather than a PNG:

     head     100  28/42 left, "View All Collections" 186x40 on the right
     tiles    186  300x350, 8 apart, from x160 and running off the RIGHT
              only - the desktop rail is NOT pre-scrolled, the phone one is
     band     100 above the head and 100 under the tiles

   The old set said an 80 inset and 353-tall tiles, which put the rail 52
   low and the band 31 long.
   =================================================================== */
@media (min-width: 900px) {
  /* "Explore more collections" puts its title on the left of the 160 gutter
     and its button on the right, both on y100. "Our Projects" on the
     Collections page has no button and stays a centred stack - so the row
     is keyed on the button being there, not on the class. */
  .project-rail__head:has(.btn) {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 24px;
    max-width: none;
    margin-inline: 0;
    /* the head IS inside `.container`, which already gives 80 - the track is
       not, which is why that one carries the full 160 */
    padding-inline: 80px;
    text-align: left;
  }
  .project-rail__head:has(.btn) .h-section { margin: 0; }
  .project-rail__head .btn { flex: 0 0 auto; }
  .project-rail__track { padding-inline: 160px; margin-top: 44px; }
  .project-rail__tile { flex: 0 0 300px; }
  .project-rail .project-rail__tile img { height: 350px; }
}


/* ===================================================================
   Collection detail  (Collection-Pearl Wave frame)

   Measured off the render: heading line box at 760 i.e. 60 below the
   section top, body copy from 802, and the photographs that punctuate it
   are 250 tall on a 386 pitch. The wide photo under the copy is its own
   250-tall band running edge to edge.
   =================================================================== */
.collection__journey-head { text-align: center; }
.collection__journey .entry { margin-top: 12px; }
.collection__journey .entry img {
  display: block;
  width: 100%;
  height: 250px;
  object-fit: cover;
  margin: 32px 0;
}
.collection__journey .entry p { margin: 0 0 24px; }
/* the frame centres the standfirst under the ruled heading */
.collection__journey .entry { text-align: center; }
.collection__journey .entry p:last-child { margin-bottom: 0; }

/* ===================================================================
   Design Journey - the mosaic.  Measured off d-pearl-wave.png.

   The section was a heading over an empty div: it renders the post body and
   the body was never written, so the whole thing came out 248 tall with
   nothing in it.

   The frame is two columns of photograph-and-copy pairs, not a stack:

     pair 1   copy 165..365   photo 402..712 (y 1136..1438)
     pair 2   photo 135..545  copy 568..763  (y 1465..1826)
     pair 3   photo 741..1051 copy 1077..1264 (y 1056..1358)
     pair 4   photo 805..1208, copy UNDER it 846..1166

   So the left column reads copy-then-photo and then photo-then-copy, the
   right column photo-then-copy and then a photo with its copy beneath, and
   the right column starts 80 higher than the left.
   =================================================================== */
.journey { margin-top: 56px; }
.journey__pair { display: grid; gap: 20px; }
.journey__media img {
  display: block;
  width: 100%;
  height: 100%;
  object-fit: cover;
}
.journey__text {
  margin: 0;
  font-size: var(--fs-14);
  line-height: var(--lh-14);
  color: var(--c-body);
}

@media (min-width: 900px) {
  /* Read out of fig_full.json, not scanned off the render. My first pass
     measured the PNG and got 310x302 / 403x233; the file says 300x300 and
     350x350 - the scan was picking up shadow and bleed at the photo edges.
     All four collection frames share these exact positions:

       photo 1   x751  y1062  300x300     copy at x1075 y1084, w197
       photo 2   x402  y1142  300x300     copy at x168  y1186, w210
       photo 3   x815  y1390  350x350     copy at x559  y1531, w194
       photo 4   x135  y1470  400x400     copy at x830  y1764, w317

     It is a free composition rather than a grid, so each piece is placed
     against the 1440 frame as a percentage and the whole thing scales. */
  .journey {
    position: relative;
    height: 0;
    padding-bottom: 62.8%;      /* 905 of 1440: y1062 to y1967 */
    /* standfirst closes y1018, the first photo opens y1062 -> 44 between.
       It was 0, so the mosaic hugged the text. */
    margin-top: 44px;
    padding-inline: 0;
    display: block;
  }
  .journey__col { display: contents; }
  .journey__pair { position: absolute; display: block; }
  .journey__media { margin: 0; }

  .journey__pair--1 { left: 52.15%; top: 0;      width: 20.83%; }
  .journey__pair--1 .journey__media { aspect-ratio: 1; }
  .journey__pair--2 { left: 27.92%; top: 8.84%;  width: 20.83%; }
  .journey__pair--2 .journey__media { aspect-ratio: 1; }
  .journey__pair--3 { left: 56.60%; top: 36.24%; width: 24.31%; }
  .journey__pair--3 .journey__media { aspect-ratio: 1; }
  .journey__pair--4 { left:  9.38%; top: 45.08%; width: 27.78%; }
  .journey__pair--4 .journey__media { aspect-ratio: 1; }

  /* The copy does not sit with its photo - each block is placed on its own.
     Positions are relative to the same 1440 x 905 stage. */
  .journey__pair .journey__copy { position: absolute; }
  /* left/top are percentages of the PAIR, which is the containing block -
     not of the stage. Pairs 1 and 2 were first written against the stage and
     landed 141px and 29px out; 3 and 4 were right. Recomputed from the file:
       p1  (1075-751)/300 = 108.00%   (1084-1062)/300 =  7.33%
       p2  (168 -402)/300 = -78.00%   (1186-1142)/300 = 14.67% */
  .journey__pair--1 .journey__copy { left: 108.00%; top:  7.33%; width: 65.7%; }
  .journey__pair--2 .journey__copy { left: -78.00%; top: 14.67%; width: 70.0%; }
  .journey__pair--3 .journey__copy { left: -73.1%; top: 40.3%; width: 55.4%; }
  .journey__pair--4 .journey__copy { left: 173.8%; top: 73.5%; width: 79.3%; }

  .journey__head {
    margin: 0 0 8px;
    font-family: var(--font-display);
    font-weight: var(--fw-regular);
    font-size: var(--fs-18);
    line-height: var(--lh-18);
    color: var(--c-ink);
  }
}

/* A phone has one column, so the pairs simply follow one another with the
   photo above its copy - the 390 frame does the same. */
@media (max-width: 899px) {
  /* The mosaic sits outside .container, so it has to carry the gutter
     itself - the 390 frame runs its photos 20..369, not edge to edge.
     Everything the desktop rule positions absolutely is unwound here. */
  .journey {
    display: grid;
    gap: 32px;
    /* standfirst closes 240, the first photo opens 272 */
    margin-top: 32px;
    padding-inline: 20px;
    position: static;
    height: auto;
    padding-bottom: 0;
  }
  .journey__col { display: grid; gap: 32px; }
  .journey__pair { position: static; gap: 16px; }
  .journey__pair .journey__copy { position: static; width: auto; }
  /* 350x250 in the 390 frame, so state it rather than letting a square file
     decide - it was landing 252 tall. */
  .journey__media { aspect-ratio: 7 / 5; }
  .journey__media img { aspect-ratio: 7 / 5; object-fit: cover; }
  /* The phone frame's step heading is the same grey as its paragraph and only
     16/24 - it reads as a label above the copy, not as a title. We had it in
     display type at 18/28 in full ink, which is the DESKTOP treatment. */
  .journey__head {
    margin: 0 0 8px;
    font-family: inherit;
    font-weight: var(--fw-regular);
    font-size: var(--fs-16);
    line-height: var(--lh-16);
    color: var(--c-body);
  }
  /* the standfirst is 14/22 here, not the 16/24 an .entry gets by default -
     six lines of it, which is what closes the head block on 240 */
  .collection__journey .entry,
  .collection__journey .entry p {
    font-size: var(--fs-14);
    line-height: var(--lh-14);
  }
  /* The band opens on 60 and closes on 80 - not the same both ends. Two
     classes deep because `.section { padding-block: 60px }` is written LATER
     in this file at the same weight and would win on source order. */
  .section.collection__journey { padding-block: 60px 80px; }
}

/* The frame rules the heading off to both edges - a line, the title, a line.
   Drawn on the head rather than as extra markup so the title stays a plain
   h2 for a screen reader. */
.collection__journey-head {
  display: grid;
  grid-template-columns: 1fr auto 1fr;
  align-items: center;
  gap: 24px;
}
.collection__journey-head::before,
.collection__journey-head::after {
  content: "";
  height: 1px;
  background: var(--c-line);
}
.collection__journey-head .h-section { margin: 0; }
@media (max-width: 720px) {
  /* no room for rules beside a wrapped title on a phone */
  .collection__journey-head { display: block; text-align: center; }
  .collection__journey-head::before,
  .collection__journey-head::after { display: none; }
}

.collection__banner { margin: 0; }
/* 250 is the 390 frame's height and it was written globally, so the desktop
   band was a 250-tall letterbox against the frame's 650 of a 1440 width.
   Ratio above 900, flat height below - the same shape of fix as the CEO
   photo and the rail tiles. */
.collection__banner img.collection__banner-img {
  display: block;
  width: 100%;
  height: 250px;
  object-fit: cover;
}
@media (min-width: 900px) {
  .collection__banner img.collection__banner-img {
    height: auto;
    aspect-ratio: 1440 / 650;
  }
}

/* the name sits on the tile in this rail, unlike the projects one */
/* The name is a SOLID BAR across the foot of the tile - 40 tall, #212121,
   16/24 #F0F1F2 centred - and it is drawn that way in the 1440 frame and the
   390 one alike (Frame 38174, 300x40 and 250x40). We had it as floating
   display type over a gradient, which is the projects rail's treatment on the
   homepage, not this one's. */
.project-rail__tile { position: relative; }
.project-rail__label {
  position: absolute;
  left: 0; right: 0; bottom: 0;
  display: grid;
  place-items: center;
  height: 40px;
  /* #212121 at 35%, not solid - the frame lets the photograph through. */
  background: rgba(33, 33, 33, .35);
  text-align: center;
  font-size: var(--fs-16);
  line-height: var(--lh-16);
  color: var(--c-bg);   /* #F0F1F2 */
  z-index: 1;
}


/* ===================================================================
   Contact details  (Contact page)

   Off the 390 frame: section 750 with 60/28 padding, title 760, paragraph
   806, the details heading 984, then the three lines on a 72 pitch, "Follow
   us" 1306 and the icons 1350. The photo behind it is washed almost out,
   the same treatment as the About mission band.
   =================================================================== */
/* The photo and its wash sit on the CARD, not on the section - see the note
   in the template. The intro above it is on plain page background. */
.contact-details__card {
  position: relative;
  isolation: isolate;
}
.contact-details__bg {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  object-fit: cover;
  z-index: -2;
  /* The frame does not put a wash OVER the photograph - it turns the
     photograph itself down: 20% on the desktop card, 15% on the phone. We
     had a 94% white sheet on top of a full-strength picture, which works out
     at about 6% and read as an empty grey panel. */
  opacity: .2;
}

/* The frame centres the intro on desktop and sets it left on a phone. */
.contact-details__intro { text-align: center; }
.contact-details__intro .body { margin-top: 12px; max-width: 900px; margin-inline: auto; }
/* The card is inset a further 14 from the section gutter: the frame puts the
   icons at x=42 against a 28 padding, and "Follow us" and the social squares
   line up with them. */
.contact-details__card {
  margin-top: 46px;
  padding: 56px 48px;
  max-width: 552px;          /* the frame's card, x 168..720 in a 1440 frame */
}
/* On a phone the card is not a bounded panel: in m-contact.png its
   PHOTOGRAPH runs edge to edge behind the copy, while the copy keeps the
   section's gutter. Ours had the photo bounded by the container, so it
   stopped 28 short of each edge and read as a panel.

   The negative margin takes the card - and therefore the absolutely
   positioned photo and its wash - out to the screen edges; the padding puts
   the copy back where the frame has it, 42 in, which is where the frame
   lines the icons up against its 28 gutter. */
@media (max-width: 720px) {
  .contact-details__card {
    max-width: none;
    margin-inline: -28px;
    padding: 32px 28px;
  }
  /* The frame does not indent everything equally: the heading sits on the
     gutter at 28 and only the list, "Follow us" and the icons step in to 42.
     Indenting the card as a whole put the heading 14 too far in. */
  /* Two classes deep on purpose: `.contact-details__list { margin: 0 }` and
     the follow paragraph's own margin are both written LATER in this file,
     and a shorthand at the same weight would win on source order. */
  .contact-details__card .contact-details__list,
  .contact-details__card .contact-details__follow,
  .contact-details__card .contact-details__social { margin-inline-start: 14px; }
}

/* Under the address, indented to the address text rather than the icon:
   the frame puts the address at x293 and this at x320. 16/24 medium #6B6E76.

   This rule and its :hover had been swallowed INTO the phone media query by a
   comment that opened mid-selector, so on desktop the link inherited the
   list's #1E1F21 and lost its own size. */
.contact-details__map {
  display: block;
  margin-top: 6px;
  font-size: var(--fs-16);
  line-height: var(--lh-16);
  font-weight: var(--fw-medium);
  color: var(--c-muted);
  text-decoration: underline;
  text-underline-offset: 3px;
  /* The address and this link are both inline inside one span, so the link
     was flowing on after the last word of the address instead of starting
     its own line - which is where the frame puts it. `fit-content` keeps the
     underline the width of the words. */
  width: fit-content;
}
.contact-details__map:hover { color: var(--c-ink); }
.contact-details__heading { margin: 0 0 26px; }

.contact-details__list {
  list-style: none;
  margin: 0;
  padding: 0;
  display: grid;
  /* items run 1047 · 1119 · 1181 on the frame and the block closes at 1229,
     which is 86 of gap across two gaps */
  gap: 43px;
}
.contact-details__item {
  display: grid;
  grid-template-columns: 24px 1fr;
  align-items: start;
  gap: 22px;   /* icon at 42, text at 88 */
  font-size: var(--fs-16);
  line-height: var(--lh-16);
  color: var(--c-ink);
}
.contact-details__item svg { width: 22px; height: 22px; }
.contact-details__item a { color: inherit; }
.contact-details__item a:hover { color: var(--c-accent); }

.contact-details__follow {
  margin: 77px 0 14px;   /* address closes 1229, "Follow us" 1306 */
  font-size: var(--fs-16);
  line-height: var(--lh-16);
  color: var(--c-body);
}
.contact-details__social { display: flex; gap: 12px; }
.contact-details__social a {
  width: 40px;
  height: 40px;
  display: grid;
  place-items: center;
  background: var(--c-dark-soft);
  color: var(--c-bg);
}
.contact-details__social a:hover { background: var(--c-ink); }


/* ===================================================================
   News listing  (News frame)

   Off the 390 frame, section padding 80/20:
   Numbers below are the FILE's (101:7252, 390x1708), measured from the
   band's own top - the earlier set here came off a PNG render and was
   wrong about the two things a render cannot show precisely:

     heading  80  24/36 · paragraph 128 14/22 · search 204 (350x36,
     #F8F8F8 on a #DDDEE1 line) · chips on a 40 pitch from 264, each 32
     tall and only as wide as its words · cards from 528, 350x250 with 12
     between them · "See more" 1588, 110x40, plain text 16/24 #6B6E76.
   =================================================================== */
.news-listing__head .body { margin-top: 12px; }

.news-search {
  display: flex;
  align-items: center;
  gap: 6px;
  height: 36px;
  margin-top: 32px;
  padding-inline: 13px;
  border: 1px solid #DDDEE1;
  background: #F8F8F8;
  color: var(--c-body);
}
/* 14, not 18: the frame's magnifier is 14x14 at x33, which is what puts the
   placeholder on x53. */
.news-search svg { width: 14px; height: 14px; flex: 0 0 auto; }
.news-search input {
  flex: 1;
  min-width: 0;
  border: 0;
  background: none;
  font: inherit;
  font-size: var(--fs-14);
  line-height: var(--lh-14);
  color: inherit;
}
.news-search input:focus { outline: none; }

/* Chips, not full-width rows: every tab in the frame is exactly as wide as
   its own words plus 14 either side (All = 14+16+14 = 44). They stack on a
   phone and run in a line on desktop, 8 apart either way. */
.news-filter {
  display: flex;
  flex-direction: column;
  align-items: flex-start;
  gap: 8px;
  margin-top: 24px;
}
.news-filter__item {
  display: inline-flex;
  align-items: center;
  height: 32px;
  padding-inline: 14px;
  font-size: var(--fs-14);
  line-height: var(--lh-14);
  color: var(--c-body);
}
.news-filter__item.is-active {
  background: #DDDEE1;
  color: var(--c-ink);
}

@media (min-width: 900px) {
  .news-filter { flex-direction: row; flex-wrap: wrap; }
}

.news-list { display: grid; gap: 12px; margin-top: 32px; }

/* The phone's four-card cap - see initNewsMore() in theme.js. The class is
   only ever added by that function, so no JavaScript means no cap. */
@media (max-width: 720px) {
  .news-list.is-capped > :nth-child(n + 5) { display: none; }
}


/* The listing is a three-column grid on desktop, not a stack. Measured off
   d-news.png at y=1300: the cards run 160..523, 538..902, 917..1280 - so
   three columns of 363 with a 15 gap inside a 1120 container, i.e. this
   section is inset 160 rather than the usual 80.

   The staggered look is real, not decoration: the first card of each six is
   double height and so is the fourth, which is what pushes the second column
   down and gives the design its rhythm. With auto-placement those two spans
   reproduce it exactly.

   `:not(.news-list--rail)` because the article page reuses this class for a
   sideways rail. */
@media (min-width: 900px) {
  .news-listing__main .container { padding-inline: 160px; }

  /* The desktop band (101:12982, 1440x1655) pads 150, not the site's usual
     100: heading 150 · standfirst 204 · search 260 · chips 332 · cards 408,
     and "See more" closes on 1505 with the same 150 underneath. */
  .news-listing__main { padding-block: 150px; }
  .news-search { margin-top: 32px; }
  .news-filter { margin-top: 36px; }
  .news-list { margin-top: 44px; }

  .news-list:not(.news-list--rail) {
    grid-template-columns: repeat(3, 1fr);
    gap: 16px;
    grid-auto-rows: 329px;
  }
  .news-list:not(.news-list--rail) .news-card {
    aspect-ratio: auto;
    height: 100%;
  }
  /* `:not(:last-child)` matters with the frame's seven articles: 6n+1 also
     picks the seventh, which made the last column run a row deeper than the
     other two and left the block with a ragged foot. The frame has exactly
     two tall cards and a level bottom edge. A tall card at the very end has
     nothing beside it to justify its height. */
  .news-list:not(.news-list--rail) .news-card:nth-child(6n+1):not(:last-child),
  .news-list:not(.news-list--rail) .news-card:nth-child(6n+4):not(:last-child) {
    grid-row: span 2;
  }
}

.news-card {
  position: relative;
  display: block;
  overflow: hidden;
  aspect-ratio: 350 / 250;
  background: var(--c-dark-soft);
  color: var(--c-bg);
  isolation: isolate;
}
.news-card__img {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  object-fit: cover;
  z-index: -2;
  transition: transform .8s var(--ease);
}
.news-card:hover .news-card__img { transform: scale(1.04); }
/* the copy sits on the photo, so it needs its own shading to stay legible */
.news-card::after {
  content: "";
  position: absolute;
  inset: auto 0 0 0;
  height: 70%;
  z-index: -1;
  background: linear-gradient(180deg, transparent, rgba(0, 0, 0, .68));
  pointer-events: none;
}
.news-card__body {
  position: absolute;
  left: 16px;
  right: 16px;
  bottom: 16px;
  display: flex;
  flex-direction: column;
  /* 4 between the title and the excerpt, 8 before the meta line */
  gap: 4px;
}
.news-card__title {
  font-family: var(--font-display);
  font-weight: var(--fw-bold);
  font-size: var(--fs-20);
  line-height: var(--lh-20);
}
.news-card__excerpt {
  font-size: var(--fs-14);
  line-height: var(--lh-14);
  color: var(--c-on-dark);
}
.news-card__meta {
  display: flex;
  flex-wrap: wrap;
  gap: 6px;
  margin-top: 4px;
  font-size: var(--fs-12);
  line-height: var(--lh-12);
  color: var(--c-on-dark);
}

/* The frame's button carries no fill and no stroke on either breakpoint -
   it is a plain muted text link, 16/24 on a phone and 18/28 on desktop. */
.news-list__more { margin-top: 24px; text-align: center; }
.news-list__more a,
.news-list__reveal {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  height: 40px;
  padding-inline: 18px;
  border: 0;
  background: none;
  font: inherit;
  font-size: var(--fs-16);
  line-height: var(--lh-16);
  font-weight: var(--fw-medium);
  color: var(--c-body);   /* #6B6E76 */
  cursor: pointer;
}
.news-list__more a:hover,
.news-list__reveal:hover { color: var(--c-ink); }
/* `display: inline-flex` above beats the browser's own rule for the `hidden`
   attribute, so without this the button showed before theme.js had capped
   anything - and stayed on screen after it was clicked. */
.news-list__reveal[hidden] { display: none; }

@media (min-width: 900px) {
  .news-list__more { margin-top: 32px; }
  .news-list__more a,
  .news-list__reveal {
    height: 48px;
    padding-inline: 22px;
    font-size: var(--fs-18);
    line-height: var(--lh-18);
  }
}

/* The four-card cap is a phone decision, so the button that lifts it is one
   too - the desktop frame's "See more" belongs to real pagination. Written
   AFTER the display rule above and one class deeper, because at equal weight
   `display: inline-flex` would win on source order and the button stayed
   visible at 1440. */
@media (min-width: 721px) {
  .news-list__more .news-list__reveal { display: none; }
}

/* --- Stay Connected -------------------------------------------------
   Off the FILE (101:7318, 390x675): the band is #F8F8F8 with 80 of padding,
   photo 350x323 on 80, heading 447 24/36, copy 495 14/22, then one 32-tall
   row on 563 - a 195 field and a 97 #6B6E76 button, 8 apart and LEFT
   aligned, not stretched across the column. */
.newsletter { background: #F8F8F8; }
.newsletter__media img {
  display: block;
  width: 100%;
  height: 323px;
  object-fit: cover;
}
/* photo closes 403, heading ink 447 -> 44 between */
.newsletter__body { margin-top: 44px; }
.newsletter__body .body { margin-top: 12px; }
.newsletter__body .mifa-form { margin-top: 24px; }
/* the frame puts the field and the button on one row, 32 tall, like the
   booklet band - left to itself the plugin stacks them and the section runs
   about 48 long */
/* wrap, not nowrap: the plugin puts a note (and, after a send, a notice) under
   the button as a full-width row. With nowrap that row sat BESIDE the button,
   crushed the email box to 0 and pushed the phone page to 468px wide. The
   field and the button still share one row - they fit in 225px. */
.newsletter__body .mifa-form--inline {
  flex-direction: row;
  flex-wrap: wrap;
  align-items: flex-start;
  justify-content: flex-start;
  gap: 8px;
}
.newsletter__body .mifa-form--inline .mifa-form__grid { flex: 1 1 120px; max-width: 195px; min-width: 0; }
.newsletter__body .mifa-form--inline .mifa-form__submit { flex: 0 0 97px; min-width: 0; }
/* 32, not the 40 every other field on the site uses */
.newsletter__body .mifa-form--inline .mifa-field input,
.newsletter__body .mifa-form--inline .mifa-form__submit {
  height: 32px;
}
.newsletter__body .mifa-form--inline .mifa-field input { padding-inline: 22px; }
.newsletter__body .mifa-form--inline .mifa-form__submit {
  padding-inline: 14px;
  background: var(--c-btn-grey);   /* #6B6E76, not the eyebrow grey */
  font-size: var(--fs-14);
  line-height: var(--lh-14);
}

/* On desktop the band is TWO COLUMNS, not a stack: the 1440 frame puts the
   photo at x240 (560x323) and the copy at x900 (300 wide), which is 240 of
   gutter either side of a 960 content strip. Stacked, the band ran 701
   against the frame's 523.

     photo  100   560x323
     title  164   28/42
     copy   218   16/24, three lines
     field  326   the same 32-tall row as the phone */
@media (min-width: 900px) {
  .newsletter .container {
    display: grid;
    grid-template-columns: 560fr 100fr 300fr;
    align-items: start;
    padding-inline: 16.67%;      /* 240 of 1440 */
  }
  .newsletter__media { grid-column: 1; }
  .newsletter__body  { grid-column: 3; margin-top: 64px; }   /* photo 100, title 164 */
  .newsletter__body .body { margin-top: 12px; }
  .newsletter__body .mifa-form { margin-top: 36px; }         /* copy closes 290, row 326 */
}


/* --- article: body, read-more rail --------------------------------
   Off the News-Vela frame (390): title 780 with the section padding 80,
   copy from 840, then "Read more News" at 2078 with its own 60 padding and
   a rail of cards 280 wide and 250 tall, 8 apart, 27 of the next showing. */
.article__title { text-align: center; }
/* The frame centres the body copy too, not just the title - all three
   paragraphs are centred under it. Pearl Wave's journey deliberately is not:
   there the first paragraph is centred and the one after the photo is left,
   so that section keeps its own alignment. */
.article__body .entry { margin-top: 12px; text-align: center; }
.article__body .entry img {
  display: block;
  width: 100%;
  height: auto;
  margin: 24px 0;
}
.article__more-head { text-align: center; }
.article__more-head .btn { margin-top: 24px; }
/* The News-Vela frame runs this head as one row: "Read more News" left and
   "View All News" at the right edge on the same baseline, cards below. On a
   phone it stays the centred stack above. */
@media (min-width: 900px) {
  .article__more-head {
    display: flex;
    align-items: center;
    justify-content: space-between;
    text-align: left;
    gap: 24px;
  }
  .article__more-head .btn { margin-top: 0; flex: none; }
}

/* Same card as the listing, different container - a rail instead of a stack. */
.news-list--rail {
  display: flex;
  gap: 8px;
  margin-top: 40px;
  overflow-x: auto;
  scroll-snap-type: x mandatory;
  scrollbar-width: none;
  padding-inline: calc((100% - 280px) / 2);
}
.news-list--rail::-webkit-scrollbar { display: none; }
.news-list--rail .news-card {
  flex: 0 0 280px;
  aspect-ratio: 280 / 250;
  scroll-snap-align: center;
}
.article__more .h-section { margin: 0; }
.hero__meta {
  margin: 16px 0 0;
  display: flex;
  flex-wrap: wrap;
  gap: 6px;
  font-size: var(--fs-14);
  line-height: var(--lh-14);
  color: var(--c-on-dark);
}


/* --- inner-page hero -----------------------------------------------
   The frames give every page except the homepage the same hero: a
   breadcrumb under the nav, then the title and one paragraph centred low
   on the photo, and no buttons. Measured off the About frame (390x700):
   breadcrumb baseline 104, title 469, dots 632.

   This block sits BEFORE the Responsive section on purpose. Placed after
   it, these rules would beat the 720 overrides that share their
   specificity - the exact mistake this file's Responsive header warns
   about, and one I made again while writing it. */
.hero--inner .container {
  display: flex;
  flex-direction: column;
  justify-content: flex-end;
}
/* Two inner-page heroes in the frames: About centres its copy low on the
   photo with no eyebrow (title 24/36); Bespoke sets it left and adds one
   (title 28/42). Which one is a per-page field, not something derivable. */
.hero--center .container { text-align: center; }
/* About's desktop hero sits the copy in the upper third, not low on the photo
   the way its mobile frame does. Measured off d-about.png: the title's glyph
   band is 262..284 and the lede's two lines 321..331 and 349..359, so the
   title box starts 254 and the lede 317. Ours was bottom-anchored and landed
   at 534/592 - about 280 too low, which is why the copy sat over the faces
   instead of above them.
   Mobile keeps its own placement; the 720 block below overrides this. */
.hero--center .container {
  justify-content: flex-start;
  padding-block: 254px 0;
}
.hero--center .hero__inner { padding-bottom: 0; }
.hero--left .container   { text-align: left; }
.hero--left .hero__lede  { margin-inline: 0; }
.hero--inner .hero__inner {
  position: static;
  left: auto;
  top: auto;
  width: 100%;
  max-width: 720px;
  margin-inline: auto;
  padding-bottom: 96px;        /* clears the slider dots */
}
/* 170, not 96, wherever the hero carries a button. Every frame that has one -
   home, Bespoke, Lighting Collection - pins the button row at y570 and the
   dots at y666 in a 780 hero, so the copy has to finish 170 above the floor.
   At 96 the whole block sat 74 low and the dots ended up touching the button,
   which is what Milad was pointing at. */
.hero--has-actions .hero__inner { padding-bottom: 170px; }

/* Above 1440 the hero grows with the window, so these two offsets grow with
   it too - in px they stayed put and the copy drifted away from the floor.
   170 and 100 of a 1440-wide frame. */
@media (min-width: 1440px) {
  .hero--has-actions .hero__inner { padding-bottom: 11.81vw; }
  .hero__dots { bottom: 6.94vw; }
}

/* This has to sit AFTER `.hero--inner .hero__inner`, not before. Both are
   (0,2,0), so order decides, and the centring rule was winning - Bespoke's
   hero copy sat at x=272 with a 720 column instead of on the 80 gutter
   with a 560 one. Same specificity, different outcome, no error anywhere. */
.hero--left .hero__inner { margin-inline: 0; max-width: 560px; }
.hero--inner .hero__title { max-width: none; }
.hero--inner .hero__lede { margin-inline: auto; max-width: 620px; }

.hero__crumbs {
  position: absolute;
  top: 120px;                  /* directly under the 120-tall nav bar */
  left: 80px;
  display: flex;
  align-items: center;
  gap: 10px;
  color: var(--c-on-dark);
  font-size: var(--fs-14);
  line-height: var(--lh-14);
  z-index: 2;
}
.hero__home { display: inline-flex; align-items: center; color: inherit; }
.hero__home svg { width: 22px; height: 22px; display: block; }
.hero__home:hover { color: var(--c-bg); }
.hero__crumb-sep { opacity: .7; }


/* ===================================================================
   Responsive

   Every breakpoint in the theme lives here, at the very end of the last
   stylesheet, and nowhere else.

   The reason is a rule people get wrong: a media query does NOT raise a
   selector's specificity. `@media { .head-row { ... } }` in theme.css and
   a plain `.head-row { ... }` in layout.css weigh exactly the same, so the
   one that appears later wins - and layout.css loads after theme.css. That
   is how the collections header kept its desktop three-column grid on a
   phone and pushed the page 190px wider than the screen.

   Keeping them last means a breakpoint always beats the base rule it is
   meant to override, whichever file that rule came from.
   =================================================================== */

@media screen and (max-width: 782px) {
  .admin-bar .site-header--sticky { top: 46px; }
}

@media (max-width: 720px) {
  /* a background video on a phone costs data and battery for very little,
     so it is opt-in per site rather than the default */
  .hero__video[data-mobile="0"] { display: none; }
  .hero__video[data-mobile="0"] + .hero__poster { display: block; }
}

@media (max-width: 720px) {
  .header-cta { display: none; }
}

@media (prefers-reduced-motion: reduce) {
  .hero__slide { transition: none; }
}

@media (prefers-reduced-motion: reduce) {
  .viewer__grab { transition: none; }
}

@media (max-width: 720px) {
  /* the :nth-child selectors above are more specific, so the reset has to
     match them or the fourth card keeps its 420ms wait */
  .steps .step,
  .steps .step:nth-child(2),
  .steps .step:nth-child(3),
  .steps .step:nth-child(4) { transform: translateY(24px); transition-delay: 0s; }
}

@media (max-width: 900px) {
  .booklet { padding-block: 100px 80px; }
  .booklet__card { padding: 28px 24px; }
}

@media (prefers-reduced-motion: reduce) {
  .reveal { opacity: 1; transform: none; transition: none; }
  .collection-card:hover img, .project-tile:hover img { transform: none; }
}

@media (max-width: 1100px) {
  .process .container { padding-inline: var(--pad-80); }
  .steps           { grid-template-columns: repeat(2, 1fr); }
  .stats .container{ grid-template-columns: repeat(2, 1fr); gap: 48px; padding-block: 72px; }
  .contact .container { grid-template-columns: 1fr; }
  /* the logo column drops away and the four columns become two */
  .footer__top  { grid-template-columns: 1fr; gap: 40px; }
  .footer__cols { grid-template-columns: 1fr 1fr; gap: 32px 48px; }
  .footer__follow { margin-top: 28px; }
}

@media (max-width: 720px) {
  /* the nav's mobile behaviour lives in the overlay block at the foot */
  .hero { min-height: 560px; }
  .steps { grid-template-columns: 1fr; }
  .stats .container { grid-template-columns: 1fr; }
  .projects__head { flex-direction: column; align-items: flex-start; }
  .site-footer .container { padding-inline: 20px; }
  .footer__social a { width: 40px; height: 40px; }

  /* Footer, 832 on the frame, read off its auto-layout end to end:
     pad 60 · logo 26 · 40 · links 232 · 44 · cta 66 · 44 · contact 147 ·
     44 · follow 71 · 12 · copyright 46, and nothing at the foot.

     Two things the +4 total was hiding. The frame puts **"Follow us" after
     the contact block**, at the bottom of the stack; our markup nests it
     beside the logo, so it was rendering second. `.footer__brand` goes
     `display: contents` here and the logo, the columns and the follow block
     become orderable siblings of `.footer__top` - no markup change, and
     desktop keeps the brand column it was written for.

     And the six links are one continuous list of 32-tall rows **8 apart**
     (6·32 + 5·8 = 232). Ours are two menus with no gap at all inside them,
     so the block read 40 short while every row was the right height. */
  .site-footer { padding-block: 60px 0; }
  .footer__logo .brand__mark svg,
  .footer__logo .brand__mark img,
  .footer__logo img { height: 26px; }

  .footer__brand { display: contents; }
  .footer__top   { display: flex; flex-direction: column; gap: 0; }
  .footer__logo   { order: 1; }
  .footer__cols   { order: 2; margin-top: 40px; }
  .footer__follow { order: 3; margin-top: 44px; }

  /* the gaps inside the column stack are not uniform - 8 between the two
     menus so they read as one list, then 44 to each block below */
  .footer__cols { display: block; }
  .footer__links + .footer__links { margin-top: 8px; }
  .footer__links li + li { margin-top: 8px; }
  .footer__cta     { margin-top: 44px; padding-top: 0; }
  .footer__contact { margin-top: 44px; }
  .footer__bottom  { margin-top: 12px; }
  /* the cta block is label 22 + gap 12 + button 32 = 66; the label carried 16 */
  .footer__cta-label { margin-bottom: 12px; }
  /* the contact rows sit 4 apart, but the last one should not push the block */
  .footer__row:last-child { margin-bottom: 0; }

  /* The contact rows are **12/18** on the frame, not 14/22 - that, and not the
     font, is why the address wrapped into a 49-tall row against the frame's
     41. Its text box is 165 wide and runs to two lines there too.
     With the right size the 5px nudge goes as well: the frame centres its
     single-line rows on the 32 icon (text at +7) and lets the two-line address
     sit near the top (+2), which is what `align-items: center` does on its own.
     The block also carried a 4px padding-top that the frame has no room for. */
  .footer__contact { padding-top: 0; }
  .footer__row {
    align-items: center;
    font-size: var(--fs-12);
    line-height: var(--lh-12);
  }
  .footer__row > :not(.footer__row-icon) { padding-top: 0; }
  .booklet__form input { min-width: 0; width: 100%; }
}


/* ---------------------------------------------------------------- 1200
   Below the frame width the page stops being the design and starts being
   a proportional version of it. The tokens shrink the page padding; what
   follows is only what needs more than that. */
@media (max-width: 1200px) {
  .hero .container { padding-block: 200px 140px; }
  .hero__inner { position: static; width: 100%; max-width: 560px; }

  .section--collections .container { padding-inline: var(--pad-80); }
  .collection-mosaic { grid-template-columns: repeat(2, 1fr); grid-template-rows: auto; }
  .collection-mosaic > * { grid-column: auto !important; grid-row: auto !important; }
  .collection-card { aspect-ratio: 3 / 4; height: auto; }

  /* the collage is positioned against a 1440 frame; below that it stops
     being a composition and becomes a plain band above the copy */
  .intro .container { padding-inline: var(--pad-80); }
  .intro__stage { aspect-ratio: auto; }
  /* Phone frame shows the team as a HORIZONTAL rail - the 390 frame draws
     140x160 portraits, roughly two-and-a-half in view, the rest reached by
     swiping. Not the desktop scatter and not a three-up grid. Snap so each
     swipe lands cleanly. */
  .intro__collage {
    position: static;
    display: flex;
    gap: 4px;                            /* the frame's 4px between portraits */
    overflow-x: auto;
    /* free scroll, not mandatory-snap: snapping forced photo 6 flush to the
       edge and hid the left peek, so the rail looked one-way. Free scroll keeps
       the 390 frame's look - a portrait cut at BOTH edges - and both directions
       stay obviously swipeable. */
    -webkit-overflow-scrolling: touch;
    scrollbar-width: none;
    margin-bottom: 24px;
    padding-bottom: 8px;
    /* the desktop base sets pointer-events:none so the absolute collage never
       eats clicks on the copy beneath it; on a phone it IS the scroller, so
       touch must reach it. touch-action:pan-x lets a sideways drag scroll the
       rail while a vertical drag still scrolls the page. */
    pointer-events: auto;
    touch-action: pan-x;
  }
  .intro__collage::-webkit-scrollbar { display: none; }
  .intro__collage img {
    position: static;
    flex: 0 0 140px;                     /* fixed 140x160, not a fill-to-three */
    width: 140px;
    height: 160px;
    aspect-ratio: 7 / 8;                 /* 140 / 160 */
    object-fit: cover;
  }
  /* Photos keep their natural order 1..12 so the rail can be swiped BOTH ways
     (1-5 to the left, 8-12 to the right). theme.js opens it parked on photo 6,
     so the two new portraits (6 & 7) lead the visible window while the rest
     stay reachable in either direction - the way the 390 frame is drawn. */
  .intro__body { position: static; width: 100%; }
  .intro__body .h-section { max-width: none; }

  /* the mosaic is a desktop composition; the rows unstack */
  .projects__row--top,
  .projects__row--bottom { grid-template-columns: 1fr; aspect-ratio: auto; gap: 16px; }
  .projects__row--bottom { gap: 32px; }
  .projects__stack { grid-template-rows: auto auto; }
  .projects__row--top > .project-tile { aspect-ratio: 16 / 11; }
  .projects__stack > .project-tile { aspect-ratio: 594 / 272; }
  .projects__pair > .project-tile { aspect-ratio: 1 / 1; }
  .projects__pair--wide > .project-tile { aspect-ratio: auto; height: 320px; }
  .projects__copy { padding-inline: 0; }

  /* Below the desktop frame the 360 band goes back to stacked - head, then
     the stage - so everything theme.css positions absolutely is unwound. */
  .viewer { aspect-ratio: auto; overflow: visible; }
  .viewer .container { padding-block: 80px 100px; }
  .viewer__stage {
    position: static;
    left: auto;
    top: auto;
    transform: none;
    width: min(560px, 88vw);
    margin: 100px auto 0;
  }
}

/* ---------------------------------------------------------------- 900 */
@media (max-width: 900px) {
  .contact .container { grid-template-columns: 1fr; padding-block: 64px; }
  .contact__media { aspect-ratio: 4 / 3; }
  .contact__panel { padding: 32px 0 0; }

  /* the two-column About sections unstack here, well before the phone
     breakpoint, because their text columns get unreadably narrow first */
  .ceo .container   { grid-template-columns: 1fr; gap: 32px; }
  .craft .container { grid-template-columns: 1fr; gap: 32px; }
  .craft-mosaic     { aspect-ratio: 3 / 2; }
  .story__image     { height: 360px; }
}

/* ---------------------------------------------------------------- 720
   One column from here down.

   Every number in this block is read off the Figma mobile frame at 390.
   The frame is consistent about two of them, so they are worth stating
   once: the page gutter is 20, and a section pads 60 top and bottom.
   Each section's total design height is in its comment - if a change
   makes the page taller, that is where to look first. */
@media (max-width: 720px) {
  .container,
  .section--collections .container,
  .intro .container,
  .process .container { padding-inline: 20px; }

  /* the frame's 60 is the whole gap between one section's content and the
     next, so it goes on the section - and any container that carries its
     own padding-block from the desktop rules has to give it up, or the two
     add up and the section comes out 120 short of the mock. */
  .section { padding-block: 60px; }
  .viewer .container,
  .process .container,
  .contact .container { padding-block: 0; }

  /* hero - 700 tall, the copy sitting 134 off the bottom with the dots
     inside that gap. The desktop container is a fixed `height: 780px`,
     not a min-height, so setting min-height on .hero alone does nothing -
     the height itself has to come down. */
  .hero { min-height: 700px; }
  .hero .container {
    height: 700px;
    display: flex;
    flex-direction: column;
    justify-content: flex-end;
    padding-block: 0 134px;
    padding-inline: 20px;
  }
  .hero__title { max-width: none; }

  /* The frame's hero gaps. All three frames that draw a hero agree on the
     first two - eyebrow->title 0 (Homepage 326+18=344, Bespoke 404+18=422)
     and title->lede 12 (Homepage, About and Bespoke alike). The third,
     lede->button 32, only exists where there is a button: the frame's lede
     box is 66 tall - three lines - so it ends at 494, and the button is at
     526. This read 54 until the measuring width was corrected, because in a
     365 column the lede wrapped to two lines and looked 22 shorter than it
     is. The frame's own box height settles it, not our wrapping.

     Scoped to the front page on purpose. Every inner hero is bottom-anchored
     with its content foot 86 above the hero's (their frames all end in a
     button at 574..614), except About at 134 - and four of the six cannot be
     anchored at all until that button exists. See ROBOT-TODO. */
  /* All six phone frames agree, inner heroes included: eyebrow->title 0,
     title->lede 12, and lede->button 32 wherever a button exists (Bespoke
     396/450/526, Collections the same, About 446/500, Contact 494/548, a
     collection page 308/362/526). The `:not(.hero--inner)` this rule used to
     carry left every inner hero on the site's default 18 and 42. */
  .hero .hero__title { margin-top: 0; margin-bottom: 12px; }
  /* Both ends of each gap have to be stated. The lede carries its own 18 of
     margin-top and the actions row 42, and sibling margins collapse to the
     LARGER - so setting only the bottoms left the gaps at 18 and 42. */
  .hero .hero__lede { margin-top: 0; margin-bottom: 32px; }
  .hero .hero__actions { margin-top: 0; }
  /* The frame draws two buttons in the hero on desktop - 216x40 at x=80 and
     185x40 at x=320 - but only one on phones: a 216x40 at x=20, the same
     component and the same width as the desktop leftmost. So on a phone the
     secondary drops rather than stacking under the primary, and the primary
     hugs its label instead of going full-bleed.

     The copy is bottom-anchored by the container's 134px bottom padding, so
     losing the second button does not leave a hole - the whole stack moves
     down 52 (a 40 button plus the 12 gap) and the survivor lands on the
     frame's y=526. */
  .hero__actions { flex-wrap: wrap; gap: 12px; }
  .hero__actions .btn { flex: 0 0 auto; justify-content: center; }
  .hero__actions .btn--ghost { display: none; }
  .hero__dots { bottom: 60px; }

  /* inner hero - breadcrumb sits under the 80-tall mobile nav */
  .hero--inner .container { padding-block: 0 110px; }
  .hero--inner .hero__inner { padding-bottom: 0; max-width: none; }

  /* About's hero, and only About's: it is the one page using hero--center.
     Frame: title 452 (24/36, one line), lede 500 (14/22, three lines), so the
     copy ends at 566 - 134 above the hero's foot, which is the same offset
     the front page uses. Same title->lede 12 as everywhere else. The lede's
     36px bottom margin goes, because here the padding is what holds the copy
     off the foot and having both meant neither was the real number.

     Deliberately not written as `.hero--inner`: the other six inner heroes
     are all hero--left and each frame stacks them differently - Bespoke ends
     in a button, Pearl Wave in a button plus a share control plus a spec
     block, the article in a meta line - so one anchor cannot serve them. */
  /* Higher-specificity (0,3,0) so it wins over whatever was zeroing the
     container's bottom padding and dropping the copy to the hero's floor (it
     sat at ~83% down; the phone frame puts the title at ~58%, y452 of 780).
     22vh holds the copy that far off the foot on the ~0.86x-viewport hero. */
  .hero--center.hero--inner .container {
    justify-content: flex-end;
    padding-bottom: 22vh;
  }
  .hero--center .hero__title { margin: 0 0 12px; }
  .hero--center .hero__lede  { margin-bottom: 0; }
  .hero__crumbs { top: 88px; left: 20px; }
  .hero__home svg { width: 20px; height: 20px; }


  /* the header row: title, then the button centred under it. The rule
     between them only makes sense when they sit side by side. */
  .head-row {
    grid-template-columns: 1fr;
    gap: 16px;
    height: auto;
    margin-bottom: 32px;
    justify-items: center;
    text-align: center;
  }
  .head-row__rule { display: none; }

  /* The frame's small button, 32 tall: 14/22 with 14px of side padding. On
     phones it is the section buttons that drop to this size while the hero
     and the form keep the 40 - so this is a breakpoint, not a variant, and
     the same buttons stay 40 on desktop. Frame widths, in page order:
     collections 169x32, intro 168x32 twice, projects 149x32. */
  .head-row .btn,
  .intro__actions .btn,
  .projects__copy-action .btn {
    height: 32px;
    padding-inline: 14px;
    font-size: var(--fs-14);
    line-height: var(--lh-14);
  }
  .head-row .btn { justify-self: center; }

  /* collections - 1326: heading 108, gap 32, then square cards 8 apart.
     Not a sideways row: the frame stacks them, and the designer's arrow
     note belongs to the process steps, not here. */
  .collection-mosaic {
    display: grid;
    grid-template-columns: 1fr;
    grid-template-rows: auto;
    gap: 8px;
  }
  .collection-mosaic > * { grid-column: auto !important; grid-row: auto !important; }
  .collection-card { aspect-ratio: 1 / 1; height: auto; }
  .collection-card__meta { left: 24px; right: 24px; bottom: 24px; }
  /* the frame shows three. Anything past that is a scroll nobody finishes,
     and the button in the header already goes to the full list. */
  .collection-mosaic > :nth-child(n+4) { display: none; }

  /* intro - the 390 frame keeps the horizontal photo rail set at <=1200 (all
     portraits, swipe for the rest - no three-up grid), the copy below it,
     then the two buttons stacked. */
  .intro { padding-block: 60px; }
  .intro__actions { flex-direction: column; align-items: center; gap: 8px; }
  .intro__actions .btn { min-width: 164px; width: auto; white-space: nowrap; }

  /* projects - 822: the copy moves above the tiles, then 350x200 over two
     rows of 171x140. `display: contents` lets the copy and the wide pair
     be ordered independently even though they share a wrapper. */
  .projects .container { gap: 8px; }
  .projects__row--bottom { display: contents; }
  .projects__copy { order: 1; padding-inline: 0; padding-bottom: 24px; gap: 16px; }
  .projects__row--top { order: 2; }
  .projects__pair--wide { order: 3; }

  .projects__row--top {
    display: flex;
    flex-direction: column;
    gap: 8px;
    aspect-ratio: auto;
  }
  .projects__row--top > .project-tile { aspect-ratio: 350 / 200; }
  .projects__stack { display: flex; flex-direction: column; gap: 8px; }
  /* five tiles in the mobile frame against six on desktop - the one in
     the middle of the stack is the one it drops */
  .projects__stack > .project-tile { display: none; }
  .projects__pair,
  .projects__pair--wide { display: grid; grid-template-columns: 1fr 1fr; gap: 8px; }
  .projects__pair > .project-tile,
  .projects__pair--wide > .project-tile { aspect-ratio: 171 / 140; height: auto; }
  /* 174 is the desktop width; on phones the frame hugs the label at 149. */
  .projects__copy-action .btn { width: auto; min-width: 0; }

  /* stats - 320: two by two, 64 between the rows, each cell 68 tall */
  .stats .container {
    grid-template-columns: 1fr 1fr;
    gap: 64px 0;
    padding-block: 60px;
    padding-inline: 20px;
  }
  /* 12/18, not the desktop 16/24. The cell is 42 + 8 + 18 = 68 and the label
     has to hold one line inside a 175 column: at 16px "YEARS IN THE INDUSTRY"
     wraps to two, which made the first row 98 instead of 68 and the whole
     band 356 against the frame's 320. The margin was already right; only the
     type was missed. */
  .stat__label {
    margin-top: 8px;
    font-size: var(--fs-12);
    line-height: var(--lh-12);
  }

  /* The 360 band on the phone is NOT a heading with a picture under it.
     The frame's F-S04 is a single 390x300 box that carries the chandelier
     as its own image fill, with the three lines of copy centred over the
     top of it - the whole band is the picture.

     Ours stacked a 350x270 stage below the heading and came out 526 tall
     against the frame's 300, which was the only real drift on the page.

     The spin files are 900x695, a ratio of 1.295; the band is 390x300, or
     1.30. They are the same shape, so the sequence fills the band exactly
     and the drag still works because the stage IS the band.

     Copy: eyebrow 12/18 w500, title 16/24 w400, hint 12/18 w400, centred,
     starting 32 down. */
  .viewer {
    position: relative;
    min-height: 300px;
    padding-block: 0;
    overflow: hidden;
  }
  /* The stage is a CHILD of .container in the markup, so making the
     container the positioning context anchored the stage to it - and the
     container is only as tall as the three lines of copy, so `inset: 0`
     resolved to 96px rather than the band's 300. The context has to stay on
     .viewer; the head is lifted above the picture on its own. */
  .viewer .container {
    padding-block: 32px 0;
  }
  /* No gap anywhere in this head on the phone: the frame stacks the three
     lines flush - eyebrow 32..50, title 50..74, hint 74..92 - so each one
     starts exactly where the last ends. */
  .viewer__head { gap: 0; position: relative; z-index: 1; }
  .viewer__head-top { gap: 0; }
  .viewer__head .eyebrow { font-size: var(--fs-12); line-height: var(--lh-12); }
  .viewer__title { font-size: var(--fs-16); line-height: var(--lh-16); }
  .viewer__hint  { font-size: var(--fs-12); line-height: var(--lh-12); }

  .viewer__stage {
    position: absolute;
    inset: 0;
    z-index: 0;
    width: 100%;
    max-width: none;
    aspect-ratio: auto;
    margin: 0;
  }
  .viewer__stage img { object-fit: cover; }
  /* the "drag" label sits under the stage on desktop; here the stage is the
     whole band, so it would land outside it */
  .viewer__grab { bottom: 12px; }

  /* process - 662: heading 130, gap 32, then a 380 tall row of 250 wide
     cards that scrolls sideways (see the block below). The card photo is
     squarer here than on desktop. */
  .steps { margin-top: 32px; }
  .step__media { aspect-ratio: 250 / 231; }
  /* The frame's card, offsets from its top: image 0..231, number row 247,
     title 283, body 313. So the gaps are 16, 12 and 6.

     `margin-bottom: 0` matters as much as the rest: .step__row is a <p> and
     keeps the paragraph default of 16 at the foot, which collapses against
     the title's 12 and wins - the title sat 16 below the row while the rule
     above it plainly said 12. Nothing was wrong with the value; it was never
     the one being used. */
  .step__row { margin-top: 16px; margin-bottom: 0; }
  .step__title { margin-top: 12px; }
  .step__text { margin-top: 6px; }

  /* contact - 758: the frame is one 350 wide card on the section's own
     background, with no separate photo block above it.

     The card is NOT flush with its contents. Read off F-S06: the card sits
     at x20 w350 and everything inside it at x40 w310 - a 20 inset - and the
     title starts 24 down from the card's top, with the closing note 24 up
     from its foot. Ours had `padding: 0`, so the fields ran the full 350,
     the title sat on the card's very edge, and the band came out 694
     against the frame's 758. One padding covers all three.

     Field heights themselves were already right: label 22, input 40,
     textarea 100, button 40, and the 4/16 rhythm between them. */
  .contact__media { display: none; }
  .contact__panel { padding: 24px 20px; }
  /* the frame leaves 21 between the standfirst and the first label; ours
     ran them together */
  .contact__panel form { margin-top: 21px; }

  /* booklet - 308: 100 above and 50 below a 158 tall full-width band, the
     field and the button side by side inside it */
  .booklet { padding-block: 100px 50px; }
  .booklet__card { width: 100%; padding: 36px 44px; }
  .booklet__inner { width: 100%; }
  .booklet__inner .mifa-form { margin-top: 24px; }
  /* the field keeps its desktop flex-basis, so field + button add up to
     more than the 302 available and the row wraps to two lines. Letting
     the field shrink is what keeps them side by side, as the frame has
     them: 158 and 136 with 8 between. */
  .booklet__inner .mifa-form--inline {
    flex-direction: row;
    flex-wrap: nowrap;
    align-items: flex-start;
    gap: 8px;
  }
  .booklet__inner .mifa-form--inline .mifa-form__grid { flex: 1 1 158px; min-width: 0; }
  .booklet__inner .mifa-form--inline .mifa-form__submit { flex: 0 0 136px; min-width: 0; }

  /* The row is 32 tall on the frame - field 158x32, button 136x32 - and the
     form plugin ships both at 40, which is the whole of this band's +8. The
     button also keeps 24px of side padding; the frame's 32-tall button uses
     14 (see the button type scale item). */
  .booklet__inner .mifa-form--inline .mifa-form__grid,
  .booklet__inner .mifa-form--inline input { height: 32px; }
  .booklet__inner .mifa-form--inline .mifa-form__submit {
    height: 32px;
    padding-inline: 14px;
  }






  /* --- article, from the News-Vela frame --- */
  .article__body { padding-block: 80px 60px; }
  .article__more { padding-block: 60px; }

  /* --- News, from the 390 frame: this section pads 80 not the usual 60 --- */
  .news-listing__main { padding-block: 80px; }
  .newsletter { padding-block: 80px; }

  /* --- Contact page, from the 390 frame: gutter 28 not 20 --- */
  .contact-details .container { padding-inline: 28px; }
  .contact-details__intro { text-align: left; }

  /* --- Lighting Collection, from the 390 frame ---
     grid two columns not three; rail tiles 250 wide, 300 tall */
  .collection-grid { grid-template-columns: 1fr 1fr; gap: 9px 8px; }
  /* Measured off m-lighting-collection.png, not assumed. The rail is NOT a
     centred first tile with empty space beside it - it is a carousel already
     part-way along, with a slice of the tile either side:

       left peek   x  20 ..  61.5   (42 wide)
       centre tile x  70 .. 319.5   (250)
       right peek  x 328 .. 369.5   (42, clipped by the track's own edge)

     So the track is 350 wide inside the 20 gutter - not bled to the screen
     edges - and it opens with the SECOND tile centred. We had
     `padding-inline: calc((100% - 250px) / 2)`, which centres the FIRST tile
     and therefore shows nothing at all on the left. Milad: "این مدلیه رو
     موبایل درست نیست کاروسلش". */
  .project-rail__track {
    margin-inline: 20px;
    padding-inline: 0;
  }

  /* --- Bespoke, from the 390 frame ---

     The panels do NOT run edge to edge. That was read off the frame's text
     coordinates, which is exactly the mistake ch.18 is about: the copy is
     inset 46 from the page, so the panel was assumed to start at 0. Measured
     off the panel's own fill colour instead, both bands sit at x 20..369 -
     the ordinary page gutter - and are 350 wide.

       light  panels 1028..1151, pitch 159  ->  124 tall, 35 apart
       dark   panels 3374..3543, pitch 186  ->  170 tall, 16 apart  */
  .feature-cards__list {
    grid-template-columns: 1fr;
    gap: 36px;              /* panels on a 160 pitch: 124 + 36 */
    margin-inline: 0;
  }
  /* last panel closes 778, the band closes 878 */
  .feature-cards--light { padding-block: 80px 100px; }

  /* Bespoke reorders itself on a phone. The 1440 frame runs
       why -> steps -> design intelligence -> gallery
     and the 390 frame runs
       why -> gallery -> steps -> design intelligence.
     The Elementor wrapper is already a flex column, so `order` is enough -
     and matching on what each container HOLDS rather than on its generated
     id keeps this working if the page is ever rebuilt. */
  body.page--bespoke .elementor > .e-con:has(.feature-cards--light) { order: 1; }
  body.page--bespoke .elementor > .e-con:has(.gallery)              { order: 2; }
  body.page--bespoke .elementor > .e-con:has(.process)              { order: 3; }
  body.page--bespoke .elementor > .e-con:has(.media-band)           { order: 4; }
  body.page--bespoke .elementor > .e-con:has(.contact)              { order: 5; }
  .feature-cards--light .feature-cards__list { margin-top: 100px; }
  .feature-cards--dark  { padding-block: 60px 56px; }
  .feature-cards--dark .feature-cards__list { margin-top: 32px; gap: 16px; }

  /* The panel is full-bleed but its copy is not: the frame insets the text
     to x=46. The bottom padding is much deeper than the top - read off the
     frame rather than assumed:

       light  panel 1032..1183, title 1052, text 1084 (2 lines, ends 1128)
              -> 20 above, 55 below, panel 151
       dark   panel 3389..3549, title 3409, text 3443 (2 lines, ends 3487)
              -> 20 above, 62 below, panel 160

     An earlier pass put 33/40 here on the assumption the copy wrapped to
     three lines. It wraps to two, so every panel came out ~22 short. */
  /* Copy is inset 26 from the panel edge (frame x 46 against a panel at 20).
     The LIGHT band is not the same: its panels put the copy on x32 against a
     panel at 20, so 12 - and at 26 the third card's line wrapped and the
     panel came out 22 deep. */
  .feature-card { padding: 24px 26px; }
  .feature-cards--light .feature-card { padding-inline: 12px; }

  /* the frame's tiles are 175x205 and sit 8 apart: columns at 20 and 198 */
  .gallery__grid { grid-template-columns: 1fr 1fr; gap: 8px; margin-top: 32px; }
  .gallery__tile { aspect-ratio: 175 / 205; }


  /* --- About story, from the 390 frame ---
     The frame pads this one 60/0 - the photo runs to the section edge. The
     desktop rule says so too, but it sits before the Responsive block and
     .section { padding-block: 60px } here has the same weight and wins. */
  .story { padding-bottom: 0; }
  .story__head .btn { height: 40px; }

  /* ---- About, from the 390 frame ---- */

  /* mission - 580: the two cards stack */
  .mission__cards { grid-template-columns: 1fr; gap: 16px; margin-top: 32px; }
  .mission-card { min-height: 150px; padding: 24px 20px; }

  /* story - 586: 60 above the copy, nothing below it, photo edge to edge */
  .story__media { margin-top: 32px; }
  .story__image { height: 250px; }

  /* founder - 620: copy above the photo */
  /* the photo is a fixed 250 on the frame (2176..2426), sitting 32 under the
     copy - not an aspect ratio, which drifts with the test width */
  .ceo .container { grid-template-columns: 1fr; gap: 32px; }
  .ceo__image { aspect-ratio: auto; height: 250px; }

  /* values - 586: one column, 80 top and bottom rather than the usual 60 */
  .values { padding-block: 80px; }
  /* 44, not 32: the frame's inner stack carries a single gap=44 and the title
     is the first child of it, so the space under the title is the same as the
     space between the items. Getting this one wrong pulled all four items up
     by 12 - the whole of the section's deficit - while every item height was
     already exact. */
  .values__title { margin-bottom: 44px; }
  /* items sit 44 apart on the frame (titles 2646 · 2738 · 2852 · 2944, the
     114 pitch being one that wraps to two lines) and the text almost touches
     its title - 2, not 8 */
  .values__grid { grid-template-columns: 1fr; gap: 44px; }
  .value__text  { margin-top: 2px; }

  /* craft - 814 = pad 60 + a 262 copy block + gap 32 + a 400 mosaic + pad 60,
     read straight off the frame's own auto-layout: the copy frame carries
     gap=12 and the section gap=32. */
  .craft .container { grid-template-columns: 1fr; gap: 32px; }
  .craft__copy .body { margin-top: 12px; }
  /* The mosaic is 350x400: a 3x3 grid of 114x131 cells 4 apart, which is
     114*3+4*2 = 350 across and 131*3+4*2 = 401 down. It was set to 350/397
     with a 6px gap - our own rendering measured and then written back as if
     it were the spec. The frame says 4. */
  .craft-mosaic { aspect-ratio: 350 / 400; gap: 4px; }
}

/* ---------------------------------------------------------------- 420
   The narrow end. The stats stay two across - that is what the 390 frame
   does - but the titles come down a step. */
@media (max-width: 420px) {
  /* .viewer__title is not stepped down here any more - the 390 frame puts it
     at 20/30, which the 720 block now sets, and this rule was overriding it
     back up to 24/36 at exactly the width we measure at. */
  .booklet__title { font-size: 20px; line-height: 30px; }
  .booklet__card { padding-inline: 24px; }
}


/* ---------------------------------------------------------------- 720
   The process row scrolls sideways.

   Stacked, four process steps run to well over two thousand pixels on a
   phone and nobody reaches the end. The designer's note is explicit about
   this one section - "بقیه مراحل با اسکرول افقی دیده میشوند" - so a
   sideways row is the intended shape. Here it is the finger that does the
   stepping, with snap points so a card always lands square.

   This applies to .steps and nothing else. The collection cards get the
   same note in the Bespoke frame but not on the homepage, where the frame
   stacks them - see the collections rules above. */
@media (max-width: 720px) {
  .steps {
    display: flex;
    grid-template-columns: none;
    gap: 16px;
    overflow-x: auto;
    scroll-snap-type: x mandatory;
    /* bleed to the screen edges so a half-visible next card signals that
       the row keeps going */
    margin-inline: -20px;
    padding-inline: 20px;
    /* no padding-bottom: the rail hugs its cards. There was 4px here, which
       is not in the frame (its rail is 380 around a 379 card, i.e. rounding)
       and cannot have been scrollbar clearance either - the scrollbar is
       hidden two rules down. */
    scrollbar-width: none;
  }
  .steps::-webkit-scrollbar { display: none; }

  /* BESPOKE ONLY - one step fills the width.
     Measured off m-bespoke.png: the photo is 350x198 at x 20..370, the
     number and the title share a centred line under it with a chevron at
     the right edge, and the description is centred below.

     This is scoped to --pairs because the HOMEPAGE mobile frame is a
     different card entirely: 250 wide, 16 apart, photo 250x231, the number
     on its own line with a rule beside it, then the title, then the copy -
     see the block after this one. Written unscoped it took the homepage
     with it, exactly as the desktop checkerboard did.
     `flex: 0 0 100%` resolves against .steps' content box, which is the
     container's 350 - the -20/+20 bleed is padding, not content. */
  .process--pairs .steps > * {
    flex: 0 0 100%;
    scroll-snap-align: center;
  }

  /* Four explicit columns rather than absolute positioning: the outer two
     are equal free space, so the number and title sit centred as a pair
     whatever their length, and the chevron pins to the right edge on the
     same line. The last step has no chevron and still centres, because its
     empty fourth track is the same width as the first. */
  .process--pairs .steps .step {
    display: grid;
    grid-template-columns: minmax(24px, 1fr) auto auto minmax(24px, 1fr);
    align-items: baseline;
    column-gap: 8px;
  }
  .process--pairs .steps .step__media { grid-column: 1 / -1; grid-row: 1; aspect-ratio: 350 / 198; }
  /* the rule beside the number is a desktop device - on the phone frame the
     number sits directly against the title */
  .process--pairs .steps .step__row   { grid-column: 2; grid-row: 2; margin: 16px 0 0; }
  .process--pairs .steps .step__rule  { display: none; }
  .process--pairs .steps .step__title { grid-column: 3; grid-row: 2; margin-top: 16px; }
  .process--pairs .steps .step__text  {
    grid-column: 1 / -1;
    grid-row: 3;
    margin-top: 6px;
    text-align: center;
  }

  .process--pairs .steps .step__nav {
    grid-row: 2;
    align-self: center;
    display: grid;
    place-items: center;
    /* 40, not the glyph's 24: this is a thumb target. The box is pinned to
       the card's edge, so a 40 box centres the chevron 20 in from it - which
       is exactly where the frame draws it (350 of a 20..370 card). */
    width: 40px;
    height: 40px;
    margin-top: 16px;
    padding: 0;
    border: 0;
    background: none;
    color: var(--c-ink);
    cursor: pointer;
  }
  .process--pairs .steps .step__nav--prev { grid-column: 1; justify-self: start; }
  .process--pairs .steps .step__nav--next { grid-column: 4; justify-self: end; }
  .process--pairs .steps .step__nav svg { width: 18px; height: 18px; }
  /* The unusable arrow (no back on step 1, no forward on the last) is hidden
     but KEEPS its 40px column, so every step is the identical four-column
     shape - the number, title and chevrons land in the same place on each and
     do not jump as you swipe. */
  .process--pairs .steps .step__nav--off { visibility: hidden; pointer-events: none; }

  /* HOMEPAGE mobile - the frame's own card, read off F-S03:
       card    250 wide on a 266 pitch, so 16 apart
       photo   250x231
       number  16/24 at y247 with a 1px rule from x32 to the card edge
       title   16/24 at y283
       copy    14/22 at y313
     The row is 380 tall; ours was 304 because it had been given the
     Bespoke card. */
  .process:not(.process--pairs) .steps > * {
    flex: 0 0 250px;
    scroll-snap-align: start;
  }
  .process:not(.process--pairs) .steps .step { display: block; }
  .process:not(.process--pairs) .steps .step__media {
    aspect-ratio: 250 / 231;
    margin: 0;
  }
  .process:not(.process--pairs) .steps .step__row { margin: 16px 0 0; }
  .process:not(.process--pairs) .steps .step__title {
    margin: 12px 0 0;
    font-size: var(--fs-16);
    line-height: var(--lh-16);
    text-align: left;
  }
  .process:not(.process--pairs) .steps .step__text {
    margin: 6px 0 0;
    font-size: var(--fs-14);
    line-height: var(--lh-14);
    text-align: left;
  }
  /* the chevron belongs to the Bespoke card */
  .process:not(.process--pairs) .step__nav { display: none; }

  /* the reveal stagger fights a scroller - everything arrives together */
  .steps .step { transform: translateY(16px); transition-delay: 0s; }
}

/* Four steps side by side above 720: nothing to advance to. */
@media (min-width: 721px) {
  .step__nav { display: none; }
}

/* ===================================================================
   Drag-to-scroll affordance.

   Only where there is a mouse: a finger already scrolls these natively and
   a grab cursor means nothing on a touch screen. `is-dragging` is put on by
   theme.js while the button is down.
   =================================================================== */
@media (hover: hover) and (pointer: fine) {
  .steps,
  .project-rail__track { cursor: grab; }
  .steps.is-dragging,
  .project-rail__track.is-dragging { cursor: grabbing; }
  /* text selection turns a drag into a highlight sweep */
  .steps.is-dragging,
  .steps.is-dragging *,
  .project-rail__track.is-dragging,
  .project-rail__track.is-dragging * { user-select: none; }
}


@media (max-width: 1000px) {
  .card-grid { grid-template-columns: repeat(2, 1fr); gap: 24px; }
  .collection-mosaic--row { grid-template-columns: repeat(2, 1fr) !important; }
  /* .article__title stays 28/42 across breakpoints - it is a section heading,
     not a display line (see theme.css). */
}

@media (max-width: 720px) {
  .container--reading { padding-inline: 20px; }
  .card-grid { grid-template-columns: 1fr; gap: 28px; }
  .collection-mosaic--row {
    grid-template-columns: none !important;
    display: flex;
  }
  .listing--bare { padding-top: 120px; }
  .article__head { padding-top: 40px; }
}


/* ===================================================================
   Image heights on pages built in the editor

   Elementor ships `.elementor img { height: auto; max-width: 100% }`.
   That selector is a class plus an element - specificity (0,1,1) - so it
   beats every plain `.some__bg { height: 100% }` in this file, and each of
   those images collapses to its own aspect ratio.

   It only happens on pages built in the editor, so the homepage looks
   correct and an inner page silently does not: the About background photo
   rendered 1900px tall inside a 581px section, and the story photo 950px
   instead of 500px. Nothing errors; the page is just wrong.

   Every image the theme gives a height to therefore has to be stated more
   specifically than (0,1,1). Qualifying with the section class is enough
   and keeps the intent readable.

   The list below kept missing one. It was written for About, and the next
   two section backgrounds added after it - Bespoke's `feature-cards__bg`
   and Contact's `contact-details__bg` - were never added, so they rendered
   488 of 837 and 260 of 711: the lower half of each band simply had no
   photo, on the two pages Milad said did not look like the design.

   So the full-bleed backgrounds are now covered by one rule instead of a
   list that has to be remembered. They are all emitted through
   `lorenzo_img()` with a `__bg` class and they are all meant to cover, so
   the pattern is the reliable thing to key on - a new section background
   is correct the day it is written. (0,2,1), which clears (0,1,1). */

.elementor img[class$="__bg"] { height: 100%; }

/* ------------------------------------------------------------------ */

.hero .hero__bg img,
.hero .hero__bg video,
.hero .hero__slide img               { height: 100%; }
.stats img.stats__bg,
.booklet img.booklet__bg,
.values img.values__bg,
.mission-card img.mission-card__bg   { height: 100%; }
/* The mission band's photograph is the BOTTOM 500 of a 750 section, not the
   whole of it - it has to be stated here at the same weight, or the blanket
   `height: 100%` above (which exists to beat Elementor's `img { height:auto }`)
   stretches it over the heading as well. */
.mission img.mission__bg              { height: 66.67%; }
.collection-mosaic .collection-card img,
.craft-mosaic .craft-tile img,
.steps .step__media img,
.contact .contact__media img         { height: 100%; }
.story .story__media img.story__image { height: auto; aspect-ratio: 1440 / 650; }
/* 300 is the phone value. It lives in this block because Elementor's
   `.elementor img { height: auto }` has to be beaten with (0,3,0), and that
   weight also beat the desktop rule up in the rail's own section - the tiles
   stayed 300 tall at full width against the frame's 353. Both heights are
   stated here, at the same weight, so the media query decides. */
/* The configurator and the media band are Elementor widgets, so every image
   in them meets `.elementor img { height: auto }` at the same weight and
   later in the cascade. The stage was the visible one: a 719x366 file in a
   711x452 box rendered at its own height and left 90px of nothing under it,
   which read as a gap above the thumbnail strip rather than as a short
   photo. */
.configurator .configurator__stage img,
.configurator .configurator__thumb img,
.configurator .configurator__swatch img { height: 100%; }
/* 64x53 in the 1440 frame (the source art is 254x210, the same ratio) - a
   flat 40x40 box squashed it out of shape and threw away most of its detail,
   which is why the five types were hard to tell apart. */
.configurator .configurator__type-icon img { width: 64px; height: 53px; }
.media-band .media-band__video { height: 100%; }

/* 350, from the file - the 353 here came off a PNG and beat the rule in the
   desktop block above on source order. */
.project-rail .project-rail__tile img  { height: 350px; }
@media (max-width: 899px) {
  .project-rail .project-rail__tile img { height: 300px; }
}
/* 250 is the phone value and it was sitting in this global block, so the
   desktop photo was squashed to 250 and `aspect-ratio` never got a say - the
   picture showed far more of the room than the frame does. The frame's box is
   594x458, so the ratio drives it above 900 and the flat height only applies
   on a phone. */
.ceo .ceo__media img.ceo__image        { height: auto; }
@media (max-width: 900px) {
  .ceo .ceo__media img.ceo__image      { height: 250px; }
}

@media (max-width: 900px) {
  .story .story__media img.story__image { height: 360px; }
}

@media (max-width: 720px) {
  .intro .intro__collage img            { height: 160px; }
  .story .story__media img.story__image { height: 250px; }
}


/* ===================================================================
   Type on the 390 frame

   Read out of the Figma tree rather than eyeballed. Every text node in the
   mobile frames uses one of six steps:

       12/18   14/22   16/24   20/30   24/36   32/42

   The theme was rendering body copy at 18/28 - a size that is not in the
   design at all - so every block ran taller than the frame and the error
   accumulated down the page: the founder section started 204px late.

   The heading step also mattered. This used to say "section titles are
   20/30" - that was an older frame. The 2026-08-22 mobile frame puts every
   section heading at 24/36, and the per-section overrides below that each
   restated 20/30 are gone with it: one rule now, so the next change lands
   in one place instead of five.
   =================================================================== */
@media (max-width: 720px) {
  .h-section       { font-size: var(--fs-24); line-height: var(--lh-24); }
  /* the booklet band sits one step below the section headings on the phone
     frame, as it does on desktop: 20/30 against 24/36 */
  .booklet__title  { font-size: var(--fs-20); line-height: var(--lh-20); }
  .hero__title,
  .values__title   { font-size: var(--fs-24); line-height: var(--lh-24); }
  .hero--left .hero__title { font-size: var(--fs-28); line-height: var(--lh-28); }

  .lede,
  .body            { font-size: var(--fs-14); line-height: var(--lh-14); }

  .mission-card__title,
  .value__title,
  .step__title     { font-size: var(--fs-16); line-height: var(--lh-16); }

  /* the inner pages set their eyebrow a step up from the homepage's 12/18 */
  .story .eyebrow,
  .ceo .eyebrow,
  .craft .eyebrow  { font-size: var(--fs-14); line-height: var(--lh-14); }

  /* These two carry their own desktop sizes at (0,2,0), so the shared rules
     above never reach them. The frame puts both back on the ordinary steps. */
  .process__head .eyebrow   { font-size: var(--fs-12); line-height: var(--lh-12); }
  /* the frame's head frame carries gap=12, not the desktop 16 */
  .process__head .body      { margin-top: 12px; }

  /* Third of these. The projects head is the same shape: its own desktop
     24/36 at (0,2,0), unreachable by the shared rule, where the frame has
     20/30 - and its copy stack carries gap=16 where the frame's block is 12.
     Together those were the section's whole +10: 6 of title height and 4 of
     gap, with everything below inheriting the sum. */
  .projects__copy-text { gap: 12px; }

  /* Fourth and last of these. Swept the stylesheets for the pattern rather
     than waiting for it again: of every `.block__part .h-section` rule that
     sets a size, `.process__head` and `.contact__head` already had mobile
     overrides, `.projects__copy-head` got one last run, `.article__more`
     sets only a margin — this is the remainder. Frame: 20/30, two lines, 60.
     Its body block carries gap=12 where ours had a 16px margin. */
  .intro__body .body { margin-top: 12px; font-size: var(--fs-14); line-height: var(--lh-14); }
  .contact__head .h-section { font-size: var(--fs-24); line-height: var(--lh-24); }

  /* side by side on desktop, centred on a phone - the frame stacks and
     centres both of these */
  .ceo,
  .craft           { text-align: center; }
}


/* --- hero wash ------------------------------------------------------
   The photo is not shown straight: a dark layer sits over it so the white
   copy reads. Solved off the render the same way as the mission wash - a
   text-free strip of the design averages rgb(119,113,103) where the source
   photo, cropped as object-fit:cover would, is rgb(165,156,140):

       1 - 119/165 = .279
       1 - 113/156 = .276
       1 - 103/140 = .264

   All three agree, so it is a flat black at 28%. */
.hero::before {
  content: "";
  position: absolute;
  inset: 0;
  z-index: 0;
  background: rgba(0, 0, 0, .28);
  pointer-events: none;
}
/* The dots are NOT in this list any more. They are `position: absolute` with
   a z-index of 2 in their own rule, which already clears the scrim - and
   `position: relative` here put them back in the flow, so every hero on the
   site was 14 taller than its frame (the dots' own height, added under the
   container). */
.hero .container { position: relative; z-index: 1; }

/* ===================================================================
   The nav collapses at 1100, not 720.

   The six desktop links plus the logo need about 1100px. The hamburger was
   only appearing below 720, so from 721 up to ~1100 the desktop nav rendered
   and ran off the right edge - measured at 860 it reached x=1021 with only
   four of the six links inside the viewport. Every page, every tablet and
   small laptop, and nothing errors: the menu is simply cut off.

   The design has frames at 390 and 1440 only, so where it switches between
   them is ours to choose. 1100 is where it stops overflowing.
   =================================================================== */
@media (max-width: 1100px) {
  /* NOT `display: none` - the panel has to stay laid out so the transform
     can slide it in. Visibility is handled together with the slide, in the
     transform rules below, NOT flipped instantly here: iOS Safari will not run
     a transform transition on an element that was `visibility: hidden` at the
     moment the class lands, and left the panel frozen off-screen - which is
     exactly what Milad saw (only the header showed, the page behind it not the
     menu). Coordinating the two fixes it. */
  .nav        { display: flex; }
  .nav-toggle { display: block; }

  /* The page behind the overlay does not scroll. This lives here, inside the
     phone query, so that if the class is ever left on at desktop width it
     cannot lock a page that has no menu button to unlock it. */
  html.is-nav-open,
  html.is-nav-open body { overflow: hidden; }
}

/* ===================================================================
   MOBILE MENU - it was never built.

   `theme.js` has always toggled `.is-open` on `.nav`, and no rule ever
   showed it. So the hamburger has never worked on any page at any width:
   the click fires, the class lands, nothing happens. Nothing errors either,
   which is why it survived.

   Built from the "Overlay" frame (390x840) in the Figma file:
     background  rgb(41,42,46)  - solid, the same as the footer
     close cross 48x48 at x=330, y=12
     six links   40 tall, first at y=76, then every 64 (so 24 apart),
                 inset 32 from the left, 16/24 type

   The whole block is inside the mobile query on purpose. It used to sit
   global with a `@media (min-width: 1101px)` block resetting each property
   back, and `.nav:not(.is-open)` at (0,2,0) beat that reset's plain `.nav`
   at (0,1,0) - so the desktop nav was translated 100% to the right and ran
   off the screen. Scoping is safer than unwinding. */
@media (max-width: 1100px) {
  .nav {
    position: fixed;
    inset: 0;
    z-index: 60;
    flex-direction: column;
    gap: 24px;
    padding: 76px 32px 32px;
    background: var(--c-dark-soft);
    overflow-y: auto;
    /* Promote to its own layer so iOS runs the slide on the compositor. */
    will-change: transform;
  }
  /* `.site-header` is on the front of both so the open state cannot lose a
     cascade race, and the list itself has to be turned into a column - the
     links live in `ul.nav__list`, which is its own flex row, so setting the
     direction on `.nav` alone left all six stacked on one line. */
  /* Written as two mutually exclusive selectors of equal weight rather than a
     base value plus an override. As `.nav { translateX(100%) }` with
     `.nav.is-open { translateX(0) }` the open state did not take on a real
     click, and chasing why was costing more than restating it. `:not()` leaves
     nothing to win or lose. */
  /* Visibility rides WITH the transform so the element is never mid-transition
     while `visibility: hidden` (the iOS freeze). Open: visibility flips to
     visible at 0s, then the transform slides in on a visible, composited layer.
     Close: visibility stays visible for the whole slide (`0s linear var(--dur)`
     delay), then hides - so the slide-out is seen and the panel is unfocusable
     once gone. */
  .nav:not(.is-open) {
    transform: translateX(100%);
    visibility: hidden;
    transition: transform var(--dur) var(--ease), visibility 0s linear var(--dur);
  }
  .nav.is-open {
    transform: translateX(0);
    visibility: visible;
    transition: transform var(--dur) var(--ease), visibility 0s;
  }

  .nav .nav__list {
    flex-direction: column;
    align-items: flex-start;
    gap: 24px;
    width: 100%;
  }

  .nav a {
    display: flex;
    align-items: center;
    min-height: 40px;
    font-size: var(--fs-16);
    line-height: var(--lh-16);
    color: var(--c-bg);
  }
  .nav a::after { display: none; }        /* no hover underline in the overlay */

  /* The toggle has to sit above the panel so it can close it. It already
     did - z-index 70 against the panel's 60 - but it kept showing the
     hamburger over an open menu, so there was nothing to say the menu could
     be closed at all. The overlay frame puts a 48x48 cross at x 330 y 12 of
     390, i.e. 12 in from the top right corner. */
  .nav-toggle { position: relative; z-index: 70; }
  .nav-toggle[aria-expanded="true"] {
    position: fixed;
    top: 12px;
    right: 12px;
    display: grid;
    place-items: center;
    width: 48px;
    height: 48px;
    padding: 0;
  }

  /* Above the breakpoint the overlay rules must all be undone, or the desktop
     nav inherits a fixed full-screen panel. */
}

/* ===================================================================
   The admin bar sits at z-index 99999 and is itself fixed, so it covers
   anything of ours pinned to the top of the viewport - the close cross
   landed underneath it and looked like it was missing. Only logged-in
   users ever see this, which is exactly who is testing the site.
   WordPress's own bar is 46 tall below 783 and 32 above it.
   =================================================================== */
@media (max-width: 782px) {
  body.admin-bar .nav { top: 46px; }
  body.admin-bar .nav-toggle[aria-expanded="true"] { top: 58px; }
}
@media (min-width: 783px) and (max-width: 1100px) {
  body.admin-bar .nav { top: 32px; }
  body.admin-bar .nav-toggle[aria-expanded="true"] { top: 44px; }
}

/* ===================================================================
   BESPOKE - the four steps, desktop. Measured off d-bespoke.png.

   The frame does not run four columns of photo-over-text. It is a
   checkerboard: two columns of two, filled DOWN each column so row one
   holds 01 and 03 and row two holds 02 and 04, and inside every step the
   photo sits beside the copy - right for 01 and 03, left for 02 and 04.

   Photos measure 251x249 at x 432 and 990 on the first row, 232 and 762 on
   the second, which is what gives the staggered look.

   Only the head keeps its own centring; the phone frame stacks these and is
   left alone below 900.
   =================================================================== */
@media (min-width: 900px) {
  /* `--pairs` only. This checkerboard is the BESPOKE frame; the homepage
     frame runs the same four steps as four plain columns, photo above copy.
     Written unscoped it took the homepage with it, which is what Milad saw:
     "قسمت BESBOKE & CUSTOM DESIGN پشت سر هم مثل فیگما نیست". */
  .process--pairs .steps {
    /* The two columns are NOT equal. Row one runs x172..682 and x722..1240
       inside a 1095 row - 510 and 518, with 27 of slack at the right that
       equal columns would swallow, pushing column two 18px right. */
    grid-template-columns: 46.58% 47.31%;   /* 510 and 518 of 1095 */
    grid-template-rows: auto auto;
    grid-auto-flow: column;      /* 01 02 down the left, 03 04 down the right */
    column-gap: 3.65%;           /* 40 */
    row-gap: 28px;               /* 2186 - 2158 */
    justify-content: start;
  }

  /* Two things per cell: a 236 copy block and a 250 photograph, 24 apart.
     The copy is SIZED rather than left to fill - the frame sizes the cell
     (510 for cell one) and letting the copy take the slack put the photo
     15px right of where the frame draws it.

     Inside the copy the NUMBER and the TITLE share a line, 12 apart. They do
     not on the homepage: F-S03 sets '01' at y610, a 210x1 rule at y622 and
     the title at y655. This band was inheriting that, which is what Milad
     was looking at - the Bespoke frame has no rule anywhere. */
  .process--pairs .steps .step {
    display: grid;
    /* percentages of the CELL, which is 510 wide - not of the 1057 row.
       Measuring them against the row gave a 113px photograph. */
    grid-template-columns: 46.27% 49.02%;   /* 236 and 250 of 510 */
    column-gap: 4.71%;                      /* 24 of 510 */
    align-items: center;
    justify-content: start;
    text-align: left;
  }
  .process--pairs .steps .step__copy {
    display: grid;
    grid-template-columns: auto 1fr;
    column-gap: 12px;   /* 204 - 192 */
    row-gap: 12px;      /* 2020 - 2008 */
  }
  .process--pairs .steps .step__row   { grid-column: 1; grid-row: 1; margin: 0; }
  .process--pairs .steps .step__title { grid-column: 2; grid-row: 1; margin: 0; text-align: left; }
  .process--pairs .steps .step__text  { grid-column: 1 / -1; grid-row: 2; margin: 0; text-align: left; }
  .process--pairs .steps .step__rule  { display: none; }
  .process--pairs .steps .step__nav   { display: none; }
  .process--pairs .steps .step__media {
    grid-column: 2;
    grid-row: 1;
    aspect-ratio: 1;             /* 250x250 */
    align-self: center;
    margin: 0;
  }

  /* 02 and 04 mirror: the photograph leads. The frame also nudges the second
     row right - cell one starts at x232 against row one's x172, cell two at
     x762 against x722 - which is what gives the band its stagger. */
  .process--pairs .steps .step:nth-child(even) { grid-template-columns: 49.02% 46.27%; }
  .process--pairs .steps .step:nth-child(even) .step__media { grid-column: 1; }
  .process--pairs .steps .step:nth-child(even) .step__copy  { grid-column: 2; }
  /* The stagger is an OFFSET, not a margin. A margin eats into the cell, so
     the 250 photograph came out 221 and the copy shifted with it; `left` on a
     relatively-positioned box moves it and leaves its width alone. The
     percentages resolve against the grid area, which is the cell. */
  .process--pairs .steps .step:nth-child(2),
  .process--pairs .steps .step:nth-child(4) { position: relative; }
  .process--pairs .steps .step:nth-child(2) { left: 11.76%; }   /* 232 - 172, of 510 */
  .process--pairs .steps .step:nth-child(4) { left: 7.72%; }    /* 762 - 722, of 518 */
}

/* ===================================================================
   The dark "who" band: heading on the page, panel starts at the cards.

   The frame sets the eyebrow, title and paragraph on the ordinary page
   background and only then opens the dark panel with its photograph. Ours
   had the whole section dark, so the copy sat over the picture in white -
   which is what Milad meant by the order being wrong.

   `.feature-cards__band` is the panel. On the light variant it is
   transparent and changes nothing.
   =================================================================== */
/* Only the dark variant needs the band to be a positioning context. On the
   light one the photo spans the whole section, so the band stays static and
   the picture keeps resolving against `.feature-cards` as it always did -
   wrapping it made the light photo shrink to the list's box. */
.feature-cards--dark .feature-cards__band { position: relative; isolation: isolate; }
.feature-cards--dark .feature-cards__band .feature-cards__bg {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  object-fit: cover;
  z-index: -2;
}

@media (min-width: 900px) {
  .feature-cards--dark { background: none; color: inherit; }
  /* The section-wide 35% black wash belongs to the phone layout, where the
     whole band is one dark photo. On desktop the page stays white, so the
     wash was greying out the entire section - heading, gaps and all. */
  .feature-cards--dark::before { display: none; }
  .feature-cards--dark .feature-cards__head { color: var(--c-ink); }
  .feature-cards--dark .feature-cards__head .body { color: var(--c-body); }
  .feature-cards--dark .feature-cards__head .eyebrow { color: var(--c-muted); }

  /* This band is the one place the desktop copy is CENTRED - the light band
     above it runs a left column, this one does not. The shared `.feature-cards
     __head { text-align: left }` a few hundred lines up was pulling it over. */
  .feature-cards--dark .feature-cards__head {
    text-align: center;
    max-width: 720px;
    margin-inline: auto;
  }

  /* The band is NOT a full-bleed dark panel and the cards are NOT a row.
     The frame keeps the page white and STACKS three dark cards inside the
     160 inset with white gaps between them: x 160..1280, cards 171/173/172
     tall, gaps 11 and 13, the block running 2810..3350.

     The three-column rule from the light band was still winning here, which
     is why the cards sat side by side. */
  .feature-cards--dark .feature-cards__band {
    position: relative;
    margin-inline: 0;
    padding: 0;
    color: var(--c-bg);
  }
  .feature-cards--dark .feature-cards__band .feature-cards__bg { display: none; }

  .feature-cards--dark .feature-cards__list {
    grid-template-columns: 1fr;
    margin-top: 44px;
    gap: 12px;
  }
  .feature-cards--dark .feature-card {
    min-height: 172px;
    display: flex;
    flex-direction: column;
    justify-content: center;
    text-align: center;
    padding: 24px 40px;
    background: var(--c-dark-soft);
  }

  /* Two cut-out chandeliers hang over the stack, one at each side. They are
     drawn ON TOP of the cards and run past the white gaps, so a string is
     visible against the dark card and again against the white gap below it -
     that only reads correctly as a transparent PNG over everything, never as
     a background behind the cards.

     Boxes measured off d-bespoke.png against the band's own 1120x540:
       left  x 226..359  y 2811..3277   ->  5.9% / 12.0% wide,  86.5% tall
       right x 1097..1196 y 2815..3313  -> 83.7% /  8.9% wide,  92.4% tall
     Percentages, not pixels, so they keep their place when the band narrows.

     The `[class]` on each selector is not decoration: Elementor ships
     `.elementor img { height: auto }` at (0,1,1), which beats a plain class
     and would collapse these to the file's own height. See ch.18. */
  .feature-cards__chand[class] {
    position: absolute;
    z-index: 2;
    pointer-events: none;
    object-fit: fill;
  }
  .feature-cards__chand--l[class] {
    left: 5.9%;
    top: 0.2%;
    width: 12%;
    height: 86.5%;
  }
  .feature-cards__chand--r[class] {
    left: 83.7%;
    top: 0.9%;
    width: 8.9%;
    height: 92.4%;
  }
}

/* ===================================================================
   The dark band on the 390 frame.

   It is NOT a full-bleed dark photo section - that is what we had, and it
   is why the heading sat on a grey wash. The phone frame is the same idea
   as the desktop one: white page, three dark panels at x 20..369, 170 tall,
   16 apart, copy centred.

   The one real difference is the chandeliers. On desktop they run straight
   over the white gaps. Here every gap is clean white, so they cannot be a
   pair of <img>s on top - each panel paints its own slice of them instead,
   with an offset of its own, and the gaps get nothing. Two backgrounds per
   panel, from the same two files the desktop <img>s use.

   Block geometry the offsets are built on:
     panel 1  0..170     panel 2  186..356     panel 3  372..542
   Amber cascade: 101 x 527 at x 123, y 1 of the block.
   Rod cluster:   85 x 427, only inside panel 2, showing its middle 170.
   =================================================================== */
@media (max-width: 899px) {
  /* the band-level pair is the desktop composition; the phone uses the
     per-card copies below */
  .feature-cards__band > .feature-cards__chand { display: none; }

  .feature-cards--dark { background: none; color: inherit; }
  .feature-cards--dark::before { display: none; }
  .feature-cards--dark .feature-cards__head { color: var(--c-ink); }
  .feature-cards--dark .feature-cards__head .body { color: var(--c-body); }
  .feature-cards--dark .feature-cards__head .eyebrow { color: var(--c-muted); }
  .feature-cards--dark .feature-cards__band { color: var(--c-bg); }
  .feature-cards--dark .feature-cards__band .feature-cards__bg { display: none; }

  .feature-cards--dark .feature-card {
    position: relative;
    overflow: hidden;
    min-height: 170px;
    display: flex;
    flex-direction: column;
    justify-content: center;
    text-align: center;
    background: var(--c-dark-soft);
  }

  /* The card is the window; the pictures hang out of it and get clipped.
     Ordered before the copy in the markup so the text stays on top without
     a z-index on every heading. */
  .feature-card__chands { position: absolute; inset: 0; pointer-events: none; }
  .feature-card__chands .feature-cards__chand[class] {
    position: absolute;
    left: 50%;
    object-fit: fill;
  }

  /* Centred, not at the frame's x 123. Measured on the 390 frame the
     cascade's centre (193.5) is the panel's centre (194.5), so centring holds
     at every width where a fixed offset would drift as the panel grows. The
     rods sit 21 left of centre, so they say so. */
  .feature-card__chands .feature-cards__chand--l[class] {
    margin-left: -50px;
    width: 101px;
    height: 527px;
  }
  .feature-card__chands .feature-cards__chand--r[class] {
    margin-left: -63px;
    width: 85px;
    height: 427px;
  }

  /* One cascade runs the whole block - panel 1 at 0, panel 2 at 186,
     panel 3 at 372 - so each card shows its own part of it. */
  .feature-card:nth-child(1) .feature-cards__chand--l[class] { top: 1px; }
  .feature-card:nth-child(2) .feature-cards__chand--l[class] { top: -185px; }
  .feature-card:nth-child(3) .feature-cards__chand--l[class] { top: -371px; }

  /* The rods belong to the middle panel only. */
  .feature-card:nth-child(2) .feature-cards__chand--r[class] { top: -128px; }
  .feature-card:nth-child(1) .feature-cards__chand--r,
  .feature-card:nth-child(3) .feature-cards__chand--r { display: none; }
}

/* On desktop the band carries the pair; the per-card copies stand down. */
@media (min-width: 900px) {
  .feature-card__chands { display: none; }
}

/* The steps band has its own surface - rgb(248,248,248) against the white
   band above it, sampled in the gutters of d-bespoke.png where no content
   sits. Ours was rendering on the page background, so the band did not read
   as a band at all. */
.process { background: #F8F8F8; }

/* ===================================================================
   Gallery tiles: the hover state from the frame.

   The frame shows a band across the foot of the tile carrying the project's
   name and where it is, and the tile opens that project. It fades in on
   hover and is always shown on touch, where there is no hover to give.

   The designer wrote the motion down next to the frame, so it is copied
   literally rather than reached for with our own tokens:

       Animation : Dissolve      -> opacity, nothing slides in
       Easing    : Ease out
       Duration  : 500ms

   `--dur`/`--ease` are the theme's own defaults (450ms, a custom curve) and
   were close but not what was asked for. A spec that names a number is not
   a suggestion.
   =================================================================== */
.gallery__tile,
.project-rail__tile { position: relative; display: block; overflow: hidden; }

/* "و عکس کمی زوم میشود" - the photo grows a little under the label. Same
   500ms so the two halves of the hover finish together. */
.gallery__tile img,
.project-rail__tile img { transition: transform 500ms ease-out; }
.gallery__tile:hover img,
.gallery__tile:focus-visible img,
.project-rail__tile:hover img,
.project-rail__tile:focus-visible img { transform: scale(1.05); }

/* Read off the hover artboards - `p01` 101:13519 and its three siblings,
   552x347, the same four refs as the resting tiles:

     band   x0 y269  552x78   #212121 at 35%, effect GLASS
     name   y281  Plus Jakarta Sans 500 18/28 #F0F1F2  centred
     place  y313  Plus Jakarta Sans 500 14/22 #F0F1F2  centred

   12 + 28 + 4 + 22 + 12 = 78, so the padding and the gap give the band its
   height rather than a stated one.

   It was a dark gradient before, PP Editorial Old bold at 16/24. The frame
   wants a flat translucent panel with a frosted backdrop - which is what
   Milad meant by "بلور میشه". GLASS carries no radius in the file, so the
   8px blur is the one number here the frame does not state. */
.tile-meta {
  position: absolute;
  inset: auto 0 0 0;
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 4px;                        /* 313 - 309 */
  padding: 12px 20px;              /* 281 - 269, and 347 - 335 */
  background: rgba(33, 33, 33, .35);
  -webkit-backdrop-filter: blur(8px);
  backdrop-filter: blur(8px);
  color: #F0F1F2;
  opacity: 0;
  transition: opacity 500ms ease-out;
}
.gallery__tile:hover .tile-meta,
.gallery__tile:focus-visible .tile-meta,
.project-rail__tile:hover .tile-meta,
.project-rail__tile:focus-visible .tile-meta { opacity: 1; }

/* The Bespoke "Brought to Life" grid on the phone frame is images only - the
   name and place come off the cards entirely (desktop keeps them on hover).
   Scoped to `.gallery` so the Projects rail is untouched. */
@media (max-width: 720px) {
  .gallery .tile-meta { display: none; }
}

/* The phone frame keeps the booklet's email box and button SIDE BY SIDE across
   the full width (field on the left, button on the right), not stacked - an
   earlier stack was wrong. Let the row fill the band and drop the button's
   min-width so both fit on one line without cramping. */
@media (max-width: 520px) {
  .booklet .mifa-form--inline { flex-wrap: nowrap; align-items: stretch; gap: 8px; }
  .booklet .mifa-form--inline .mifa-form__grid { flex: 1 1 auto; min-width: 0; }
  /* Compact button so the email box keeps a usable width beside it - the label
     is long ("Receive the Profile") and at the default padding it ate the field
     down to ~100px. */
  .booklet .mifa-form--inline .mifa-form__submit {
    flex: 0 0 auto;
    min-width: 0;
    white-space: nowrap;
    padding-inline: 12px;
    font-size: 13px;
  }
}

.tile-meta__name {
  font-family: var(--font-body);   /* Plus Jakarta Sans, not the serif */
  font-weight: var(--fw-medium);
  font-size: var(--fs-18);
  line-height: var(--lh-18);
  color: #F0F1F2;
}
.tile-meta__place {
  font-size: var(--fs-14);
  line-height: var(--lh-14);
  font-weight: var(--fw-medium);
  color: #F0F1F2;
}

/* No hover on a touch screen, so the label is simply always there and the
   photo never zooms - there is no state to return from. */
@media (hover: none) {
  .tile-meta { opacity: 1; }
  .gallery__tile img,
  .gallery__tile:hover img,
  .project-rail__tile img,
  .project-rail__tile:hover img { transition: none; transform: none; }
}

/* Someone who has asked their system to stop animations gets the end state
   with no travel. The label still appears - it is information, not decoration. */
@media (prefers-reduced-motion: reduce) {
  .gallery__tile img,
  .project-rail__tile img,
  .tile-meta { transition: none; }
  .gallery__tile:hover img,
  .project-rail__tile:hover img { transform: none; }
}


/* ===================================================================
   COLLECTION CONFIGURATOR — measured off d-pearl-wave.png.

     rail    x 152..235   cards 84 wide, 98 apart
     stage   x 272..990   719 x 457
     thumbs  161 x 136, 6 apart, running off the right edge (a scroller)
     specs   x 1027..1290

   The whole thing is one grid: rail, then a middle column holding the stage
   and its strip, then the specification. Every type is printed as its own
   panel and all but the first carry `hidden`, so with no JavaScript the
   section is still a readable stack rather than an empty box.
   =================================================================== */
/* `.section` pads 100 of its own and `.configurator__inner` pads again, so
   the band was carrying both - the photo opened on 250 where the frame puts
   it on 150. The inner is the one that holds the frame's number. */
.configurator.section { padding-block: 0; }
.configurator { background: var(--c-bg); }
.configurator__inner { padding-block: 100px; }

.configurator__rail {
  display: flex;
  gap: 8px;
  overflow-x: auto;
  scrollbar-width: none;
}
.configurator__rail::-webkit-scrollbar { display: none; }

.configurator__type {
  flex: 0 0 auto;
  display: grid;
  justify-items: center;
  /* the frame's tile is 83x87: 4 above a 53-tall icon, 5, a 22 label, 3 */
  gap: 5px;
  width: 83px;
  padding: 4px 6px 3px;
  border: 0;
  background: none;
  color: var(--c-muted);
  cursor: pointer;
  /* the frame's active card is a white tile with a hairline, so the border is
     always there and only its colour changes - otherwise the label shifts by
     a pixel every time you pick a different type */
  border: 1px solid transparent;
  border-radius: var(--radius-sm);
  transition: background .2s var(--ease), color .2s var(--ease);
}
.configurator__type-icon img {
  display: block;
  width: 64px;
  height: 53px;
  object-fit: contain;
}
.configurator__type-label {
  font-size: var(--fs-14);
  line-height: var(--lh-14);
}
/* The frame outlines EVERY tile - #B7B9BE at rest, #3B3D42 with a #FCFCFC
   fill on the open one. Ours drew nothing at rest and a pale #DDDEE1 when
   open, so the five types read as loose artwork rather than as a control. */
.configurator__type { border-color: #B7B9BE; }
.configurator__type.is-active {
  background: var(--c-surface);
  border-color: #3B3D42;
  color: var(--c-ink);
}
.configurator__type:focus-visible { outline: 2px solid var(--c-accent); outline-offset: 2px; }

.configurator__panel { margin-top: 32px; }
.configurator__panel[hidden] { display: none; }

.configurator__stage img {
  display: block;
  width: 100%;
  height: 100%;
  object-fit: cover;
}

.configurator__thumbs {
  display: flex;
  gap: 6px;
  margin-top: 6px;
  overflow-x: auto;
  scroll-snap-type: x mandatory;
  scrollbar-width: none;
}
.configurator__thumbs::-webkit-scrollbar { display: none; }
.configurator__thumb {
  flex: 0 0 161px;
  scroll-snap-align: start;
  padding: 0;
  border: 2px solid transparent;
  background: none;
  cursor: pointer;
  overflow: hidden;
}
.configurator__thumb img {
  display: block;
  width: 100%;
  height: 100%;
  object-fit: cover;
}
.configurator__thumb.is-active { border-color: var(--c-ink); }
.configurator__thumb:focus-visible { outline: 2px solid var(--c-accent); outline-offset: 2px; }

.configurator__title {
  margin: 0;
  font-family: var(--font-display);
  font-size: var(--fs-20);
  line-height: var(--lh-20);
  color: var(--c-ink);
}
.configurator__text {
  margin: 12px 0 0;
  font-size: var(--fs-14);
  line-height: var(--lh-14);
  color: var(--c-body);
}

.configurator__rows { margin: 24px 0 0; }
.configurator__row {
  padding-block: 14px;
  border-top: 1px solid var(--c-line);
}
.configurator__row:last-child { border-bottom: 1px solid var(--c-line); }
.configurator__row dt {
  font-size: var(--fs-12);
  line-height: var(--lh-12);
  letter-spacing: .06em;
  text-transform: uppercase;
  color: var(--c-muted);
}
.configurator__row dd {
  margin: 8px 0 0;
  font-size: var(--fs-14);
  line-height: var(--lh-14);
  color: var(--c-ink);
}

.configurator__swatches { display: flex; gap: 6px; }
.configurator__swatch {
  display: block;
  width: 32px;
  height: 32px;
  overflow: hidden;
  background: var(--c-warm-tint);
}
.configurator__swatch img {
  display: block;
  width: 100%;
  height: 100%;
  object-fit: cover;
}
.configurator__material { display: flex; align-items: center; gap: 10px; }
/* Several materials stack, one swatch and name per line. The swatch keeps its
   32 when a long name wraps beside it. */
.configurator__materials { display: flex; flex-direction: column; gap: 8px; }
.configurator__material .configurator__swatch { flex: 0 0 32px; }

/* The numbers that used to be here - "rail 152..235, stage 272..990, specs
   1027..1290" - were read off a PNG. The FILE (101:12045, 1440x948) says:

     rail     x254  83 wide, buttons 83x87 twelve apart, centred on the photo
     photo    x381  500x500 on y150
     thumbs   x381  140x140 on y658, eight apart, running off to the right
     details  x925  260 wide, opening on y236
     band     150 above the photo and 150 under the thumbnails

   so the stage was 719x457 where the frame draws a 500 square, and the whole
   block sat 100 too far left. */
@media (min-width: 900px) {
  .configurator__inner {
    display: grid;
    grid-template-columns: 83fr 44fr 500fr 44fr 260fr;
    align-items: start;
    padding-block: 150px;
    padding-inline: 17.64% 17.71%;   /* 254 and 255 of 1440 */
  }
  .configurator__rail {
    grid-column: 1;
    grid-row: 1;
    flex-direction: column;
    gap: 12px;
    overflow: visible;
    align-self: center;              /* rail 232..715 against photo 150..798 */
  }
  .configurator__type { width: 100%; padding: 4px 6px 3px; }

  /* Every panel is placed in the SAME cell. They are mutually exclusive at
     runtime, so stacking them keeps the grid from reserving a row per type
     and leaving four gaps below the open one. */
  .configurator__panel {
    grid-column: 3 / -1;
    grid-row: 1;
    margin-top: 0;
    display: grid;
    grid-template-columns: 500fr 44fr 260fr;
    align-items: start;
    column-gap: 0;
  }
  .configurator__stage  { grid-column: 1; grid-row: 1; aspect-ratio: 1; }
  .configurator__thumbs { grid-column: 1; grid-row: 2; margin-top: 8px; }
  /* Carousel: size each thumb so exactly 4.5 fit the strip - four whole and
     the fifth peeking half out, whatever the width, so a type with more than
     four photos always reads as scrollable. (4 gaps of 6 between 4.5 items.) */
  .configurator__thumb  { flex: 0 0 calc((100% - 24px) / 4.5); height: auto; aspect-ratio: 1; }
  /* margin, not padding: the details block itself opens on 236 against the
     photo's 150, and padding would have moved only its text. */
  .configurator__specs  { grid-column: 3; grid-row: 1 / span 2; margin-top: 86px; }
}

/* The 390 frame (Frame collection, 390x982):
     rail    100  five 64x65 tiles on a ~71 pitch from x20, icon 45x37, label 12/18
     photo   185  350x350
     thumbs  543  100x100, eight apart
     details 663  title 20/30, rows under it
     band    100 above the rail and 60 under the details */
@media (max-width: 899px) {
  .configurator__inner { padding-block: 100px 60px; padding-inline: 20px; }
  .configurator .configurator__type-icon img,
  .configurator__type-icon img { width: 45px; height: 37px; }
  .configurator__type {
    width: 64px;
    padding: 4px 0;
    gap: 2px;
  }
  .configurator__type-label { font-size: var(--fs-12); line-height: var(--lh-12); }
  .configurator__rail { gap: 7px; }
  .configurator__stage { aspect-ratio: 1; }
  /* carousel half-peek, like desktop: 3.5 thumbs fit (3 whole + half of the
     fourth) so a strip of 5+ photos always reads as scrollable. */
  .configurator__thumb { flex: 0 0 calc((100% - 18px) / 3.5); height: auto; aspect-ratio: 1; }
  /* photo closes 535, thumbs 543..643, the details open on 663 */
  .configurator__specs { margin-top: 20px; }
  /* The 390 frame fits all five types across the width - they share it
     rather than scrolling. Its tile is 64x65: 4 above a 45x37 icon, 2, a
     12/18 label, 4. The 10/2 padding and the 32px icon here were a guess
     from before the frame was read, and made the tile 79. */
  .configurator__type {
    flex: 0 0 64px;
    width: 64px;
    min-width: 0;
    padding: 4px 0;
    gap: 2px;
  }
  .configurator__type-icon img { width: 45px; height: 37px; }
  .configurator__rail { gap: 7px; }   /* five 64s across 350 */

  /* The details pair up on a phone: DIMENSION beside CRYSTAL QUANTITY and
     CRYSTAL COLOR beside CRYSTAL MATERIAL, two 169-wide cells 12 apart.
     Stacked one per line the block ran 439 against the frame's 259. */
  .configurator__rows {
    display: grid;
    /* minmax(0, 1fr), not 1fr: a bare 1fr will not go below its content, so the
       six 32px colour swatches (222 in a row) plus the three material names held
       the pair at 359 inside 320 - a 360 phone scrolled sideways by 19. */
    grid-template-columns: minmax(0, 1fr) minmax(0, 1fr);
    gap: 12px;
  }
  .configurator__row { padding-block: 4px 12px; }
  .configurator__row:last-child { border-bottom: 0; }
  /* ...so the swatches wrap to a second line, each kept square, and a long
     material name breaks inside its cell instead of widening it */
  .configurator__swatches { flex-wrap: wrap; }
  .configurator__swatches > .configurator__swatch { flex: 0 0 32px; }
  .configurator__row dd { overflow-wrap: anywhere; }
  .configurator__material > span:last-child { min-width: 0; }
}

/* ===================================================================
   PHOTO-OR-VIDEO BAND

   One box whichever it holds, so swapping a still for a clip does not move
   the page around it. The ratio comes in as a custom property from the
   widget rather than a class per shape.
   =================================================================== */
/* Bespoke's "Design Intelligence" head. Centred, and narrower than the
   container: the frame sets the heading and the paragraph to 682 inside a
   1280 column, which is 53.3%. The eyebrow rides above both. */
/* Band y2536..3470, 934 tall:
     y100  eyebrow    Plus Jakarta Sans 400 12/18 #7D818A
     y118  title      PP Editorial Old 400 28/42 #1E1F21, 682 wide
     y176  paragraph  Plus Jakarta Sans 400 16/24 #6B6E76
     y292  photograph 1280x542
   100 above the eyebrow, 68 between the copy and the photo, 100 below it.
   Without those the band ran 703 against the frame's 934. Only when the band
   carries a head - the collection pages use the same part with none. */
.media-band__head {
  width: 53.28%;   /* 682 of the 1280 content column */
  min-width: 260px;
  margin: 100px auto 44px;   /* copy closes 248, the photo opens 292 */
  text-align: center;
}
.media-band--headed { margin-bottom: 100px; }
/* The photograph is inset like every other content block - 1280 of a 1440
   frame - not full-bleed. It only looked right while the image was falling
   back to its own height; once it filled the box the band ran 93 long. */
.media-band--headed { margin-inline: var(--pad-80); }
/* The frame sets the title straight under the eyebrow at both sizes -
   1440: eyebrow 100..118, title 118. 390: 60..78, title 78. */
.media-band__head .eyebrow { margin: 0; }
.media-band__head .h-section { margin: 0; }
.media-band__head .body { margin: 16px 0 0; }

.media-band {
  position: relative;
  aspect-ratio: var(--media-ratio, 1440 / 650);
  overflow: hidden;
  background: var(--c-dark-soft);
}
.media-band > img,
.media-band__video {
  display: block;
  width: 100%;
  height: 100%;
  object-fit: cover;
}
/* Elementor ships `.elementor img { height: auto }` - the same weight as the
   rule above, and its stylesheet loads after ours, so inside a builder page
   the band's photo fell back to its own aspect and left the rest of the box
   empty. One class deeper settles it. */
.elementor .media-band > img { height: 100%; }
.media-band__player { position: absolute; inset: 0; }

/* The play button. Sized for a thumb at 64, and it steps out of the way
   once the video is running so it does not sit over the picture. */
.media-band__play {
  position: absolute;
  inset: 50% auto auto 50%;
  transform: translate(-50%, -50%);
  display: grid;
  place-items: center;
  width: 64px;
  height: 64px;
  padding: 0;
  border: 0;
  border-radius: 50%;
  background: rgba(252, 252, 252, .9);
  color: var(--c-ink);
  cursor: pointer;
  transition: opacity .3s var(--ease), transform .3s var(--ease);
}
.media-band__play svg { width: 26px; height: 26px; margin-inline-start: 3px; }
.media-band__play:hover { transform: translate(-50%, -50%) scale(1.06); }
.media-band__play:focus-visible { outline: 2px solid var(--c-surface); outline-offset: 3px; }
.media-band__player.is-playing .media-band__play {
  opacity: 0;
  pointer-events: none;
}

/* A silent background loop has no controls to reach, so it should not be a
   tab stop or advertise itself as interactive. */
.media-band__player.is-auto .media-band__video { pointer-events: none; }

@media (prefers-reduced-motion: reduce) {
  .media-band__play { transition: none; }
  .media-band__play:hover { transform: translate(-50%, -50%); }
}

/* ===================================================================
   The three pictures floating on the configurator's stage.

   A translucent dark panel low on the left of the photograph, holding three
   small squares - measured off d-pearl-wave.png: panel 290..445 x
   3099..3160 against a stage of 272..990 x 2722..3179, chips about 40x44
   with 8 between them. Sampled panel colour rgb(83,45,41) over a warm wall
   and rgb(46,24,26) over dark marble, i.e. it is translucent, not flat.
   =================================================================== */
.configurator__views {
  position: absolute;
  inset: auto auto 4% 2.5%;
  display: flex;
  gap: 8px;
  padding: 9px;
  background: rgba(30, 20, 20, .55);
  backdrop-filter: blur(2px);
}
.configurator__view {
  width: 40px;
  height: 44px;
  padding: 0;
  border: 0;
  background: var(--c-surface);
  cursor: pointer;
  overflow: hidden;
  transition: transform .2s var(--ease);
}
.configurator__view:hover { transform: scale(1.06); }
.configurator__view:focus-visible { outline: 2px solid var(--c-surface); outline-offset: 2px; }
.configurator .configurator__view img {
  display: block;
  width: 100%;
  height: 100%;
  object-fit: cover;
}

/* The stage has to be the positioning context for them, and they must sit
   over the photograph rather than under it. */
.configurator__stage { position: relative; }

@media (prefers-reduced-motion: reduce) {
  .configurator__view { transition: none; }
  .configurator__view:hover { transform: none; }
}

@media (max-width: 899px) {
  /* smaller stage, so smaller chips - the panel would otherwise take a
     quarter of the picture */
  .configurator__view { width: 32px; height: 36px; }
  .configurator__views { gap: 6px; padding: 7px; }
}

/* The contact band's colour is a per-page choice in the design, not a
   constant. Sampled: #F0F1F2 on the homepage (the page colour, so no class),
   #F8F8F8 on a collection page, #FFFFFF on Bespoke. */
.contact--soft  { background: #F8F8F8; }
.contact--white { background: #FFFFFF; }

/* ===================================================================
   CONTACT PAGE — the card and the form are ONE block, side by side.

   Measured off d-contact.png:
     intro    centred, 889..1099
     card     x 170..719  (550)   y 1094..2099
     form     x 764..1226 (462)
     surface  #FFFFFF, one block - not a white card over a grey band

   We had two stacked sections, the second on #F0F1F2, so the page read as
   two unrelated blocks. `--with-form` puts them in one row.
   =================================================================== */
.contact-details--with-form .contact-details__intro { text-align: center; }

@media (min-width: 900px) {
  .contact-details--with-form .container {
    display: grid;
    /* 550 : 45 : 462 of the 1440 frame, inside a 170/214 inset */
    grid-template-columns: 550fr 45fr 462fr;
    align-items: start;
    padding-inline: 11.8% 14.9%;
  }
  .contact-details--with-form .contact-details__intro {
    grid-column: 1 / -1;
    max-width: 720px;
    margin-inline: auto;
    margin-bottom: 56px;
  }
  .contact-details--with-form .contact-details__card { grid-column: 1; }
  .contact-details--with-form .contact-details__form { grid-column: 3; }
}

/* The form column's own head is centred in the frame, like the card's. */
.contact-details__form > .h-section { margin: 0; text-align: center; }
.contact-details__form > .body {
  margin: 8px 0 24px;
  text-align: center;
  color: var(--c-body);
}

@media (max-width: 899px) {
  .contact-details--with-form .contact-details__form { margin-top: 40px; }
}

/* -------------------------------------------------------------------
   Contact page, desktop geometry — read off frame 101:13533 (1440x1045),
   measured from the BAND's own top rather than the page's.

     title            100   28/42 centred
     paragraph        166   18/28, 896 wide
     photo/card       314   x170  550x631
       heading        378   x248  24/36 #6B6E76
       rows           462 · 534 · 614   icon box 48, text x293, 18/28
       map link       682   x320  16/24 #6B6E76
       "Follow us"    750   x248  18/28 #6B6E76
       social         794   x248  40 squares on a 52 pitch
     form column      x764 w462
       "Ask us..."    358   24/36 centred
       paragraph      398   14/22 centred
       first label    463   14/22

   We had the whole card at 16/24 and its heading at 28/42, the paragraph
   clamped to 720, and the form column aligned to the top of the row instead
   of 44 below it.
   ------------------------------------------------------------------- */
@media (min-width: 900px) {
  .contact-details--with-form { padding-block: 100px; }

  .contact-details--with-form .contact-details__intro {
    max-width: 896px;
    /* the paragraph is three lines of 18/28 in the frame too, so the intro
       closes at 250 and the card opens at 314 */
    margin-bottom: 64px;
  }
  .contact-details--with-form .contact-details__intro .h-section { margin-top: 0; }
  .contact-details--with-form .contact-details__intro .body {
    margin-top: 24px;
    max-width: none;
    font-size: var(--fs-18);
    line-height: var(--lh-18);
  }

  .contact-details--with-form .contact-details__card {
    margin-top: 0;
    max-width: 550px;
    /* left 64 puts the icon boxes on x234; the heading and the social row
       step in a further 14 to x248, which is what the frame does. */
    padding: 64px 64px 111px;
  }
  .contact-details--with-form .contact-details__heading {
    margin: 0 0 48px 14px;
    font-size: var(--fs-24);
    line-height: var(--lh-24);
    color: var(--c-muted);
  }
  .contact-details--with-form .contact-details__list { gap: 24px; }
  /* Rows are 48 · 56 · 100 in the frame with 24 between them, which is what
     closes the list on 714 and puts "Follow us" on 750. The middle row IS
     eight taller than the first one - that is the frame, not a rounding. */
  .contact-details--with-form .contact-details__item { min-height: 48px; }
  .contact-details--with-form .contact-details__item:nth-child(2) { min-height: 56px; }
  .contact-details--with-form .contact-details__item {
    grid-template-columns: 48px 1fr;
    gap: 11px;
    font-size: var(--fs-18);
    line-height: var(--lh-18);
  }
  .contact-details--with-form .contact-details__item svg {
    width: 24px;
    height: 24px;
    justify-self: center;
    align-self: center;
  }
  .contact-details--with-form .contact-details__map {
    /* address closes 670, the link's own 40 box opens 674 and its words sit
       on 682; x320 against the address's x293 */
    margin: 4px 0 0 27px;
    min-height: 40px;
    display: grid;
    align-content: center;
  }
  .contact-details--with-form .contact-details__follow {
    margin: 36px 0 16px 14px;
    font-size: var(--fs-18);
    line-height: var(--lh-18);
  }
  .contact-details--with-form .contact-details__social { margin-inline-start: 14px; }

  /* The form column is not flush with the card: its heading sits 44 lower. */
  .contact-details--with-form .contact-details__form { margin-top: 44px; }
  .contact-details--with-form .contact-details__form > .h-section {
    font-size: var(--fs-24);
    line-height: var(--lh-24);
  }
  .contact-details--with-form .contact-details__form > .body {
    margin: 4px 0 21px;
    font-size: var(--fs-14);
    line-height: var(--lh-14);
  }
}

/* -------------------------------------------------------------------
   Contact page on a phone — frame 101:7748 (390x750), the details band.

     title           60   x28  24/36
     paragraph      108   x28  14/22, six lines
     heading        284   x28  20/30 #6B6E76
     phone          348   x87  18/28
     email          420   x87
     address        482   x87  two lines
     map link       550   x108 16/24 #6B6E76
     "Follow us"    606   x42  18/28 #6B6E76
     social         650   x42  40 squares on a 52 pitch

   The photograph behind all of it is the band's own, at 15%.
   ------------------------------------------------------------------- */
@media (max-width: 720px) {
  .contact-details--with-form { padding-block: 60px; }
  .contact-details--with-form .contact-details__bg { opacity: .15; }

  .contact-details--with-form .contact-details__intro { margin-bottom: 12px; }
  .contact-details--with-form .contact-details__intro .body { margin-top: 12px; }
  /* The card carries `margin-top: 46px` of its own, and sibling margins
     collapse to the LARGER of the two - so setting the intro's alone left
     everything below it 34 low. */
  .contact-details--with-form .contact-details__card { margin-top: 12px; }

  .contact-details--with-form .contact-details__heading {
    margin: 0 0 34px;
    font-size: var(--fs-20);
    line-height: var(--lh-20);
    color: var(--c-muted);
  }
  .contact-details--with-form .contact-details__list { gap: 34px; }
  .contact-details--with-form .contact-details__item {
    grid-template-columns: 24px 1fr;
    gap: 21px;            /* icon on 42, text on 87 */
    font-size: var(--fs-18);
    line-height: var(--lh-18);
  }
  /* The first row is ten taller than its text in the frame, which is what
     puts the email on 420 with a single 34 gap for both. */
  .contact-details--with-form .contact-details__item:first-child { min-height: 38px; }
  .contact-details--with-form .contact-details__map {
    margin: 12px 0 0 21px;   /* address x87, this x108 */
  }
  .contact-details--with-form .contact-details__follow {
    margin: 32px 0 16px 14px;
    font-size: var(--fs-18);
    line-height: var(--lh-18);
  }
}

/* -------------------------------------------------------------------
   Contact page on a phone — F-S06 (101:7782, 390x740), the form band.

   In the frame this is a SEPARATE band from the details above it: the
   photograph stops at 750 and the form sits on plain page background, on a
   40 gutter rather than the 28 the copy above it uses.

     title          834   x40  24/36 centred        (84 into the band)
     paragraph      878   x40  14/22 centred, two lines
     first label    943   x40  14/22
     first input    969   x40  310x40, 1px #B7B9BE, placeholder 12/18 at x54
     field pitch     82   (label 22 · 4 · input 40 · 16)
     textarea      1215   310x100, 1px #DDDEE1
     Send          1340   310x40  #500F28, 16/24 #FCFCFC
     note          1388   12/18 #7D818A centred
     band closes   1490

   Two mechanics make this work:

   1. The photograph moves off the CARD and onto the SECTION, so it also
      covers the title and paragraph the way the frame does. The card gives
      up `position: relative` AND `isolation` - keeping the isolation would
      make the card a stacking context and the z-index:-2 photo would paint
      over the copy above it instead of behind everything.
   2. The form then paints its own page-coloured background edge to edge,
      which is what ends the photograph on 750 without hard-coding a height.
   ------------------------------------------------------------------- */
@media (max-width: 720px) {
  .contact-details--with-form {
    position: relative;
    isolation: isolate;
    padding-bottom: 0;      /* the form band carries the closing 84 */
  }
  .contact-details--with-form .contact-details__card {
    position: static;
    isolation: auto;
    padding-bottom: 60px;   /* icons close 690, the band closes 750 */
    /* `.reveal` puts a translate on this card, and ANY transform makes an
       element the containing block for its absolutely positioned children -
       so the photograph stayed pinned to the card no matter what `position`
       said. Worse, `.reveal.is-in` ends on `transform: none`, so the photo
       would have jumped from the card's box to the section's the moment the
       card scrolled into view. Dropping the slide here (the fade stays) is
       what actually frees it. */
    transform: none;
  }

  .contact-details--with-form .contact-details__form {
    position: relative;
    /* the frame gives this band no fill of its own, so it is the page - and
       `body.page--contact` is white, not the site's #F0F1F2 */
    background: #FFFFFF;
    margin: 0 -28px;
    padding: 84px 40px;
  }
  /* body closes 878+44=922 and the first label is on 943 */
  .contact-details--with-form .contact-details__form > .body { margin-bottom: 21px; }
  /* the textarea closes 1315 and the button opens 1340 */
  .contact-details--with-form .contact-details__form .mifa-form__actions,
  .contact-details--with-form .contact-details__form button[type="submit"],
  .contact-details--with-form .contact-details__form .btn { margin-top: 25px; }
}

/* ===================================================================
   The header on a phone.

   `.site-header { padding-block: 40px }` and `.site-header .container
   { padding-inline: 80px }` were written with no media query, so a 390
   screen kept the desktop's 80 gutter and an 127-tall bar. The logo started
   at x 80 against the frame's 20, and the breadcrumb - which is pinned at
   top 88 - landed INSIDE the header and sat over the logo.

   The 390 frame: logo at x 20, header about 80 tall, breadcrumb clear of it.
   =================================================================== */
@media (max-width: 720px) {
  .site-header { padding-block: 20px; }
  .site-header .container { padding-inline: 20px; gap: 16px; }
}

/* ===================================================================
   ARTICLE PAGE — off d-news-vela.png.

   The frame centres the heading AND the body copy in a narrow column, then
   runs a photo mosaic nearly the full page width under it:

     copy    centred, about 682 wide on 1440
     row 1   two photos, 80..707 and 732..1359, 473 tall
     row 2   one photo full width, 80..1359
     row 3   two photos again

   So the pattern is two, one, two - every third photo spans both columns.
   Ours had left-ranged copy and no mosaic at all.
   =================================================================== */
.article__body .article__title { text-align: center; }
.article__body .entry { text-align: center; }

.article__mosaic {
  display: grid;
  gap: 24px;
  margin-top: 56px;
  padding-inline: var(--pad-80);
}
.article__shot { margin: 0; }
.article__shot img {
  display: block;
  width: 100%;
  height: 100%;
  object-fit: cover;
}

@media (min-width: 900px) {
  /* Two even columns, NOT `628fr 24fr 628fr`. The middle track was standing
     in for the 24px channel between the photos, but `gap: 24px` above already
     draws a channel either side of it - so the real separation came out at
     24 + 24 + 24 = 71px and each photo lost 24px of width (597 against the
     frame's 628). The gap alone does the job. */
  .article__mosaic {
    grid-template-columns: 1fr 1fr;
    /* 80 either side of a 1440 frame */
    padding-inline: 5.6%;
    margin-top: 72px;
  }
  .article__shot { grid-column: 1; aspect-ratio: 628 / 472; }
  .article__shot:nth-child(2n) { grid-column: 2; }
  /* every third one runs the full width and resets the alternation, which is
     what makes the frame's two-one-two read the way it does */
  .article__shot:nth-child(3n) {
    grid-column: 1 / -1;
    aspect-ratio: 1280 / 472;
  }
  .article__shot:nth-child(3n+1) { grid-column: 1; }
  .article__shot:nth-child(3n+2) { grid-column: 2; }
}

@media (max-width: 899px) {
  /* one under another on a phone, which is what the 390 frame does */
  .article__mosaic { gap: 16px; margin-top: 40px; }
  .article__shot { aspect-ratio: 350 / 260; }
}

/* The mosaic is Elementor-free, but the article body can carry Elementor
   content, so the same `.elementor img { height: auto }` weight applies. */
.article__mosaic .article__shot img { height: 100%; }


/* ===================================================================
   The four steps as the HOMEPAGE frame draws them: four plain columns,
   photograph above the number, the rule, the title and the copy. 244x305
   photos at x 184, 460, 736 and 1012 of the 1440 frame - so 244 wide on a
   276 pitch, i.e. 32 between them.
   =================================================================== */
@media (min-width: 900px) {
  .process:not(.process--pairs) .steps {
    grid-template-columns: repeat(4, 1fr);
    gap: 32px;
    align-items: start;
  }
  .process:not(.process--pairs) .steps .step { display: block; }
  .process:not(.process--pairs) .steps .step__media {
    aspect-ratio: 244 / 305;
    margin: 0 0 22px;
  }
}

/* ===================================================================
   HERO — read out of the file, not measured off a render.

   desktop 1440            mobile 390
     eyebrow  12/18  w500    12/18 w500
     title    32/42  w800    28/42 w400   <- lighter AND larger on the phone
     lede     18/28  w400    14/22 w400
     copy column 500 wide at x 80         350 at x 20
     buttons  y 578, two                  y 534, ONE

   The phone title being weight 400 where desktop is 800 is the part that
   would never be guessed: it is a different cut of PP Editorial Old, not the
   same headline scaled down.
   =================================================================== */
.hero__inner { max-width: 500px; }

.hero__eyebrow {
  font-size: 12px;
  line-height: 18px;
  font-weight: 500;
}
.hero__title {
  font-size: 32px;
  line-height: 42px;
  font-weight: 800;
  margin: 18px 0 0;
}
.hero__lede {
  font-size: 18px;
  line-height: 28px;
  font-weight: 400;
  margin: 22px 0 0;
}
.hero__actions { margin-top: 42px; }

@media (max-width: 720px) {
  .hero__inner { max-width: 350px; }
  .hero__title {
    font-size: 28px;
    line-height: 42px;
    font-weight: 400;      /* the frame's own value - see the note above */
    margin: 18px 0 0;
  }
  .hero__lede {
    font-size: 14px;
    line-height: 22px;
    margin: 18px 0 0;
  }
  /* the 390 frame shows one button; the second would wrap and look broken */
  .hero__actions .btn--ghost { display: none; }
}

/* The nav is 16/24 weight 500 in the frame; ours inherited 510 from the
   medium token, which renders visibly heavier in Plus Jakarta Sans.

   The nav looked wrong to Milad and he read it as a font problem. It was
   not: measured against the file, "Bespoke Design" renders 125.1px where
   Figma records 126 - the same text in the same face. Every item was
   exactly 23px too WIDE, a constant, which a wrong font could never
   produce: that scales with the text. It was `padding: 4px 12px` on each
   link, 24px a piece.

   The frame spaces the items 60 apart, text edge to text edge - 542+126=668
   to 728, 881 to 941, and so on all the way across. With the padding gone
   the gap can say exactly that, and the hover underline stops overhanging
   the word. Vertical padding stays: it is the hit area. */
.nav a { font-size: 16px; line-height: 24px; font-weight: 500; padding-inline: 0; }
/* The links live in ul.nav__list, so the gap has to go THERE - setting it on
   .nav only spaced .nav's own children, of which there is one: the list.
   Scoped above the overlay breakpoint so the stacked phone menu keeps its
   own 24. */
@media (min-width: 1101px) {
  .nav .nav__list { gap: 60px; }
}

/* "Request a Consultation" points at #contact further down the same page.
   A jump lands there with no sense of where it came from. */
html { scroll-behavior: smooth; }
@media (prefers-reduced-motion: reduce) {
  html { scroll-behavior: auto; }
}

/* ===================================================================
   HERO SLIDE TRANSITIONS

   Three, chosen per page under Lorenzo → Hero. All of them move the same
   `.is-active` class, so the markup and the script are identical whichever
   is picked - only what the class means changes.

   Fade is the default and the one the design implies. Slide and zoom are
   there because Milad asked to be able to change it later without code.
   =================================================================== */
.hero__bg[data-effect] .hero__slide {
  transition: opacity var(--dur) var(--ease), transform var(--dur) var(--ease);
}

/* --- fade (default) --- */
.hero__bg[data-effect="fade"] .hero__slide { opacity: 0; }
.hero__bg[data-effect="fade"] .hero__slide.is-active { opacity: 1; }

/* --- slide sideways --- */
.hero__bg[data-effect="slide"] { overflow: hidden; }
.hero__bg[data-effect="slide"] .hero__slide {
  opacity: 0;
  transform: translateX(6%);
}
.hero__bg[data-effect="slide"] .hero__slide.is-active {
  opacity: 1;
  transform: translateX(0);
}

/* --- zoom --- */
.hero__bg[data-effect="zoom"] { overflow: hidden; }
.hero__bg[data-effect="zoom"] .hero__slide {
  opacity: 0;
  transform: scale(1.06);
}
.hero__bg[data-effect="zoom"] .hero__slide.is-active {
  opacity: 1;
  transform: scale(1);
}

@media (prefers-reduced-motion: reduce) {
  .hero__bg[data-effect] .hero__slide { transition: opacity .01s linear; transform: none; }
}

/* ===================================================================
   The last few text values that were not the file's.

   Checked element by element against the Homepage frame rather than by eye:
   the 360 hint is ink not body grey, and the process heading and step titles
   were using the warm ink (#1E1A16) that belongs to the About page's warm
   sections, not this one's #1E1F21.
   =================================================================== */
.viewer__hint { color: var(--c-ink); }
.process .h-section,
.process__head .h-section { color: var(--c-ink); }
.step__title { color: var(--c-ink); }


/* The numbers band steps down to 32/42 on a phone - the file's own value,
   and the same on both pages that carry the band. */
@media (max-width: 720px) {
  .stat__value { font-size: 32px; line-height: 42px; }
}

/* About draws these one step larger than the homepage does: 48/62 against
   40/52. Same band, same markup, different frame - so it is scoped to the
   page rather than changed globally. */
@media (min-width: 721px) {
  /* `.page-template-template-about` never matched: About is built with
     Elementor on the default template, so WordPress never adds that class.
     `body.page--about` comes from the theme's own body_class filter. */
  body.page--about .stat__value { font-size: 48px; line-height: 62px; }
}

/* ---------- 390: the Bespoke frame's own values ------------------------
   The mobile frame is a separate design, not the desktop one narrowed:

     band              desktop        390
     Design Intel.     1280x542       350x542   ratio 0.646, not 2.36
     its title         28/42          24/36
     its body          16/24          14/22
     gallery photos    552x347 x4     171x200 in a 2x2, 8 apart
     gallery heading   28/42          24/36

   The band photo was inheriting the desktop's 1280/542 and came out 157px
   tall inside a 390 screen. */
@media (max-width: 720px) {
  /* x20 w350 in the 390 frame. The band is written OUTSIDE `.container`, so
     the section gutter does not reach it and it has to carry its own 20 -
     the old comment here claimed the opposite and the photo ran edge to
     edge.

     Both of these belong to the HEADED band only. A band with no head is
     full-bleed by design: the collection page's banner is 390x250 at x0 in
     its own frame, and inheriting either value here cropped it. */
  .media-band--headed { aspect-ratio: 350 / 542; margin-inline: 20px; }
  .collection__banner .media-band { aspect-ratio: 390 / 250; margin-inline: 0; }
  /* copy closes 214, the photo opens 246 */
  .media-band__head { width: auto; margin: 60px auto 32px; }
  .media-band__head .h-section {
    font-size: var(--fs-24);
    line-height: var(--lh-24);
  }
  .media-band__head .body {
    font-size: var(--fs-14);
    line-height: var(--lh-14);
    margin-top: 12px;
  }
  .media-band--headed { margin-bottom: 60px; }


  /* hero - the 390 frame is its own design:
       eyebrow y378 12/18 · title y396 28/42 · lede y450 14/22
       button  y534 16/24 · hero 700, so 126 under the button */
  .hero { min-height: 700px; }
  .hero--inner .hero__title { font-size: var(--fs-28); line-height: var(--lh-28); }
  .hero--inner .hero__lede {
    font-size: var(--fs-14);
    line-height: var(--lh-14);
    max-width: none;
  }
  /* button box y526..566 in a 700 hero, so 134 under it. */
  .hero--has-actions .hero__inner { padding-bottom: 134px; }
  .hero--inner .hero__actions .btn { height: 40px; min-width: 214px; }

  /* gallery band 644:
       heading y60 24/36 · button y112 157x32, label 14/22
       grid    y176, 171x200 tiles, 8 apart, rows at y176 and y384
       60 above the heading and 60 under the last row */
  .gallery { padding-block: 60px; }
  .gallery__head .btn {
    height: 32px;
    font-size: var(--fs-14);
    line-height: var(--lh-14);
    margin-top: 16px;          /* 112 - 96 */
  }
  .gallery__grid { margin-top: 32px; }   /* 176 - 144 */

  /* steps band 672:
       eyebrow y60 12/18 · title y82 24/36 · body y130 14/22
       button  y220 186x32, label 14/22
       photo   y284 350x198 · number + title inline at y498 18/28
       body    y530 14/22 · 60 under the block */
  .process--pairs { padding-block: 60px; }
  .process--pairs .process__head .h-section {
    font-size: var(--fs-24);
    line-height: var(--lh-24);
  }
  .process--pairs .process__head .body {
    font-size: var(--fs-14);
    line-height: var(--lh-14);
    margin-top: 12px;          /* 130 - 118 */
  }
  .process--pairs .process__cta { margin-top: 24px; }   /* body closes 196, button opens 220 */
  .process--pairs .process__cta .btn {
    height: 32px;
    /* 14 either side of a 158-wide label is the frame's 186 - the site's
       default 18 made it 196 and pulled the whole carousel 2 up. */
    min-width: 0;
    padding-inline: 14px;
    font-size: var(--fs-14);
    line-height: var(--lh-14);
  }
  /* The hero container carries a desktop `min-height: 780` and the inner
     hero adds 110 of its own - inside a 700 frame that made the band 794 and
     pushed the button 258 above the floor instead of 134.

     `height: 100%` was the wrong cure: `.hero` is a plain block of automatic
     height, so a percentage on its child resolves to auto and the container
     shrank to its copy. With nothing to push against, `justify-content:
     flex-end` did nothing and EVERY inner hero on a phone rendered its copy
     at the top of the photograph instead of anchored 134 above the foot. It
     has to be the frame's own 700. */
  .hero .container { min-height: 0; height: 700px; padding-block: 0; }
  .hero--inner .container { padding-block: 0; }

  /* The copy wrapper has to span the step, not sit in one of the four tracks
     the carousel sets up - at 163 wide the paragraph wrapped to six lines and
     the step came out 398 against the frame's 328.

       photo   350x198
       number  x126  title x154   both 18/28, inline and centred
       body    x56   278 wide, 14/22, centred */
  .process--pairs .steps .step__media { aspect-ratio: 350 / 198; }
  .process--pairs .steps .step__copy {
    grid-column: 1 / -1;
    grid-row: 2;
    display: grid;
    grid-template-columns: auto auto;
    justify-content: center;
    column-gap: 8px;
    row-gap: 4px;              /* 530 - 526 */
    padding-inline: 36px;      /* 56 - 20 */
    /* the copy block is 482..612 in the frame and the paragraph closes on
       596, so the step keeps 16 under it */
    padding-bottom: 16px;
  }
  .process--pairs .steps .step__row   { grid-column: 1; grid-row: 1; margin: 16px 0 0; }
  .process--pairs .steps .step__title { grid-column: 2; grid-row: 1; margin: 16px 0 0; }
  .process--pairs .steps .step__text  { grid-column: 1 / -1; grid-row: 2; margin: 0; text-align: center; }
  .process--pairs .steps .step__title {
    font-size: var(--fs-18);
    line-height: var(--lh-18);
  }
  .process--pairs .steps .step__text {
    font-size: var(--fs-14);
    line-height: var(--lh-14);
  }

  /* "Designed Around Your Space" - band 878:
       title y98 24/36 · body y146 14/22
       photo y234 350x644, running to the band's floor
       panels 350x124 at y334 and y494, pitch 160
       panel title 16/24, panel body 14/22 */
  .feature-cards--light .feature-cards__head .h-section {
    font-size: var(--fs-24);
    line-height: var(--lh-24);
  }
  .feature-cards--light .feature-cards__head .body {
    font-size: var(--fs-14);
    line-height: var(--lh-14);
  }
  .feature-cards--light .feature-card__title {
    font-size: var(--fs-16);
    line-height: var(--lh-16);
  }
  .feature-cards--light .feature-card__text {
    font-size: var(--fs-14);
    line-height: var(--lh-14);
  }

  /* The 390 frame runs the bands in a different order to the desktop one:
     hero, "Designed Around Your Space", GALLERY, Steps, Design Intelligence,
     contact. Elementor lays its containers out in document order, so the
     wrapper becomes a column here and each container is told its place. */
  body.page--bespoke .elementor { display: flex; flex-direction: column; }
  body.page--bespoke .elementor > .elementor-element:has(.feature-cards--light) { order: 1; }
  body.page--bespoke .elementor > .elementor-element:has(.gallery)              { order: 2; }
  body.page--bespoke .elementor > .elementor-element:has(.process)              { order: 3; }
  body.page--bespoke .elementor > .elementor-element:has(.media-band)           { order: 4; }
  body.page--bespoke .elementor > .elementor-element:has(.contact)              { order: 5; }
  /* the gallery is a 2x2 of 171x200 with an 8 channel, not three across */
  .gallery__grid {
    grid-template-columns: 1fr 1fr;
    gap: 8px;
    margin-top: 32px;
  }
  .gallery__tile { aspect-ratio: 171 / 200; }
  .gallery__head .h-section {
    font-size: var(--fs-24);
    line-height: var(--lh-24);
  }
}

/* ---------- 390: the About frame's own values --------------------------
   Read off frame 101:7088 (390x6568). The bands keep the desktop order but
   almost every measure changes.

     mission  646  title y60 24/36 · body y108 14/22
                   photo y206 350x440 · cards 302 wide at y266 and y414
                   card title 16/24, card body 14/22
     story    592  eyebrow y60 14/22 · title y82 24/36 · body y130 14/22
                   button y278 148x32 · photo y342 390x250, full bleed
     founders 1006 title y82 24/36 · photos 350x200 STACKED, no stagger
                   name 16/24 · role 12/18 · body 14/22 · pitch 396
     values   586  title y80 24/36 · one column, items at y160/252/366/458
                   item title 16/24, body 14/22
   =================================================================== */
@media (max-width: 720px) {
  /* mission - the same shape as the desktop band, at the 390 frame's numbers:
       band  390x646
       title y60 24/36 · body y108 14/22
       photo y206 x20 350x440, running to the band's floor
       cards y266 and y414, 302 wide at x44, 20 apart, ON the photo
     Placed against the band rather than flowed - a negative margin collapsed
     it to 114 when the cards climbed over the head. */
  .mission {
    aspect-ratio: 390 / 646;
    padding-block: 0;
    overflow: hidden;
  }
  .mission > .container { padding-top: 15.38%; }   /* 60 of 390 */
  .mission__head .h-section { font-size: var(--fs-24); line-height: var(--lh-24); }
  .mission__head .lede {
    font-size: var(--fs-14); line-height: var(--lh-14);
    max-width: none; margin-top: 12px;
  }
  .mission img.mission__bg,
  .mission__bg {
    height: 68.11%;            /* 440 of 646 */
    left: 5.13%;               /* x20 */
    right: 5.13%;
    width: 89.74%;             /* 350 of 390 */
  }
  .mission__cards {
    position: absolute;
    left: 11.28%;              /* x44 */
    top: 41.18%;               /* y266 of 646 */
    width: 77.44%;             /* 302 of 390 */
    grid-template-columns: 1fr;
    gap: 20px;                 /* 414 - 394 */
    margin: 0;
  }
  .mission-card { aspect-ratio: auto; padding: 24px 12px; }
  .mission-card__title { font-size: var(--fs-16); line-height: var(--lh-16); }
  .mission-card__text  { font-size: var(--fs-14); line-height: var(--lh-14); margin-top: 12px; }

  /* story */
  .story { padding-block: 60px 0; }
  .story__head .h-section { font-size: var(--fs-24); line-height: var(--lh-24); }
  .story__head .body {
    font-size: var(--fs-14); line-height: var(--lh-14);
    max-width: none; margin-top: 12px;
  }
  .story__head .btn {
    height: 32px; min-width: 148px; margin-top: 26px;
    font-size: var(--fs-14); line-height: var(--lh-14);
  }
  .story__media { margin-top: 32px; }        /* 342 - 310 */
  .story .story__media img.story__image { aspect-ratio: 390 / 250; height: auto; }

  /* founders - stacked, landscape photographs, no stagger */
  .founders { padding-block: 60px 28px; }
  .founders__head { width: auto; margin-bottom: 44px; }   /* 186 - 142 */
  .founders__head .h-section { font-size: var(--fs-24); line-height: var(--lh-24); }
  .founders__grid { width: auto; grid-template-columns: 1fr; gap: 46px; }
  .founders__grid > :nth-child(even) { margin-top: 0; }
  .founder__media { margin-bottom: 24px; }
  .founder__media img { aspect-ratio: 350 / 200; }
  .founder__name { font-size: var(--fs-16); line-height: var(--lh-16); }
  .founder__role { font-size: var(--fs-12); line-height: var(--lh-12); }
  .founder__text { font-size: var(--fs-14); line-height: var(--lh-14); margin-top: 12px; }

  /* values - one column */
  .values { padding-block: 80px 58px; }
  .values__title { font-size: var(--fs-24); line-height: var(--lh-24); margin-bottom: 44px; }
  .values__grid { grid-template-columns: 1fr; gap: 44px; max-width: none; }
  .values__grid > * { max-width: none; }
  .value__title { font-size: var(--fs-16); line-height: var(--lh-16); }
  .value__text  { font-size: var(--fs-14); line-height: var(--lh-14); margin-top: 2px; }
}

/* ---------- 390: the Lighting Collection frame -------------------------
   Frame 101:6076 (390x4618). Its collections band is 804:

     y60   title  24/36 · y108 body 14/22
     y228  row 1  two 171-wide cards, photo 171x228, name at +230 14/22
     y492  row 2  pitch 264, so 12 between the rows and 8 between columns
     60 above the title and 60 under the last row

   The frame draws FOUR cards here where the desktop one draws six. */
@media (max-width: 720px) {
  .section--collections-grid { padding-block: 60px; }
  .collections__head .h-section { font-size: var(--fs-24); line-height: var(--lh-24); }
  .collections__head .body {
    font-size: var(--fs-14); line-height: var(--lh-14); margin-top: 12px;
  }
  .collection-grid {
    grid-template-columns: 1fr 1fr;
    gap: 12px 8px;          /* 492 - 480, and 199 - 191 */
    /* The standfirst is four lines in the frame and closes on 196, not the
       168 this number was derived from - the grid opens 32 under it. */
    margin-top: 32px;
  }
  .collection-grid .collection-card img { aspect-ratio: 171 / 228; }
  .collection-grid .collection-card__meta { margin-top: 2px; }
  .collection-grid .collection-card__title {
    font-size: var(--fs-14);
    line-height: var(--lh-14);
    padding-inline-start: 8px;
  }
  /* The 390 frame stops at four - but only as the collapsed state. theme.js
     adds .is-capped (and shows the "See more" button) only when there are more
     than four, so the rest stay reachable; without JS nothing is hidden. */
  .collection-grid.is-capped > :nth-child(n+5) { display: none; }
}

/* The "See more" under the collections grid - a phone-only reveal. Mirrors the
   news list's button. Shown on phones (where the grid caps at four); hidden on
   desktop, where the whole grid is visible. The button inside stays [hidden]
   until theme.js decides there is more than the four the frame draws. The
   desktop hide is a min-width rule on purpose: an unconditional display:none
   here would win over the phone rule by source order and never show. */
.collection-grid__more { margin-top: 24px; text-align: center; }
@media (min-width: 721px) {
  .collection-grid__more { display: none; }
}
.collection-grid__reveal {
  appearance: none;
  background: none;
  border: 0;
  cursor: pointer;
  font-family: var(--font-body);
  font-size: var(--fs-14);
  line-height: var(--lh-14);
  font-weight: var(--fw-medium);
  letter-spacing: .02em;
  color: var(--c-body);
  padding: 10px 24px;
  border: 1px solid var(--c-btn-line);
}
.collection-grid__reveal:hover { color: var(--c-ink); }
.collection-grid__reveal[hidden] { display: none; }

/* ===================================================================
   MOBILE MENU - opened as a short strip once the page was scrolled.

   The panel is `position: fixed; inset: 0` and lives inside the header.
   A fixed box is sized to the viewport only while no ancestor has a
   `backdrop-filter` or a `transform`; the moment one does, that ancestor
   becomes its containing block instead. The scrolled header has both
   (the blur on `.is-scrolled`, the slide on `.is-hidden`), so past the
   first screen the panel was laid out inside the 80px bar and only the
   first link showed. At the top of the page neither applies, which is
   why it only happened "sometimes".

   Two parts:
   1. the blur and its tint move to a pseudo-element. Same look, but a
      filter on a child does not turn the header into a containing block.
   2. while the menu is open the header holds no transform at all, so a
      tap during the bar's own slide-in cannot catch it mid-transition.
   =================================================================== */
@media (max-width: 1100px) {
  .site-header.is-scrolled,
  .site-header--over.is-scrolled,
  .site-header--sticky.is-scrolled {
    background: transparent;
    backdrop-filter: none;
    -webkit-backdrop-filter: none;
  }
  .site-header.is-scrolled::before {
    content: "";
    position: absolute;
    inset: 0;
    z-index: -1;
    background: rgba(18, 18, 20, .82);
    backdrop-filter: blur(12px);
    -webkit-backdrop-filter: blur(12px);
    pointer-events: none;
  }
  /* a sticky bar on a page with no hero keeps its own, lighter surface */
  .site-header--sticky:not(.site-header--over).is-scrolled::before {
    background: rgba(20, 20, 22, .72);
    backdrop-filter: blur(10px);
    -webkit-backdrop-filter: blur(10px);
  }
  html.is-nav-open .site-header,
  html.is-nav-open .site-header.is-hidden {
    transform: none;
    transition: none;
  }
}

/* ===================================================================
   LEGAL PAGES - /legal-terms/ and /policy/ (panel tasks 3668, 3669, 3424).
   They are plain block-editor pages under the About-style hero. The base
   `.entry p { margin: 0 }` outranks `.entry > * + *`, so on a plain page
   every paragraph sat flush against the next - the "intro lines stuck
   together". Here they get the reading rhythm and a readable measure.
   =================================================================== */
.page--legal-terms .entry,
.page--policy .entry { max-width: 760px; margin-inline: auto; }
.page--legal-terms .entry > * + *,
.page--policy .entry > * + * { margin-top: 16px; }
.page--legal-terms .entry > h2,
.page--policy .entry > h2 { margin-top: 48px; }
@media (max-width: 720px) {
  .page--legal-terms .entry > h2,
  .page--policy .entry > h2 {
    margin-top: 36px;
    font-size: var(--fs-24);
    line-height: var(--lh-24);
  }
}


/* ===================================================================
   QA LIST, October 2026. Numbers are the rows of that list.
   =================================================================== */

/* 3. The 360 picture on a phone: `cover` in a band 300 tall cut the base of the
   chandelier off, and the wider the phone the more it lost. `contain`, in a band
   that keeps the picture's shape as the screen widens. 300 at 390, as the frame. */
@media (max-width: 720px) {
  .viewer { min-height: clamp(300px, 77vw, 555px); }
  .viewer__stage img { object-fit: contain; object-position: center bottom; }
}

/* 4. The steps row on a phone. Two faults, both on the first card:
   - `.steps .step:nth-child(2..4)` set a resting 24px drop with more weight than
     `.step.is-in`, so cards 2-4 never came back up and card 1 looked 24 too high;
   - mandatory snap with no scroll-padding scrolled the row's own 20 gutter away
     on load, so card 1 sat against the screen edge. */
@media (max-width: 720px) {
  .steps .step.is-in,
  .steps .step.is-in:nth-child(n) { transform: none; }
  .steps { scroll-padding-inline: 20px; }
}

/* 5. Company profile on a phone: field and button one height, on one row, and
   the label inside its button. The fixed 136 is what the label overflowed. */
@media (max-width: 720px) {
  .booklet .booklet__inner .mifa-form--inline { flex-wrap: nowrap; align-items: stretch; gap: 8px; }
  .booklet .booklet__inner .mifa-form--inline .mifa-form__grid { flex: 1 1 auto; min-width: 0; height: 40px; }
  .booklet .booklet__inner .mifa-form--inline input { width: 100%; height: 40px; min-width: 0; }
  .booklet .booklet__inner .mifa-form--inline .mifa-form__submit {
    flex: 0 0 auto;
    width: auto;
    height: 40px;
    min-width: 0;
    padding-inline: 12px;
    font-size: 13px;
    white-space: nowrap;
  }
}

/* 6. Footer button: one line at every width. */
.footer__cta a { white-space: nowrap; max-width: 100%; }

/* 11. Logo on a phone: 147 x 30 at x20 y25 on every 390 frame. It was the
   desktop's 196 x 40. */
@media (max-width: 720px) {
  /* 17 + the 46 menu button + 17 = the frame's 80; the logo centres on y25 */
  .site-header { padding-block: 17px; }
  .site-header .brand__mark svg,
  .site-header .brand__img { height: 30px; width: auto; }
}

/* 12. Elementor's `.elementor img { height: auto }` ties with the tile's rule and
   loads later, so the photo kept its own shape and left the tile half empty. */
.elementor .gallery__tile img { height: 100%; }

/* 13. Menu rows: the whole row is the target, not just the words. */
@media (max-width: 1100px) {
  .nav .nav__list li { width: 100%; }
  .nav .nav__list a { width: 100%; }
}

/* 14. An article's hero on a phone: a long standfirst pushed the meta line to
   the very edge of the photo. Three lines of standfirst, and room under the meta. */
@media (max-width: 720px) {
  .single-post .hero--inner .hero__inner { padding-bottom: 40px; }
  .single-post .hero__lede {
    display: -webkit-box;
    -webkit-box-orient: vertical;
    -webkit-line-clamp: 3;
    overflow: hidden;
  }
}

/* 15. "Read more News": the cards start under the heading, on the page gutter,
   rather than centred in the window; card titles one step down. */
.news-list--rail {
  padding-inline: var(--pad-80);
  scroll-padding-inline: var(--pad-80);
  cursor: grab;
}
.news-list--rail .news-card { scroll-snap-align: start; }
.news-list--rail .news-card__title { font-size: var(--fs-16); line-height: var(--lh-16); }

/* 8. One "See more" on a phone: the next-page link waits until the cap is lifted. */
.news-list__next[aria-busy="true"] { opacity: .5; pointer-events: none; }
@media (max-width: 720px) {
  .news-list.is-capped ~ .news-list__more .news-list__next { display: none; }
}

/* 10. Results dim while the next set loads; nothing else moves. */
#news-results { transition: opacity var(--dur) var(--ease); }
#news-results[aria-busy="true"] { opacity: .45; }
.news-listing__empty { margin-top: 32px; }

/* 21. Arrows on the thumbnail strip. */
.configurator__strip { position: relative; min-width: 0; }
@media (min-width: 900px) {
  .configurator__strip { grid-column: 1; grid-row: 2; }
}
.configurator__thumbs { cursor: grab; }
.configurator__thumbs.is-dragging { cursor: grabbing; }
.configurator__arrow {
  position: absolute;
  top: 50%;
  display: flex;
  align-items: center;
  justify-content: center;
  width: 36px;
  height: 36px;
  margin-top: -15px;
  padding: 0;
  border: 0;
  background: rgba(30, 31, 33, .72);
  color: #fff;
  cursor: pointer;
  transition: background var(--dur) var(--ease);
}
.configurator__arrow[hidden] { display: none; }
.configurator__arrow--prev { left: 6px; }
.configurator__arrow--prev svg { transform: rotate(180deg); }
.configurator__arrow--next { right: 6px; }
.configurator__arrow:focus-visible { outline: 2px solid #fff; outline-offset: 2px; }
@media (hover: hover) and (pointer: fine) {
  .configurator__arrow:hover { background: var(--c-ink); }
}

/* 20. Open a collection photograph large. */
.lz-zoomable { cursor: zoom-in; }
.lz-zoomable:focus-visible { outline: 2px solid var(--c-accent); outline-offset: -2px; }
.lz-lightbox {
  position: fixed;
  inset: 0;
  width: 100%;
  max-width: none;
  height: 100%;
  max-height: none;
  margin: 0;
  padding: 0;
  border: 0;
  background: rgba(17, 14, 11, .94);
  color: #fff;
  overflow: hidden;
}
.lz-lightbox:not([open]) { display: none; }
.lz-lightbox[open] { display: flex; align-items: center; justify-content: center; }
.lz-lightbox::backdrop { background: rgba(17, 14, 11, .94); }
.lz-lightbox__img {
  display: block;
  max-width: calc(100vw - 144px);
  max-height: calc(100dvh - 96px);
  width: auto;
  height: auto;
  object-fit: contain;
}
.lz-lightbox__close,
.lz-lightbox__nav {
  position: absolute;
  display: flex;
  align-items: center;
  justify-content: center;
  width: 48px;
  height: 48px;
  padding: 0;
  border: 0;
  background: rgba(255, 255, 255, .08);
  color: #fff;
  cursor: pointer;
  transition: background var(--dur) var(--ease);
}
.lz-lightbox__nav[hidden] { display: none; }
.lz-lightbox__close { top: 16px; right: 16px; }
.lz-lightbox__nav { top: 50%; margin-top: -24px; }
.lz-lightbox__nav--prev { left: 16px; }
.lz-lightbox__nav--next { right: 16px; }
.lz-lightbox__close:focus-visible,
.lz-lightbox__nav:focus-visible { outline: 2px solid #fff; outline-offset: 2px; }
@media (hover: hover) and (pointer: fine) {
  .lz-lightbox__close:hover,
  .lz-lightbox__nav:hover { background: rgba(255, 255, 255, .2); }
}
.lz-lightbox__count {
  position: absolute;
  bottom: 16px;
  left: 0;
  right: 0;
  margin: 0;
  text-align: center;
  font-size: var(--fs-14);
  line-height: var(--lh-14);
  color: rgba(255, 255, 255, .8);
}
@media (max-width: 720px) {
  .lz-lightbox__img { max-width: 100vw; max-height: calc(100dvh - 144px); }
  .lz-lightbox__nav { top: auto; bottom: 8px; margin-top: 0; }
  .lz-lightbox__nav--prev { left: 8px; }
  .lz-lightbox__nav--next { right: 8px; }
  .lz-lightbox__close { top: 8px; right: 8px; }
  .lz-lightbox__count { bottom: 22px; }
}

/* 23. Lenis drives the wheel, so the browser's own smoothing must stay out of it. */
html.lenis, html.lenis body { height: auto; }
html.lenis { scroll-behavior: auto !important; }
.lenis:not(.lenis-autoToggle).lenis-stopped { overflow: clip; }

/* 2, 18. The AVIF wrapper takes no part in layout. */
.lz-picture { display: contents; }

/* 7. Four gallery photos on a tablet: two rows of two, not three and an orphan. */
@media (min-width: 721px) and (max-width: 899px) {
  .gallery .gallery__grid { grid-template-columns: 1fr 1fr; }
}

/* The side-scrolling rails must not scroll up and down as well. A card waiting
   for its reveal rests 16-24px low, which made the rail 24 taller than its box,
   so a finger on it moved the rail instead of the page. They fade in place. */
.news-list--rail { overflow-y: hidden; }
/* Phone only: above 720 the steps are a plain grid. overflow-y: hidden there
   turned its overflow-x into auto and drew a scrollbar under the desktop grid. */
@media (max-width: 720px) {
  .steps { overflow-y: hidden; }
  .steps .step,
  .steps .step:nth-child(n) { transform: none; }
}
.news-list--rail .news-card.reveal { transform: none; }

/* The band's phone photograph sits in a <picture>; the crop rules follow it. */
.media-band > .lz-picture > img { display: block; width: 100%; height: 100%; object-fit: cover; }
.elementor .media-band > .lz-picture > img { height: 100%; }

/* "drag" under the 360 picture. Desktop: -8 fell past the band's clipped edge,
   cutting the word off. Phone: it sat on the chandelier, so the picture stops
   above it and the word has its own line at the foot of the band. */
.viewer__grab { bottom: 6px; }
@media (max-width: 720px) {
  .viewer { min-height: clamp(326px, calc(77vw + 26px), 581px); }
  .viewer .viewer__stage { bottom: 26px; }
  .viewer .viewer__grab { bottom: -22px; }
}

/* News cards were reading as a wall of text over the photo. */
.news-card__title,
.news-card__excerpt {
  display: -webkit-box;
  -webkit-box-orient: vertical;
  overflow: hidden;
}
.news-card__title { -webkit-line-clamp: 3; }
.news-card__excerpt { -webkit-line-clamp: 1; }
@media (max-width: 720px) {
  .news-card__title { -webkit-line-clamp: 2; }
}


