/* =============================================================================
   facade-cleaning — brand.css  (v5)

   Chosen by the operator after four rejected attempts, and both halves were decided
   rather than guessed:

     COLOUR WORLD  ocean blue + terracotta, pulled out of the hero photograph itself
                   (Pexels 16934519: two towers, vivid sky, turquoise glazing,
                   terracotta louvres). "i like how u made the colors match the image".
     RICHNESS      rich and layered. Gradients, glass, tinted glow, real depth.

   v4 was rejected as "a newspaper design, white and black". The fix is not more
   decoration, it is COLOUR CARRYING STRUCTURE: the page ground is a tinted wash rather
   than white, every raised surface casts an ocean-tinted shadow rather than a grey one,
   the deep bands are gradients rather than fills, and the hero photograph is graded
   toward the brand hue so the image and the palette are visibly one thing.

   Two rules survive from earlier rounds and are not up for revisiting:
     - Section heads are CENTRED STACKS. Never a heading beside its paragraph.
     - The work band is a uniform grid of equal cells.
   ========================================================================== */

/* -----------------------------------------------------------------------------
   1. TOKENS
   -------------------------------------------------------------------------- */
:root {
  /* Turquoise is a LINE AND FILL colour, never a word: it is 3.91:1 on paper and
     3.52:1 on the deep band, so it fails as text in both directions. The light
     variant exists for the one place a turquoise word is wanted, on dark. */
  --c-turquoise: #17879b;
  --c-turquoise-light: #5cc5db;

  --radius-sm: 6px;
  --radius-md: 10px;
  --radius-lg: 14px;
  --radius-xl: 20px;

  /* Depth, tinted to the brand hue. A grey shadow over a blue-tinted page reads as
     dirt; 00-design-law.md 4.2 says the same thing and it is the difference between
     "layered" and "smudged". */
  --shadow-sm: 0 1px 2px hsl(203 45% 18% / 0.05), 0 2px 6px hsl(203 45% 18% / 0.06);
  --shadow-md: 0 2px 6px hsl(203 45% 18% / 0.06), 0 10px 26px hsl(203 45% 20% / 0.10);
  --shadow-lg: 0 4px 12px hsl(203 45% 18% / 0.08), 0 22px 48px hsl(203 45% 20% / 0.14);
  --shadow-xl: 0 8px 20px hsl(203 45% 18% / 0.10), 0 36px 80px hsl(203 45% 20% / 0.18);
  --shadow-ring: inset 0 0 0 1px hsl(197 30% 78% / 0.55);
  --shadow-action: 0 6px 20px hsl(203 80% 22% / 0.28);
  --shadow-accent: 0 6px 20px hsl(17 76% 40% / 0.30);

  --border-hairline: 1px solid var(--color-border);
  --media-placeholder: linear-gradient(160deg, hsl(196 40% 90%), hsl(203 35% 82%));
  --max-width: 78rem;
  --max-width-measure: 38rem;
  --header-height: 4.5rem;

  --tracking-display: -0.035em;
  --tracking-label: 0.12em;
  --leading-display: 1.02;
  --weight-display: 700;

  --hero-band: clamp(30rem, 74vh, 42rem);
  --work-ratio: 1 / 1;
}

:root:lang(ar) {
  --tracking-display: 0;
  --tracking-label: 0;
  --leading-display: 1.3;
}

/* THE PAGE GROUND IS NOT WHITE. Two very soft radial washes in the brand hues, so the
   paper itself carries colour and a white card sitting on it has something to be
   raised above. This is most of the difference between v4 and v5. */
body {
  background-color: var(--color-surface);
  background-image:
    radial-gradient(55rem 38rem at 88% -8%, hsl(190 72% 42% / 0.13), transparent 62%),
    radial-gradient(48rem 32rem at -5% 12%, hsl(203 76% 26% / 0.08), transparent 58%);
  background-repeat: no-repeat;
  letter-spacing: -0.011em;
}

:root:lang(ar) body { letter-spacing: 0; }

/* -----------------------------------------------------------------------------
   2. HEADER — frosted, so the page reads as layered from the first pixel
   -------------------------------------------------------------------------- */
.site-header {
  background-color: hsl(196 40% 97% / 0.72);
  border-block-end: 1px solid hsl(197 30% 78% / 0.5);
}

@supports (backdrop-filter: blur(1px)) {
  .site-header {
    backdrop-filter: saturate(1.5) blur(16px);
    -webkit-backdrop-filter: saturate(1.5) blur(16px);
  }
}

.site-header.is-scrolled { box-shadow: 0 1px 24px hsl(203 45% 20% / 0.10); }

.brand__name { font-size: var(--step-1); font-weight: 700; color: var(--color-brand); }
.site-nav__link { font-weight: 500; }
.site-nav__link::after { background-color: var(--color-accent); block-size: 3px; }

/* -----------------------------------------------------------------------------
   3. BUTTONS — filled, rounded, with a coloured lift
   -------------------------------------------------------------------------- */
.btn {
  border-radius: var(--radius-sm);
  font-weight: 600;
  padding-inline: var(--space-lg);
}

.btn--lg { min-block-size: 3.5rem; font-size: var(--step-0); }

.btn--primary {
  color: var(--color-brand-contrast);
  background-color: var(--color-accent);
  background-image: linear-gradient(to bottom, hsl(0 0% 100% / 0.16), hsl(0 0% 100% / 0) 60%);
  border-color: transparent;
  box-shadow: var(--shadow-accent);
}

.btn--ghost {
  color: var(--color-brand);
  background-color: hsl(0 0% 100% / 0.72);
  border-color: hsl(197 30% 70% / 0.7);
  box-shadow: var(--shadow-sm);
}

@media (hover: hover) {
  .btn--primary:hover { background-color: #a04214; box-shadow: var(--shadow-accent), var(--shadow-lg); }
  .btn--ghost:hover { background-color: #ffffff; border-color: var(--color-brand); }
}

.link-arrow {
  display: inline-flex;
  align-items: center;
  gap: var(--space-2xs);
  min-block-size: 3.5rem;
  font-weight: 600;
  text-decoration: none;
  color: inherit;
}

.link-arrow span { padding-block-end: 2px; border-block-end: 2px solid currentColor; }
.link-arrow .icon { transition: transform var(--transition); }

@media (hover: hover) {
  .link-arrow:hover .icon { transform: translateX(4px); }
  [dir="rtl"] .link-arrow:hover .icon { transform: translateX(-4px); }
}

@media (prefers-reduced-motion: reduce) { .link-arrow:hover .icon { transform: none; } }

/* -----------------------------------------------------------------------------
   4. HERO — the photograph GRADED to the palette

   The overlay is not just a scrim. It is two layers: a directional wash that carries
   legibility, and a turquoise radial bloom over the sky corner that pulls the whole
   frame toward the brand hue. That grade is why the image and the palette read as one
   decision instead of a photograph with a colour scheme laid on top of it.

   Legibility is still arithmetic: over a pure-white photograph the text region sits at
   0.86 effective, which is 14:1.
   -------------------------------------------------------------------------- */
.hero--photo:not(.hero--split) {
  min-block-size: var(--hero-band);
  padding-block: var(--space-3xl) var(--space-2xl);
  display: flex;
  flex-direction: column;
  justify-content: flex-end;
}

/* <picture> inserts an auto-height parent between .hero__media and the <img>, which
   breaks base.css's `block-size: 100%` and lets the photograph overflow the band. */
.hero--photo .hero__media { overflow: hidden; }
.hero--photo .hero__media picture { display: block; block-size: 100%; inline-size: 100%; }

.hero--photo .hero__media::after {
  background-image:
    radial-gradient(80% 90% at 100% 0%, hsl(190 80% 48% / 0.34), transparent 62%),
    linear-gradient(
      to right,
      hsl(203 82% 9% / 0.93) 0%,
      hsl(203 80% 11% / 0.8) 38%,
      hsl(199 65% 18% / 0.5) 68%,
      hsl(196 60% 26% / 0.34) 100%
    );
}

[dir="rtl"] .hero--photo .hero__media::after {
  background-image:
    radial-gradient(80% 90% at 0% 0%, hsl(190 80% 48% / 0.34), transparent 62%),
    linear-gradient(
      to left,
      hsl(203 82% 9% / 0.93) 0%,
      hsl(203 80% 11% / 0.8) 38%,
      hsl(199 65% 18% / 0.5) 68%,
      hsl(196 60% 26% / 0.34) 100%
    );
}

@media (max-width: 48rem) {
  .hero--photo .hero__media::after,
  [dir="rtl"] .hero--photo .hero__media::after {
    background-image:
      radial-gradient(90% 60% at 50% 0%, hsl(190 80% 48% / 0.3), transparent 65%),
      linear-gradient(to top, hsl(203 82% 9% / 0.95) 0%, hsl(203 80% 12% / 0.82) 46%, hsl(196 60% 26% / 0.45) 100%);
  }
}

.hero--photo .hero__text { max-inline-size: 42rem; }

/* THE BADGE IS A LABEL, NOT A BUTTON, so it stops being drawn as one.
   It previously carried a frosted fill, a border and a radius, which is exactly the
   treatment the WhatsApp button and the four benefit chips also had: six identical
   rectangles in one hero, only one of which was clickable. A rule and a word cannot be
   mistaken for a control. */
.hero__badge {
  display: block;
  inline-size: fit-content;
  margin-block-end: var(--space-md);
  padding: 0 0 var(--space-2xs);
  font-size: var(--step--1);
  font-weight: 600;
  letter-spacing: var(--tracking-label);
  text-transform: var(--label-transform);
  color: var(--c-turquoise-light);
  background-color: transparent;
  border: 0;
  border-block-end: 2px solid var(--color-accent);
  border-radius: 0;
  backdrop-filter: none;
  -webkit-backdrop-filter: none;
}

.hero__title {
  font-size: clamp(2.5rem, 1.3rem + 4.9vw, 5rem);
  font-weight: 700;
  line-height: var(--leading-display);
  letter-spacing: var(--tracking-display);
  text-shadow: 0 2px 30px hsl(203 82% 8% / 0.4);
}

.hero__subtitle {
  margin-block-start: var(--space-md);
  max-inline-size: 34rem;
  font-size: var(--step-1);
  font-weight: 300;
  line-height: 1.55;
  opacity: 0.94;
}

.hero__actions { margin-block-start: var(--space-xl); gap: var(--space-sm); align-items: center; }
.hero .link-arrow { color: var(--color-text-inverse); }

/* ON A PHONE THE HERO TEXT CENTRES.

   Start-aligned is right on a wide screen, where the text occupies the left (or right,
   under RTL) portion of a landscape photograph and the ragged edge runs down the middle of
   the frame. On a phone the text is the full width of the screen, so start-alignment just
   pins everything to one side of a centred composition and reads as a mistake — which
   under RTL means everything jammed against the right edge. The badge, the benefit tiles
   and the two buttons are all inline-flex, so each needs its own centring; text-align
   alone would leave them hugging the start edge.

   SPECIFICITY. base.css sets `.hero:not(.hero--split) .hero__text { text-align: start }`,
   which is (0,3,0) — a bare `.hero__text` here ties at best and loses outright if the two
   stylesheets are ever reordered. Mirroring base's own selector under `:root` takes this
   to (0,4,0), so it wins on specificity rather than on load order. */
@media (max-width: 47.99rem) {
  :root .hero:not(.hero--split) .hero__text { text-align: center; }
  :root .hero .hero__subtitle { margin-inline: auto; }
  :root .hero .hero__actions { justify-content: center; }

  /* The badge is `display: block; inline-size: fit-content` (section 5 above), so it is a
     block box that shrink-wraps — `text-align` on the parent cannot move it and
     `justify-content` does nothing to it. Auto inline margins are what centres this one. */
  :root .hero .hero__badge { margin-inline: auto; }

  /* The tiles are flex rows with an inline-end pad that pulls the icon-and-label pair
     toward the start. Centre the row and make the padding symmetric, or each label sits
     off-centre inside a cell that is itself centred. */
  :root .hero .hero__benefits { margin-inline: auto; }
  :root .hero .hero__benefit {
    justify-content: center;
    text-align: center;
    padding-inline: var(--space-2xs);
  }
}

/* THE PAIR THAT POPS. Now that nothing else in the band is a filled rectangle, the two
   controls only have to differ from each other: one solid accent, one white outline.
   That is the oldest reliable hero pair there is, and it works because the primary is
   the only saturated fill on the first screen and the secondary is the only 2px stroke.

   Both are bigger than the shipped `--lg`. On a photographic hero the button competes
   with the whole image rather than with a white page, and 3.5rem is sized for the page. */
.hero .btn--lg {
  min-block-size: 3.9rem;
  padding-inline: var(--space-xl);
  font-size: var(--step-1);
  border-radius: var(--radius-md);
}

.hero .btn--primary {
  background-color: var(--color-accent);
  background-image: linear-gradient(to bottom, hsl(0 0% 100% / 0.2), hsl(0 0% 100% / 0) 55%);
  border-color: transparent;
  /* Two layers: a tight contact shadow that anchors it to the photograph, and a wide
     accent-coloured bloom that reads as the button glowing rather than floating. */
  box-shadow: 0 2px 6px hsl(200 60% 6% / 0.4), 0 10px 34px hsl(17 78% 42% / 0.55);
}

.hero .btn--whatsapp {
  color: var(--color-text-inverse);
  background-color: hsl(200 60% 8% / 0.28);
  border: 2px solid hsl(0 0% 100% / 0.85);
  box-shadow: none;
}

@supports (backdrop-filter: blur(1px)) {
  .hero .btn--whatsapp { backdrop-filter: blur(6px); -webkit-backdrop-filter: blur(6px); }
}

@media (hover: hover) {
  .hero .btn--primary:hover {
    background-color: #a04214;
    box-shadow: 0 2px 6px hsl(200 60% 6% / 0.45), 0 14px 44px hsl(17 78% 42% / 0.7);
  }
  .hero .btn--whatsapp:hover {
    color: var(--color-brand);
    background-color: #ffffff;
    border-color: #ffffff;
  }
}

/* -----------------------------------------------------------------------------
   15.2b HERO CTA TREATMENTS — `data-cta` on <html>, set from CTA_STYLE in
   build-pages.js. Three values, all fully styled, one constant to switch.

     accent        terracotta primary + white-outline secondary
     accent-green  terracotta primary + WhatsApp green secondary   <- live
     bold          white primary + WhatsApp green secondary

   `accent-green` is the operator's pick: the brand orange keeps the primary action, so
   terracotta stays on the first screen and matches the header CTA and the section
   rules, and WhatsApp keeps its own green so the second action is recognised before it
   is read.

   THE GREEN CARRIES DARK TEXT, NOT WHITE, AND THAT IS NOT A STYLE CHOICE. White on
   WhatsApp's #25D366 is 1.98:1, which fails AA by a mile even though it is what the
   WhatsApp app itself uses on its own dark chrome. Ink on the same green is 8.78:1.
   The colour people recognise is the FILL, not the label, so recognition survives.

   Both treatments apply to both locales without a second rule: the stylesheet is shared
   and the attribute is written by the one generator that emits every page.
   -------------------------------------------------------------------------- */

/* White primary: `bold` only. `accent-green` deliberately does NOT match this, so the
   terracotta primary declared above stands. */
:root[data-cta="bold"] .hero .btn--primary {
  color: var(--color-brand);
  background-color: #ffffff;
  background-image: none;
  border-color: #ffffff;
  box-shadow: 0 2px 8px hsl(200 60% 6% / 0.45), 0 12px 40px hsl(200 40% 4% / 0.5);
}

/* Green secondary: shared by `bold` and `accent-green`. */
:root[data-cta="bold"] .hero .btn--whatsapp,
:root[data-cta="accent-green"] .hero .btn--whatsapp {
  color: #08301a;
  background-color: #25d366;
  background-image: linear-gradient(to bottom, hsl(0 0% 100% / 0.2), hsl(0 0% 100% / 0) 55%);
  border: 0;
  box-shadow: 0 2px 8px hsl(200 60% 6% / 0.4), 0 12px 36px hsl(146 60% 35% / 0.45);
  backdrop-filter: none;
  -webkit-backdrop-filter: none;
}

@media (hover: hover) {
  :root[data-cta="bold"] .hero .btn--primary:hover {
    color: var(--color-brand);
    background-color: #eaf4f6;
    border-color: #eaf4f6;
  }
  :root[data-cta="bold"] .hero .btn--whatsapp:hover,
  :root[data-cta="accent-green"] .hero .btn--whatsapp:hover {
    color: #08301a;
    background-color: #1fbb59;
    border-color: transparent;
  }
}

/* -----------------------------------------------------------------------------
   5. SECTION RHYTHM AND HEADS  (centred stacks, house rule)
   -------------------------------------------------------------------------- */
.section { padding-block: clamp(3.5rem, 2.5rem + 4vw, 6rem); }

.section--alt {
  background-color: transparent;
  background-image: linear-gradient(to bottom, hsl(196 45% 93% / 0.85), hsl(196 40% 96% / 0.5));
}

.section__head--start,
.section__head--split {
  display: block;
  max-inline-size: 46rem;
  margin-inline: auto;
  margin-block-end: var(--space-2xl);
  text-align: center;
}

/* The rule under every heading is a two-stop gradient rather than a bar of flat colour:
   the same move as the rest of the page, at its smallest scale. */
.section__head--start::before,
.section__head--split::before {
  content: "";
  display: block;
  inline-size: 3.5rem;
  block-size: 4px;
  margin-inline: auto;
  margin-block-end: var(--space-md);
  border-radius: var(--radius-sm);
  background-image: linear-gradient(to right, var(--color-accent), var(--c-turquoise));
}

.section__head--start .section__intro,
.section__head--split .section__intro { margin-inline: auto; }

.section__title {
  font-size: clamp(1.9rem, 1.35rem + 2.4vw, 3.3rem);
  font-weight: 700;
  line-height: 1.08;
  letter-spacing: var(--tracking-display);
  color: var(--color-brand);
}

:root:lang(ar) .section__title { line-height: 1.35; }

.section__intro {
  margin-block-start: var(--space-md);
  font-size: var(--step-1);
  font-weight: 300;
  line-height: 1.6;
  max-inline-size: 36rem;
}

/* -----------------------------------------------------------------------------
   6. THE FOUR CONCERNS — raised glass cards
   -------------------------------------------------------------------------- */
.concerns__grid {
  display: grid;
  gap: var(--space-md);
  margin-block-start: var(--space-2xl);
}

@media (min-width: 48rem) { .concerns__grid { grid-template-columns: repeat(2, minmax(0, 1fr)); } }
@media (min-width: 60rem) { .concerns__grid { grid-template-columns: repeat(4, minmax(0, 1fr)); } }

.concern {
  position: relative;
  padding: var(--space-lg);
  padding-block-start: var(--space-xl);
  background-color: hsl(0 0% 100% / 0.78);
  border-radius: var(--radius-lg);
  box-shadow: var(--shadow-md), var(--shadow-ring);
  transition: transform var(--transition), box-shadow var(--transition);
}

@supports (backdrop-filter: blur(1px)) {
  .concern { backdrop-filter: blur(10px); -webkit-backdrop-filter: blur(10px); }
}

/* The coloured cap. A gradient edge along the top of each card, which is what makes a
   row of four read as one system rather than four boxes. */
.concern::before {
  content: "";
  position: absolute;
  inset-block-start: 0;
  inset-inline: var(--space-lg);
  block-size: 4px;
  border-end-start-radius: var(--radius-sm);
  border-end-end-radius: var(--radius-sm);
  background-image: linear-gradient(to right, var(--color-accent), var(--c-turquoise));
}

@media (hover: hover) {
  .concern:hover { transform: translateY(-4px); box-shadow: var(--shadow-lg), var(--shadow-ring); }
}

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

.concern__title { font-size: var(--step-1); font-weight: 600; line-height: 1.3; color: var(--color-brand); }
.concern__body { margin-block-start: var(--space-2xs); font-weight: 300; color: var(--color-text-muted); line-height: 1.65; }

/* -----------------------------------------------------------------------------
   7. OUR WORK — uniform grid of equal cells on a deep gradient band
   -------------------------------------------------------------------------- */
.work {
  color: var(--color-text-inverse);
  background-color: var(--color-brand-dark);
  background-image:
    radial-gradient(60% 80% at 85% 0%, hsl(190 78% 42% / 0.28), transparent 60%),
    radial-gradient(50% 70% at 0% 100%, hsl(17 70% 45% / 0.18), transparent 62%),
    linear-gradient(160deg, var(--color-brand) 0%, var(--color-brand-dark) 100%);
}

.work .section__title { color: var(--color-text-inverse); }
.work .section__intro { color: hsl(196 40% 86% / 0.85); }

.work__grid {
  display: grid;
  grid-template-columns: repeat(6, minmax(0, 1fr));
  gap: var(--space-2xs);
  margin-block-start: var(--space-xl);
}

@media (max-width: 75rem) { .work__grid { grid-template-columns: repeat(4, minmax(0, 1fr)); } }
@media (max-width: 48rem) { .work__grid { grid-template-columns: repeat(3, minmax(0, 1fr)); } }

.work__item {
  position: relative;
  overflow: hidden;
  margin: 0;
  border-radius: var(--radius-md);
  background-image: var(--media-placeholder);
  box-shadow: inset 0 0 0 1px hsl(190 60% 80% / 0.16);
}

.work__item img {
  display: block;
  inline-size: 100%;
  block-size: auto;
  aspect-ratio: var(--work-ratio);
  object-fit: cover;
  transition: transform var(--transition-slow), filter var(--transition-slow);
}

@media (hover: hover) {
  .work__item:hover img { transform: scale(1.06); filter: saturate(1.12) brightness(1.04); }
}

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

/* -----------------------------------------------------------------------------
   8. SURFACES — a ruled index
   -------------------------------------------------------------------------- */
.index__list {
  margin-block-start: var(--space-2xl);
  border-block-start: 2px solid var(--color-brand);
}

.index__row {
  display: grid;
  gap: var(--space-2xs) var(--space-xl);
  padding-block: var(--space-lg);
  padding-inline: var(--space-md);
  border-block-end: var(--border-hairline);
  border-radius: var(--radius-sm);
  transition: background-color var(--transition);
}

@media (min-width: 48rem) {
  .index__row { grid-template-columns: minmax(0, 1fr) minmax(0, 1.3fr); align-items: baseline; }
}

@media (hover: hover) {
  .index__row:hover { background-color: hsl(190 50% 92% / 0.55); }
}

.index__label { font-size: var(--step-2); font-weight: 600; line-height: 1.2; color: var(--color-brand); }
.index__note { font-weight: 300; color: var(--color-text-muted); line-height: 1.65; }

/* The cover on a post page. `--max-width-measure` would be too narrow for a 16:9 frame
   above a measure-width article, so it takes the container and the prose stays narrow. */
.post__cover {
  margin: 0 0 var(--space-xl);
  overflow: hidden;
  border-radius: var(--radius-lg);
  background-image: var(--media-placeholder);
  box-shadow: var(--shadow-md), var(--shadow-ring);
}

.post__cover img {
  display: block;
  inline-size: 100%;
  block-size: auto;
  aspect-ratio: 16 / 9;
  object-fit: cover;
}

/* A CAROUSEL ARROW THAT CANNOT GO ANYWHERE MUST LOOK LIKE IT.

   site.js already sets `disabled` on the arrow at each end of the track — that part was
   working. base.css styles `.carousel__btn` and `.carousel__btn[hidden]` but never styled
   `:disabled`, so the dead arrow rendered identically to the live one: full contrast, full
   shadow, hover response intact. A visitor clicks it, nothing moves, and the control looks
   broken rather than exhausted. At rest that is the arrow pointing back toward the start,
   which under RTL is the one on the RIGHT — the first thing a thumb reaches for.

   Faded rather than hidden. Removing it would shift the remaining arrow across the band as
   soon as the track moves, and a control that appears and disappears under your thumb is
   worse than one that is visibly unavailable. `disabled` already takes it out of the tab
   order and blocks the click, so this is purely making the existing state visible. */
.carousel__btn:disabled {
  opacity: 0.3;
  cursor: default;
  box-shadow: var(--shadow-ring);
}

@media (hover: hover) {
  .carousel__btn:disabled:hover {
    background-color: var(--color-surface);
    box-shadow: var(--shadow-ring);
  }
}

/* THE BLOG INDEX — a uniform grid of peers.

   Two earlier attempts failed for opposite reasons. The ruled two-column row wrapped
   post titles one word per line inside `container--narrow`. Then `.card-grid` shipped
   base.css's composed trio, which promotes the first card to a full-height lead: right
   for a features row where one item leads, wrong for an index where nine posts are
   equals and a bigger cell is a claim the content does not make. The operator's word
   for that was "uneven sizes".

   So: explicit column counts rather than `auto-fit`. `auto-fit` would stretch a lone
   trailing item across the full row whenever the count does not divide evenly, which is
   the same unevenness arriving through a different door. With fixed tracks, an orphan
   occupies one cell at the same width as every other. */
.blog-grid {
  display: grid;
  grid-template-columns: minmax(0, 1fr);
  gap: var(--space-md);
  margin-block-start: var(--space-2xl);
}

@media (min-width: 44rem) { .blog-grid { grid-template-columns: repeat(2, minmax(0, 1fr)); } }
@media (min-width: 64rem) { .blog-grid { grid-template-columns: repeat(3, minmax(0, 1fr)); gap: var(--space-lg); } }

/* Every cover the same frame. base.css cards default to `--media-ratio-card`; these are
   900x506 sources, so 16:9 crops nothing. */
.blog-card .card__media img { aspect-ratio: 16 / 9; }

/* Grid rows stretch and `.card` is already `block-size: 100%`, so cards in a row match.
   `flex: 1` on the stack plus `margin-block-start: auto` on the read row keeps the
   footer of every card on the same baseline instead of floating under a short excerpt. */
.blog-card .card__content {
  flex: 1;
  gap: var(--space-3xs);
}

.blog-card .card__title {
  font-size: var(--step-0);
  line-height: 1.35;
  color: var(--color-brand);
}

/* Clamped so one long excerpt cannot drive a whole row taller than the rest. Three
   lines is the shortest clamp that still holds the full first sentence of every post. */
.blog-card__text {
  display: -webkit-box;
  -webkit-box-orient: vertical;
  -webkit-line-clamp: 3;
  line-clamp: 3;
  overflow: hidden;
  font-size: var(--step--1);
}

.blog-card__more {
  display: inline-flex;
  align-items: center;
  gap: var(--space-3xs);
  margin-block-start: auto;
  padding-block-start: var(--space-2xs);
  font-size: var(--step--1);
  font-weight: 600;
  color: var(--color-accent);
}

.blog-card__more .icon { inline-size: 1em; block-size: 1em; transition: transform var(--transition); }

@media (hover: hover) {
  .blog-card:hover .card__title { color: var(--color-accent); }
  .blog-card:hover .blog-card__more .icon { transform: translateX(4px); }
  [dir="rtl"] .blog-card:hover .blog-card__more .icon { transform: translateX(-4px); }
}

/* -----------------------------------------------------------------------------
   9. ACCESS — two raised panels
   -------------------------------------------------------------------------- */
.access__grid { display: grid; gap: var(--space-md); margin-block-start: var(--space-2xl); }

@media (min-width: 48rem) { .access__grid { grid-template-columns: repeat(2, minmax(0, 1fr)); } }

.access__block {
  padding: var(--space-xl);
  background-color: #ffffff;
  background-image: radial-gradient(90% 120% at 100% 0%, hsl(190 60% 92% / 0.9), transparent 60%);
  border: 0;
  border-radius: var(--radius-lg);
  box-shadow: var(--shadow-md), var(--shadow-ring);
}

/* A photograph of the method itself. Not decoration: "rope access" and "boom lift" are
   two words that mean nothing to a reader who has not seen either, and the whole band
   exists to answer how the crew reaches the elevation. */
.access__media {
  display: block;
  /* `max-inline-size: none` is load-bearing: base.css sets `max-inline-size: 100%` on
     every img, which silently caps this bleed at the content box and leaves a bare
     strip the width of the padding down one side of the card. */
  inline-size: calc(100% + var(--space-xl) * 2);
  max-inline-size: none;
  margin-inline: calc(-1 * var(--space-xl));
  margin-block: calc(-1 * var(--space-xl)) var(--space-lg);
  block-size: auto;
  aspect-ratio: 3 / 2;
  object-fit: cover;
  border-start-start-radius: var(--radius-lg);
  border-start-end-radius: var(--radius-lg);
}

.access__title { font-size: var(--step-2); font-weight: 600; color: var(--color-brand); }

.access__when {
  display: block;
  inline-size: fit-content;
  margin-block-start: var(--space-sm);
  padding: 3px var(--space-2xs);
  font-size: var(--step--1);
  font-weight: 600;
  letter-spacing: var(--tracking-label);
  text-transform: var(--label-transform);
  color: var(--color-brand-contrast);
  background-color: var(--color-accent);
  border-radius: var(--radius-sm);
}

.access__body { margin-block-start: var(--space-md); font-weight: 300; color: var(--color-text-muted); line-height: 1.7; }

/* -----------------------------------------------------------------------------
   10. PROCESS
   -------------------------------------------------------------------------- */
.step__number {
  inline-size: 2.75rem;
  block-size: 2.75rem;
  font-weight: 700;
  color: var(--color-brand-contrast);
  background-color: var(--color-brand);
  background-image: linear-gradient(160deg, var(--c-turquoise), var(--color-brand) 70%);
  border-radius: var(--radius-md);
  box-shadow: var(--shadow-md);
}

.section--alt .step__number, .section .step__number { box-shadow: var(--shadow-md); }
.process__grid > .step::before { background-image: linear-gradient(to bottom, var(--color-border), var(--color-border)); }
.step__title { font-size: var(--step-1); font-weight: 600; color: var(--color-brand); }
.step__body { font-weight: 300; }

/* -----------------------------------------------------------------------------
   11. FAQ — raised rows
   -------------------------------------------------------------------------- */
.faq__list { gap: var(--space-2xs); }

.faq__item {
  padding-inline: var(--space-md);
  background-color: hsl(0 0% 100% / 0.8);
  border-radius: var(--radius-md);
  box-shadow: var(--shadow-sm), var(--shadow-ring);
}

.faq__item[open] { box-shadow: var(--shadow-md), var(--shadow-ring); }

@media (hover: hover) {
  .faq__item:hover .faq__question { color: var(--color-accent); }
}

.faq__summary { padding-block: var(--space-md); }
.faq__question { font-size: var(--step-1); font-weight: 600; color: var(--color-brand); transition: color var(--transition); }
.faq__marker { color: var(--color-accent); }
.faq__answer { max-inline-size: 44rem; font-weight: 300; line-height: 1.75; }

/* -----------------------------------------------------------------------------
   12. CLOSING BAND AND FOOTER — deep gradient with a bloom
   -------------------------------------------------------------------------- */
.cta {
  color: var(--color-text-inverse);
  background-color: var(--color-brand-dark);
  background-image:
    radial-gradient(55% 100% at 82% 0%, hsl(190 78% 45% / 0.3), transparent 62%),
    radial-gradient(45% 90% at 8% 100%, hsl(17 74% 46% / 0.24), transparent 60%),
    linear-gradient(150deg, var(--color-brand) 0%, var(--color-brand-dark) 100%);
}

.cta__inner { text-align: center; }

.cta__title {
  font-size: clamp(1.9rem, 1.35rem + 2.4vw, 3.3rem);
  font-weight: 700;
  line-height: 1.08;
  letter-spacing: var(--tracking-display);
  max-inline-size: 24ch;
  margin-inline: auto;
}

:root:lang(ar) .cta__title { line-height: 1.35; }

.cta__body { margin-block-start: var(--space-md); font-weight: 300; max-inline-size: 36rem; margin-inline: auto; opacity: 0.86; }
.cta__actions { justify-content: center; gap: var(--space-md); align-items: center; }
.cta .btn--primary { background-color: var(--color-accent); color: #ffffff; box-shadow: var(--shadow-accent); }
.cta .link-arrow { color: var(--color-text-inverse); }

.site-footer {
  background-color: var(--color-brand-dark);
  background-image: radial-gradient(70% 120% at 50% 0%, hsl(190 70% 40% / 0.14), transparent 60%);
  border-block-start: 1px solid hsl(190 50% 60% / 0.2);
}

.footer-title { color: var(--c-turquoise-light); opacity: 1; font-weight: 600; }
.site-footer__about { font-weight: 300; }

/* -----------------------------------------------------------------------------
   13. FLOAT CTAs
   -------------------------------------------------------------------------- */
.float-ctas { transition: opacity var(--transition), transform var(--transition); }
.float-ctas.is-hidden { opacity: 0; transform: translateY(140%); pointer-events: none; }

@media (prefers-reduced-motion: reduce) {
  .float-ctas { transition: none; }
  .float-ctas.is-hidden { transform: none; }
}

.float-cta { border-radius: var(--radius-md); box-shadow: var(--shadow-lg); }
.float-cta--primary { color: #ffffff; background-color: var(--color-accent); background-image: linear-gradient(to bottom, hsl(0 0% 100% / 0.15), transparent 60%); }
.float-cta--secondary { color: #ffffff; background-color: var(--color-brand); background-image: linear-gradient(to bottom, hsl(0 0% 100% / 0.15), transparent 60%); }

/* =============================================================================
   15. v6 ADDITIONS — the review round that kept the design and changed the parts
   ========================================================================== */

/* -----------------------------------------------------------------------------
   15.1 A TALLER HERO, AND FOUR BENEFITS IN IT

   The benefits are a custom class rather than base.css's `.hero__bullets`, on purpose:
   that class hard-hides the third and later chips below 48rem so the primary action
   cannot be pushed off a 375px screen. Four benefits were asked for, so they get a
   layout that shows all four at every width instead of a rule that silently drops two:
   2x2 on a phone, one row from 48rem.
   -------------------------------------------------------------------------- */
:root { --hero-band: clamp(38rem, 90vh, 54rem); }

.hero__benefits {
  display: grid;
  grid-template-columns: repeat(2, minmax(0, 1fr));
  gap: var(--space-2xs);
  margin-block-start: var(--space-lg);
  max-inline-size: 40rem;
}

@media (min-width: 48rem) {
  .hero__benefits { grid-template-columns: repeat(4, minmax(0, 1fr)); gap: var(--space-xs); }
}

/* Frosted chips rather than plain text: on a photograph, a label needs its own ground
   or its contrast depends on whatever pixel it happens to land on. The fill is a
   turquoise glass at 0.18 over the hero scrim, which keeps white text above 12:1. */
/* The benefits are INFORMATION. A border and a fill make each one look pressable, so
   both go: what is left is an icon, a word, and a hairline above the row. They still
   group and still read, and they no longer compete with the two things that are
   actually clickable. */
.hero__benefit {
  display: flex;
  align-items: center;
  gap: var(--space-2xs);
  padding-block: var(--space-2xs);
  padding-inline-end: var(--space-sm);
  font-size: var(--step--1);
  font-weight: 500;
  line-height: 1.3;
  color: var(--color-text-inverse);
  background-color: transparent;
  border: 0;
  border-block-start: 1px solid hsl(190 60% 80% / 0.28);
  border-radius: 0;
}

.hero__benefit .icon {
  flex: none;
  color: var(--c-turquoise-light);
  inline-size: var(--icon-size-lg);
  block-size: var(--icon-size-lg);
}

/* WhatsApp is the second hero action. Frosted rather than a second solid fill, so the
   call button stays the single primary and the pair does not read as two equals. */
.btn--whatsapp {
  color: var(--color-text-inverse);
  background-color: hsl(190 60% 70% / 0.16);
  border-color: hsl(190 70% 80% / 0.45);
  box-shadow: none;
}

@supports (backdrop-filter: blur(1px)) {
  .btn--whatsapp { backdrop-filter: blur(10px); -webkit-backdrop-filter: blur(10px); }
}

@media (hover: hover) {
  .btn--whatsapp:hover { background-color: hsl(190 60% 78% / 0.28); border-color: var(--color-text-inverse); }
}

/* -----------------------------------------------------------------------------
   15.2 THE FIXED CALL BAR — two flush rectangles, nothing else

   Was: two floating pills with a gap between them, a label and a second line of meta
   text each. Now: one block, split in two, no gap, no radius between them, icon plus
   one word. The gap is removed by making the pair a single grid with a 1px rule, so
   there is no page showing through between the two halves at any width.
   -------------------------------------------------------------------------- */
.float-ctas {
  inset-inline-end: 0;
  inset-block-end: 0;
  flex-direction: row;
  gap: 0;
  border-start-start-radius: var(--radius-md);
  overflow: hidden;
  box-shadow: var(--shadow-xl);
}

.float-cta {
  inline-size: auto;
  block-size: auto;
  min-block-size: 3.5rem;
  padding-inline: var(--space-md);
  padding-block: var(--space-2xs);
  gap: var(--space-2xs);
  border-radius: 0;
  box-shadow: none;
}

.float-cta + .float-cta { box-shadow: inset 1px 0 0 hsl(0 0% 100% / 0.22); }

/* The label is real text at every width, not a clipped accessible name. */
.float-cta__label {
  position: static;
  inline-size: auto;
  block-size: auto;
  overflow: visible;
  clip-path: none;
  font-size: var(--step-0);
  font-weight: 600;
}

@media (hover: hover) {
  .float-cta:hover { transform: none; }
  .float-cta--primary:hover { background-color: #a04214; }
  .float-cta--secondary:hover { background-color: var(--color-brand-dark); }
}

@media (max-width: 40rem) {
  .float-ctas { inset-inline: 0; border-start-start-radius: 0; }
  .float-cta { flex: 1; flex-direction: row; min-block-size: 3.75rem; border-radius: 0; }
  .float-cta .icon { margin-block-end: 0; inline-size: var(--icon-size-lg); block-size: var(--icon-size-lg); }
}

/* -----------------------------------------------------------------------------
   15.3 ILLUSTRATED CONCERN CARDS

   An illustration is not an icon: 11-illustrations.md section 2 is explicit that the
   two classes may never stand in for one another, and that a sprite glyph scaled up is
   the observed failure. These are composed 240-unit scenes of four to six overlapping
   objects, solid flat fill, one hue plus one accent object, no letterforms.
   -------------------------------------------------------------------------- */
.concern { padding-block-start: var(--space-lg); text-align: center; }

/* The plate is declared rather than inherited. base.css puts `background-color:
   --color-surface-alt` on every <img> so a slow photograph never shows as a hole, which
   on a transparent SVG leaves a hard-edged grey square behind the drawing. Here it
   becomes a deliberate rounded tint with padding, which is also what gives the four
   drawings a common ground and makes them read as a set. */
.concern__illus {
  display: block;
  inline-size: clamp(5.5rem, 30%, 7rem);
  block-size: auto;
  margin-inline: auto;
  margin-block-end: var(--space-md);
  padding: var(--space-2xs);
  background-color: hsl(190 48% 92%);
  border-radius: var(--radius-lg);
}

/* -----------------------------------------------------------------------------
   15.4 SURFACES — a photograph per row

   Not an illustration. 11-illustrations.md section 2.2: if the element needs to prove
   something, it is a photograph, and a drawing reached for as a substitute is the worst
   thing that file could cause. There is a real client frame of every one of these five
   surfaces, so each row carries the actual material rather than a drawing of it.
   -------------------------------------------------------------------------- */
.index__row { padding-inline: var(--space-sm); }

@media (min-width: 48rem) {
  .index__row {
    grid-template-columns: 5.5rem minmax(0, 1fr) minmax(0, 1.25fr);
    align-items: center;
    gap: var(--space-lg);
  }
}

.index__thumb {
  inline-size: 5.5rem;
  block-size: 5.5rem;
  object-fit: cover;
  border-radius: var(--radius-md);
  box-shadow: var(--shadow-sm), var(--shadow-ring);
}

@media (max-width: 48rem) {
  .index__thumb { inline-size: 4rem; block-size: 4rem; }
}

/* -----------------------------------------------------------------------------
   15.5 THE WORK SLIDESHOW

   base.css ships `.carousel` and site.js wires `[data-carousel]` with the ONE
   direction-aware line in the whole front end, which is exactly the RTL scroll-origin
   problem that broke a hand-rolled rail earlier in this project. So this uses the
   shipped component rather than a second implementation of it.

   Slides are large and show the WHOLE frame: `object-fit: contain` on a deep ground,
   because the point of the slideshow is seeing the photograph rather than a crop of it.
   -------------------------------------------------------------------------- */
.work__slideshow { margin-block-start: var(--space-xl); }

.work__slideshow .carousel__track {
  display: flex;
  gap: var(--space-sm);
  list-style: none;
  padding-inline: 0;
  scroll-snap-type: x mandatory;
}

.work__slide {
  flex: 0 0 100%;
  scroll-snap-align: center;
}

/* Narrow enough that a 4:5 client frame nearly fills its slide. `contain` is what shows
   the whole photograph, but a wide slide around a portrait source is mostly letterbox,
   so the slide is sized close to the source ratio instead of the band. */
@media (min-width: 48rem) { .work__slide { flex-basis: 44%; } }
@media (min-width: 60rem) { .work__slide { flex-basis: 31%; } }

.work__slide figure {
  margin: 0;
  border-radius: var(--radius-lg);
  overflow: hidden;
  background-color: hsl(203 60% 8%);
  box-shadow: inset 0 0 0 1px hsl(190 60% 80% / 0.18);
}

.work__slide img {
  display: block;
  inline-size: 100%;
  block-size: clamp(18rem, 46vh, 30rem);
  object-fit: contain;
  background-color: hsl(203 60% 8%);
}

.carousel__btn {
  color: var(--color-brand);
  background-color: var(--color-surface);
  border-radius: var(--radius-md);
  box-shadow: var(--shadow-md);
}

@media (hover: hover) {
  .carousel__btn:hover { background-color: var(--color-accent); color: #ffffff; }
}

.work__more { display: flex; justify-content: center; margin-block-start: var(--space-xl); }

.work .btn--ghost {
  color: var(--color-text-inverse);
  background-color: hsl(190 60% 70% / 0.14);
  border-color: hsl(190 70% 80% / 0.42);
}

@media (hover: hover) {
  .work .btn--ghost:hover { background-color: hsl(190 60% 78% / 0.26); border-color: var(--color-text-inverse); }
}

/* -----------------------------------------------------------------------------
   15.6 THE GALLERY PAGE
   -------------------------------------------------------------------------- */
/* Explicit tracks rather than `auto-fill`, for the same reason as `.blog-grid`: with ten
   photographs auto-fill settled on four columns and left a two-cell stub on the last row.
   Ten divides evenly by 2 and by 5, so those are the two counts used — two full rows of
   five on a wide screen, five rows of two on a narrow one, and no orphan either way. */
.gallery-grid {
  display: grid;
  grid-template-columns: repeat(2, minmax(0, 1fr));
  gap: var(--space-md);
  margin-block-start: var(--space-2xl);
}

@media (min-width: 62rem) {
  .gallery-grid { grid-template-columns: repeat(5, minmax(0, 1fr)); gap: var(--space-sm); }
}

.gallery-grid figure {
  margin: 0;
  overflow: hidden;
  border-radius: var(--radius-lg);
  background-image: var(--media-placeholder);
  box-shadow: var(--shadow-md), var(--shadow-ring);
}

.gallery-grid img {
  display: block;
  inline-size: 100%;
  block-size: auto;
  aspect-ratio: 4 / 5;
  object-fit: cover;
  transition: transform var(--transition-slow);
}

@media (hover: hover) { .gallery-grid figure:hover img { transform: scale(1.045); } }
@media (prefers-reduced-motion: reduce) { .gallery-grid figure:hover img { transform: none; } }

/* -----------------------------------------------------------------------------
   16. APPROVAL-PREVIEW CHROME — DELETE THIS BLOCK BEFORE LAUNCH
   -------------------------------------------------------------------------- */
.approval-note {
  padding-block: var(--space-sm);
  font-size: var(--step--1);
  line-height: 1.55;
  color: var(--color-text-inverse);
  background-image: linear-gradient(120deg, var(--color-brand-dark), var(--color-brand));
}

.approval-note__title { display: block; margin-block-end: var(--space-3xs); font-size: var(--step-0); font-weight: 700; color: var(--c-turquoise-light); }
.approval-note p { max-inline-size: var(--max-width-narrow); opacity: 0.88; font-weight: 300; }
.approval-note ul { display: flex; flex-wrap: wrap; gap: var(--space-3xs) var(--space-2xs); margin-block-start: var(--space-2xs); }
.approval-note li { padding: 2px var(--space-2xs); border: 1px solid hsl(190 50% 70% / 0.3); border-radius: var(--radius-sm); }

.ph {
  padding-inline: 0.35em;
  font-style: normal;
  font-weight: 600;
  color: #ffffff;
  background-color: var(--color-accent);
  border-radius: var(--radius-sm);
  unicode-bidi: isolate;
}

/* -----------------------------------------------------------------------------
   MOBILE NAV — "THE BUTTON BECOMES THE PANEL"

   The menu surface grows out of the toggle: it starts at the button's own size and
   position and expands into the panel, so the two read as one object.

   It is `transform: scale()` on a single solid layer, NOT clip-path. clip-path is
   the obvious way to write a reveal-from-a-point and it is the wrong one — Chromium
   does not composite clip-path animations, so a panel-sized circle repaints every
   frame at a cost proportional to its area. A scaled rectangle is the same picture,
   on the compositor. Nothing here animates anything but `transform` and `opacity`;
   `visibility` changes once, discretely, at each end.

   WHY THE SURFACE IS A PSEUDO-ELEMENT AND NOT THE PANEL. Three reasons, all
   load-bearing: text inside a scaled box would zoom and distort; a transformed
   .site-nav would become the containing block for any position:fixed descendant;
   and fading the panel as well as its rows would create group opacity, which forces
   an offscreen render surface. Panel box static, surface scales, rows fade — no
   layer does two jobs.

   DIRECTION. Nothing slides on the inline axis, and the one value that cannot be
   expressed logically — transform-origin — is measured by brand.js from physical
   rects, so it comes out right under rtl and ltr from the same code. There is not a
   single [dir] rule or --dir scalar in this file.
   -------------------------------------------------------------------------- */

@media (max-width: 60rem) {
  /* M3 emphasized curves. Entry decelerates, exit accelerates, exit is 0.7x entry —
     the same curve in both directions is the cheapest tell there is. */
  html.js-nav {
    --nav-in-d: 260ms; --nav-out-d: 180ms;
    --nav-in-e: cubic-bezier(0.05, 0.7, 0.1, 1);
    --nav-out-e: cubic-bezier(0.3, 0, 0.8, 0.15);
  }

  /* NO SCRIPT, NO HIDING. base.css closes the panel with display:none and only
     script can undo that, so a visitor whose JS never runs would get no navigation
     at all. Everything below is gated on .js-nav, which brand.js adds as its first
     statement; without it the panel falls through to a plain stacked list and the
     toggle disappears. The links are in the DOM and `visibility` never removes them,
     so they stay crawlable in every state, closed included. */
  html:not(.js-nav) .site-nav { display: flex; }
  html:not(.js-nav) .nav-toggle { display: none; }

  /* Everything in sections 3 and 4 is deliberately NOT gated on .js-nav, and the
     panel carries the dark fill directly, so the no-script fallback is the same
     designed card rather than base.css's paper band with the styling stripped off.
     The scripted path overrides the fill in section 1 and lets ::before paint it. */
  .site-nav { background-color: var(--color-brand-dark); }

  /* --- 1. THE PANEL BOX — laid out, never painted, never transformed ------- */
  html.js-nav .site-nav {
    /* `display` cannot be transitioned, and a display:none box has no rect, so
       brand.js could not measure where the surface must grow from. The panel is
       therefore always laid out and `visibility` carries the state: discrete, but it
       honours transition-delay, which is the whole trick — it can be held until the
       exit finishes. It also drops the closed links out of the tab order and the
       accessibility tree for free, so nothing needs tabindex or aria-hidden. */
    display: flex;
    visibility: hidden;
    /* iOS-26 floating-card geometry without the glass: inset from both edges,
       rounded on all four corners, lifted. This is what stops it reading as the 2014
       dropdown band. inset-inline:0 comes from base.css; the margins pull it in. */
    margin-inline: var(--gutter);
    margin-block-start: var(--space-2xs);
    padding: var(--space-2xs) 0 var(--space-xs);
    border-radius: var(--radius-xl);
    isolation: isolate;
    /* base.css paints the panel; a separate layer does it here, so strip it. */
    background: none; border-block-end: 0; box-shadow: none;
    --nav-seed: var(--nav-seed-measured, 0.11);
  }

  /* --- 2. THE SURFACE — the object that used to be the button -------------- */
  html.js-nav .site-nav::before {
    content: "";
    /* Absolute, so it is not a flex item of the panel. Sized to the panel exactly,
       so scale never exceeds 1: the layer rasterises at its natural size and is only
       ever MINIFIED while animating. Growing a small element to many times its size
       is what makes hand-rolled circular reveals look soft at the edges. */
    position: absolute; inset: 0; z-index: 0;
    border-radius: inherit;
    background-color: var(--color-brand-dark);
    /* The shadow scales with the surface, so elevation grows as the object does.
       Nothing repaints — box-shadow is baked into the layer once. */
    box-shadow: var(--shadow-xl), inset 0 1px 0 hsl(0 0% 100% / 0.06);
    /* THE EFFECT, ENTIRE. brand.js writes transform-origin as the toggle's centre in
       this element's own coordinates; `50% 0` is the symmetric fallback for the
       frames before that lands, and for a page where the measurement fails. */
    transform: scale(var(--nav-seed));
    transform-origin: var(--nav-origin-x, 50%) var(--nav-origin-y, 0px);
    opacity: 0;
  }

  html.js-nav .site-nav.is-open { visibility: visible; }
  html.js-nav .site-nav.is-open::before { transform: scale(1); opacity: 1; }

  /* --- 3. THE ROWS — content never scales, it only fades and drifts -------- */
  .site-nav__list,
  .site-nav__langs { position: relative; z-index: 1; }

  /* The LIST scrolls, not the panel: an absolutely positioned surface inside a
     scroll container scrolls away with the content and leaves the foot of the panel
     unpainted. Landscape, 200% text and 400% zoom all need this, and the language
     row stays pinned below it. dvh, or the iOS URL bar eats the last row. */
  .site-nav__list {
    max-block-size: calc(100dvh - var(--header-height) - 9rem);
    overflow-y: auto; overscroll-behavior: contain;
  }

  /* Gated — without script these rows must simply be visible. */
  html.js-nav .site-nav li { opacity: 0; translate: 0 6px; }
  html.js-nav .site-nav.is-open li { opacity: 1; translate: none; }

  /* Stagger index without touching the generator; row 1 uses the var() fallback.
     22ms x 6 is a 132ms tail and the last row settles near 350ms — inside the 400ms
     panel ceiling and the 500ms recent-input window. The 100ms step that ships in
     most tutorials would put it at 820ms, which is the "laggy menu" complaint. */
  .site-nav__list > li:nth-child(2) { --nav-i: 1; }
  .site-nav__list > li:nth-child(3) { --nav-i: 2; }
  .site-nav__list > li:nth-child(4) { --nav-i: 3; }
  .site-nav__list > li:nth-child(5) { --nav-i: 4; }
  .site-nav__list > li:nth-child(6) { --nav-i: 5; }
  .site-nav__langs > li { --nav-i: 6; }

  /* --- 4. ROW CRAFT — 3.25rem rows on dark, not 14px links with hairlines -- */
  .site-header .site-nav a {
    display: flex; align-items: center;
    min-block-size: 3.25rem;
    padding-block: 0; padding-inline: var(--space-sm);
    border-block-end: 0;
    /* Logical, so the current-page marker lands on the reading edge in both
       directions from one declaration. */
    border-inline-start: 3px solid transparent;
    color: var(--color-text-inverse);
    font-size: var(--step-0); font-weight: 500;
  }
  /* Turquoise at 14% is a rule, never a word: #17879b is 3.9:1 and fails as text. */
  .site-nav__list > li + li > a,
  .site-nav__langs { box-shadow: inset 0 1px 0 hsl(190 74% 55% / 0.14); }

  .site-header .site-nav__link.is-current {
    border-inline-start-color: var(--color-accent);
    background-color: hsl(17 76% 40% / 0.13);
    color: #fff;
  }

  .site-nav__langs {
    margin-block-start: var(--space-2xs); padding-block-start: var(--space-2xs);
  }
  .site-header .site-nav__lang {
    justify-content: center;
    margin-inline: var(--space-sm); min-block-size: 2.75rem;
    border-inline-start: 0; border-radius: var(--radius-md);
    background-color: hsl(0 0% 100% / 0.08);
    font-size: var(--step--1);
  }

  /* --- 5. THE TOGGLE — same material, so the growth reads as continuous ---- */
  /* base.css puts `margin-inline-start: auto` on the toggle to push it to the trailing
     edge, which was right when the toggle sat before the call button. The toggle is now
     last in source, so the auto margin has to move with the job: it goes on the call
     button, and the pair travels to the edge together with the toggle outermost. Leaving
     it on the toggle would strand the call button back against the brand. */
  .site-header__cta { margin-inline-start: auto; }
  .nav-toggle { margin-inline-start: 0; }

  html.js-nav .nav-toggle {
    /* inline-grid so both icons stack in one cell and can cross-fade; base.css
       swaps them with display none/block, which cannot animate. */
    display: inline-grid; place-items: center;
    position: relative; isolation: isolate;
    /* Already 2.75rem in base.css; restated as a floor so a later padding change
       cannot quietly drop the target below 44px (AAA / Apple HIG — the AA floor is
       24px). The button box is the target; never a pointer-events:none pseudo. */
    min-inline-size: 2.75rem; min-block-size: 2.75rem;
    color: var(--color-brand);
  }
  html.js-nav .nav-toggle::before {
    content: ""; position: absolute; inset: 0; z-index: -1;
    border-radius: inherit; background-color: var(--color-brand-dark);
    transform: scale(0.4); opacity: 0;
  }
  html.js-nav .nav-toggle[aria-expanded="true"] { color: var(--color-text-inverse); }
  html.js-nav .nav-toggle[aria-expanded="true"]::before { transform: scale(1); opacity: 1; }

  /* Only opacity and transform touch the <svg>. The sprite is stroke:currentColor
     and cannot be filled from CSS, and stroke-width:0 makes an icon vanish silently. */
  html.js-nav .nav-toggle .icon { grid-area: 1 / 1; rotate: 0deg; }
  html.js-nav .nav-toggle__close { opacity: 0; rotate: -60deg; }
  html.js-nav .nav-toggle[aria-expanded="true"] .nav-toggle__open { opacity: 0; rotate: 60deg; }
  html.js-nav .nav-toggle[aria-expanded="true"] .nav-toggle__close { opacity: 1; rotate: 0deg; }

  /* --- 6. THE SCRIM — free, and it closes the menu without a line of JS ---- */
  html.js-nav .site-header::after {
    content: "";
    /* absolute, not fixed: .site-header already carries a backdrop-filter, which
       makes it a containing block for fixed descendants, so a fixed scrim would
       silently re-anchor to the header box. Absolute inside the sticky header is
       unambiguous. z-index:-1 keeps it above the header's own background and below
       .site-header__inner, so the panel stays on top of it. */
    position: absolute; inset-inline: 0; inset-block-start: 100%; z-index: -1;
    block-size: 100dvh;
    /* Flat, never blurred. The header above it already runs one full-width
       backdrop-filter; a second on a viewport-sized surface has the compositor read
       back and re-blur ~2.5M device pixels on a mid-range Android every frame the
       scrim's own opacity is moving. */
    background-color: hsl(203 60% 8% / 0.5);
    opacity: 0; visibility: hidden;
    /* Stops a pan that begins on the scrim leaking to the page on iOS, where
       overflow:hidden on the root does not gate touch panning. Deliberately not on
       the panel or the body — that would kill scrolling inside the menu. */
    touch-action: none;
  }
  /* DISMISS COMES FREE. A pseudo-element is not an event target, so a tap here
     reports .site-header, which site.js's outside-click check treats as neither nav
     nor toggle and closes. No second handler, nothing duplicated. */
  html.nav-open .site-header::after { opacity: 1; visibility: visible; }

  /* --- 7. WHAT RECEDES ---------------------------------------------------- */
  /* .brand and .site-header__cta go inert while the menu is open, and a control that
     is visible but dead is worse than one that is gone. It also leaves the toggle
     alone at full strength, which is the point of the whole effect. */
  html.nav-open .brand,
  html.nav-open .site-header__cta { opacity: 0.3; }

  /* The call bar is z-index 30 — above the header — so it would float over the
     scrim, outside the trap, bright and tappable. visibility also takes it out of
     the tab order, so it needs no inert of its own. */
  html.nav-open .float-ctas { opacity: 0; visibility: hidden; translate: 0 100%; }

  /* --- 8. SCROLL LOCK ----------------------------------------------------- */
  /* overflow on the ROOT only. The most-copied recipe also puts position:fixed and
     overflow:hidden on <body>, which makes body the sticky header's scrollport — the
     header then sticks to the top of a box parked at -800px and the whole menu
     leaves the screen. scrollbar-gutter keeps the classic scrollbar's space reserved
     at the narrow desktop widths this query also matches, so the lock cannot shift
     the layout. */
  html { scrollbar-gutter: stable; }
  html.nav-open { overflow: hidden; overscroll-behavior: none; }

  /* --- 9. TRANSITIONS, ARMED ONLY AFTER FIRST PAINT ----------------------- */
  /* base.css declares the closed state INSIDE this media query. When the query
     starts or stops matching — rotating the phone, dragging a desktop window past
     60rem — those properties change and the browser transitions them, so the panel
     animates when nobody touched it. There is no CSS fix yet (transition-scope is
     still a proposal), so the transition declarations themselves are gated on
     .nav-ready, and brand.js drops that class for the duration of a resize. */
  /* The panel and the scrim share a shape: hold `visibility` for the exit duration
     so the closing surface is never hidden before it has finished leaving, and drop
     the hold to 0s on the way in. The panel's own opacity never moves; listing it
     costs nothing and lets one pair of rules cover both elements. */
  html.nav-ready .site-nav,
  html.nav-ready .site-header::after {
    transition: opacity var(--nav-out-d) linear, visibility 0s linear var(--nav-out-d);
  }
  html.nav-ready .site-nav.is-open,
  html.nav-ready.nav-open .site-header::after {
    transition: opacity var(--nav-in-d) linear, visibility 0s;
  }

  html.nav-ready .site-nav::before {
    transition: transform var(--nav-out-d) var(--nav-out-e), opacity 110ms linear;
  }
  html.nav-ready .site-nav.is-open::before {
    /* Solid within ~90ms, long before it has finished growing: what you see is the
       button expanding, not a shape fading up. */
    transition: transform var(--nav-in-d) var(--nav-in-e), opacity 90ms linear;
  }

  /* No delay on the closed rule, so the rows leave together. A mirrored stagger
     doubles perceived close latency and is the usual source of "laggy". */
  html.nav-ready .site-nav li,
  html.nav-ready .brand,
  html.nav-ready .site-header__cta,
  html.nav-ready .float-ctas {
    transition: opacity 110ms linear, translate 160ms var(--nav-out-e);
  }
  html.nav-ready .site-nav.is-open li {
    transition: opacity 150ms linear, translate 220ms var(--nav-in-e);
    transition-delay: calc(40ms + var(--nav-i, 0) * 22ms);
  }

  /* base.css transitions background-color here for :hover; keep it. */
  html.nav-ready .nav-toggle { transition: color 140ms linear, background-color var(--transition); }
  html.nav-ready .nav-toggle::before,
  html.nav-ready .nav-toggle .icon {
    transition: transform 220ms var(--nav-in-e), rotate 240ms var(--nav-in-e), opacity 140ms linear;
  }

  /* --- 10. REDUCED MOTION ------------------------------------------------- */
  /* Reduce means less motion, not a frozen page. One declaration does the work: a
     seed of 1 starts the surface at full size, so the growth collapses to a
     cross-fade with no displacement at all — and the delayed `visibility` still
     resolves, so the panel is never invisible-but-clickable. A blanket
     `transition: none` strands exactly that, which is the classic bug here. */
  @media (prefers-reduced-motion: reduce) {
    html.js-nav .site-nav { --nav-seed: 1; }
    /* The stagger goes entirely: items arriving at different times is motion over
       time even when each one barely moves. */
    html.nav-ready .site-nav.is-open li { transition-delay: 0s; }
    html.js-nav .site-nav li,
    html.nav-open .float-ctas { translate: none; }
    /* Written at the open state's own specificity, not below it, or the 60deg from
       section 5 would still win and the icon would still spin. */
    html.js-nav .nav-toggle .icon,
    html.js-nav .nav-toggle[aria-expanded="true"] .icon { rotate: none; }
    html.js-nav .nav-toggle::before { transform: none; }
  }
}

