/* The footer wordmark. Identical on all 96 pages. */

    /* ─────────────────────────────────────────────────────────────────
       1 · FOOTER WORDMARK
       The greyed mark sat hard against the footer above it, and at phone
       widths "RAYAN ARCHITECTS" is wider than the screen: nowrap plus the
       band's own overflow:hidden clipped ARCHITECTS off the right edge.
       Space above it at every width; on phones it wraps at its own space
       into two centred lines instead of being cut.
       ───────────────────────────────────────────────────────────────── */
    .foot-megamark { margin-top: clamp(3rem, 7vw, 5.5rem); }
    @media (max-width: 760px) {
      .foot-megamark { padding: 0 1rem; line-height: 0.92; }
      .foot-megamark span {
        white-space: normal;
        font-size: clamp(2.4rem, 15vw, 5rem);
        text-align: center;
      }
    }

    /* ─────────────────────────────────────────────────────────────────
       2 · MOBILE NAV OVERLAY
       It was four 16px words centred in an empty screen. It is now a full
       menu page: display-scale rows on hairlines, a numbered index, the
       practice's contact details under it, and a wipe rather than a fade.
       Desktop is untouched — every rule here is inside the burger's range.
       ───────────────────────────────────────────────────────────────── */
    .nav-overlay__eyebrow,
    .nav-overlay__foot { display: none; }
    /* The chat widget sits at z-index 1000 and was floating over the open
       menu, landing on top of the contact line. */
    .nav-overlay { z-index: 1100; }

    @media (max-width: 899px) {
      .nav-overlay {
        place-items: stretch;
        /* Wipes down over the page. Opacity alone read as a dissolve with no
           direction, which is why the menu felt like it had no arrival. */
        clip-path: inset(0 0 100% 0);
        transition: opacity 260ms cubic-bezier(0.33, 1, 0.68, 1),
                    clip-path 620ms cubic-bezier(0.16, 1, 0.3, 1);
      }
      .nav-overlay.is-open { clip-path: inset(0 0 0 0); }

      .nav-overlay-inner {
        width: 100%;
        max-width: none;
        height: 100%;
        grid-template-columns: 1fr;
        grid-template-rows: auto 1fr auto;
        align-items: stretch;
        gap: 0;
        padding: calc(env(safe-area-inset-top, 0px) + 5.25rem)
                 clamp(1.25rem, 5vw, 2rem)
                 calc(env(safe-area-inset-bottom, 0px) + 1.75rem);
        overflow-y: auto;
        overscroll-behavior: contain;
      }
      .nav-overlay-preview { display: none; }

      .nav-overlay__eyebrow {
        display: block;
        margin: 0 0 clamp(1.6rem, 4vh, 2.6rem);
        font-size: 0.7rem;
        font-weight: 500;
        letter-spacing: 0.22em;
        text-transform: uppercase;
        color: var(--muted);
      }

      .nav-overlay-list {
        align-content: start;
        gap: 0;
        counter-reset: navidx;
        border-top: 1px solid var(--hairline);
      }
      .nav-overlay-list li { border-bottom: 1px solid var(--hairline); }
      .nav-overlay-list li,
      .nav-overlay.is-open .nav-overlay-list li {
        transition: opacity 620ms cubic-bezier(0.16, 1, 0.3, 1),
                    transform 760ms cubic-bezier(0.16, 1, 0.3, 1);
      }
      .nav-overlay-list li { transform: translateY(26px); }
      .nav-overlay.is-open .nav-overlay-list li:nth-child(5) { transition-delay: 340ms; }
      .nav-overlay.is-open .nav-overlay-list li:nth-child(6) { transition-delay: 400ms; }

      .nav-overlay-list a {
        flex-direction: row;
        justify-content: flex-start;
        align-items: baseline;
        gap: clamp(0.9rem, 4vw, 1.5rem);
        padding: clamp(0.8rem, 2.2vh, 1.2rem) 0;
        min-height: 56px;
        font-size: clamp(1.8rem, 8.4vw, 2.9rem);
        font-weight: 500;
        line-height: 1.05;
        letter-spacing: -0.03em;
        text-transform: none;
      }
      /* Numbered off a counter rather than markup, so all 96 pages pick it up
         and a page with a fifth item numbers itself. */
      .nav-overlay-list a::before {
        counter-increment: navidx;
        content: counter(navidx, decimal-leading-zero);
        flex: 0 0 auto;
        font-size: 0.68rem;
        font-weight: 500;
        letter-spacing: 0.2em;
        font-variant-numeric: tabular-nums;
        color: var(--accent);
      }
      .nav-overlay-list a:active { color: var(--accent); }

      .nav-overlay__foot {
        display: grid;
        gap: 0.3rem;
        margin-top: clamp(2rem, 5vh, 3.2rem);
        padding-top: clamp(1.1rem, 2.6vh, 1.6rem);
        border-top: 1px solid var(--hairline);
        opacity: 0;
        transform: translateY(18px);
        transition: opacity 620ms cubic-bezier(0.16, 1, 0.3, 1) 380ms,
                    transform 760ms cubic-bezier(0.16, 1, 0.3, 1) 380ms;
      }
      .nav-overlay.is-open .nav-overlay__foot { opacity: 1; transform: none; }
      .nav-overlay__foot a {
        display: inline-block;
        justify-self: start;
        min-height: 40px;
        padding: 0.5rem 0;
        font-size: 0.92rem;
        color: var(--ink);
        text-decoration: none;
      }
      .nav-overlay__foot span {
        font-size: 0.7rem;
        letter-spacing: 0.2em;
        text-transform: uppercase;
        color: var(--muted);
        padding-top: 0.35rem;
      }

      .nav-overlay-close {
        top: calc(env(safe-area-inset-top, 0px) + 1.05rem);
        right: clamp(1.25rem, 5vw, 2rem);
        width: 48px;
        height: 48px;
        font-size: 1.35rem;
      }
    }
    @media (max-width: 899px) and (prefers-reduced-motion: reduce) {
      .nav-overlay { clip-path: none; }
    }

    /* ─────────────────────────────────────────────────────────────────
       3 · PROJECT ZOOM TRANSITION
       Opening a project is a push into the card rather than a page swap.
       Only transform, opacity and clip-path are animated, all on their own
       layer, so it stays on the compositor at every breakpoint.
       ───────────────────────────────────────────────────────────────── */
    .pzoom {
      /* Left/top rather than inset, because the script writes width and height
         in pixels: a full-viewport fixed box would follow the visual viewport
         if the address bar moved during the push, and the picture would resize
         under the visitor. Frozen at the size the push was measured against. */
      position: fixed; left: 0; top: 0; z-index: 99999;
      /* The overlay is a child of <html>, not <body>, so it never inherited the
         body font and the zoom title rendered in the UA serif until the
         navigation completed and the real hero took over. */
      font-family: 'Satoshi', sans-serif;
      pointer-events: none;
      contain: layout paint;
    }
    .pzoom__veil { position: absolute; inset: 0; background: var(--ink, #1a1a1a); opacity: 0; }
    .pzoom__clip { position: absolute; inset: 0; will-change: clip-path; }
    .pzoom__img {
      position: absolute; left: 0; top: 0;
      transform-origin: 50% 50%;
      will-change: transform;
      max-width: none;
    }
    .pzoom__img--hi { opacity: 0; transition: opacity 220ms linear; }
    .pzoom__img--hi.is-on { opacity: 1; }
    /* Matches .detail-hero__scrim so the last frame and the page it lands on
       are the same picture. */
    .pzoom__scrim {
      position: absolute; inset: 0; opacity: 0;
      background: linear-gradient(180deg, rgba(0,0,0,0.32) 0%, transparent 30%, transparent 55%, rgba(0,0,0,0.55) 100%);
    }
    /* Geometry copied from .detail-hero__titlewrap, so the title the zoom
       ends on is already where the project page draws it. */
    .pzoom__title {
      position: absolute;
      inset: auto 0 clamp(2rem, 6vh, 5rem) 0;
      padding: 0 clamp(1.5rem, 4vw, 2.5rem);
      max-width: 1400px; margin: 0 auto;
      color: var(--bg, #f5f3ed);
      opacity: 0;
    }
    .pzoom__eyebrow {
      margin: 0 0 clamp(0.8rem, 1.5vh, 1.4rem);
      font-size: 0.78rem; font-weight: 500;
      letter-spacing: 0.22em; text-transform: uppercase;
      color: rgba(255,255,255,0.82);
      font-variant-numeric: tabular-nums;
    }
    .pzoom__name {
      margin: 0;
      font-size: clamp(3.2rem, 9.5vw, 9.5rem);
      font-weight: 500; line-height: 0.92; letter-spacing: -0.04em;
      max-width: 22ch;
    }
    @media (max-width: 899px) { .pzoom__name { font-size: clamp(2.6rem, 15vw, 5.3rem); } }
    /* clip, not hidden: hidden would turn <html> into a scroll container and
       reset the scroll position, which with smooth scroll in play jumps the
       page on the first frame of the push. clip does neither.

       One axis, and that restriction is the whole point. The dolly scales the
       body 6% about its centre, which overflows on all four sides, but only
       the right-hand overflow does damage: it opens a horizontal scrollbar and
       relaid the page out under the first frame of the push. Clipping both
       axes fixed that and caused two worse things. On a phone the address bar
       is collapsed whenever you have scrolled down and returns the instant the
       document stops being scrollable, so it slid back mid-push, the visual
       viewport lost about 56px, and every 100dvh block behind the overlay
       jumped. On desktop the vertical scrollbar vanished for the length of the
       push and took 15px of page width with it. Clipping x alone leaves the
       document scrollable, so neither happens. */
    html.is-zooming { overflow-x: clip; }
    /* Arriving from a zoom: the curtain fade would blink the picture the zoom
       just landed on. The hero's own 1.04 to 1 settle carries the arrival. */
    /* ───── Imagery parallax ─────────────────────────────────────────────
       Every framed photograph drifts against the scroll. The 6% scale is not
       decoration: it is the overscan the drift spends, 3% above and 3% below a
       travel of ±2.5%, so no drift can ever expose the frame behind it. The
       half percent held back is the sub-pixel margin: measured, the tightest
       frame on the site keeps 2px of it at the extreme of its travel.
       --py is written per frame from JS. The scale stays declarative in --ps so
       the hover states can still transition it, which a transition on transform
       itself could not do without smearing the drift into a lag. The var()
       fallbacks keep both working where @property is not supported; only the
       hover easing needs the registration. */
    @property --py { syntax: '<length>'; inherits: false; initial-value: 0px; }
    @property --ps { syntax: '<number>'; inherits: false; initial-value: 1; }
    .detail-hero__img img, .walk-img img, .next__media img,
    .work-tile__img img, .statement__media img, .partner__portrait img,
    .person__img img, .leader-portrait img, .sectorlead__frame img {
      /* Declared, not left to the fallback: registering --ps gives it an
         initial value of 1, which a var() fallback never gets to override. */
      --ps: 1.06;
      transform: translate(0, var(--py, 0px)) scale(var(--ps, 1.06));
    }
    /* The hero opens 4% wider and settles onto the parallax rest state, so a
       zoom in from the work grid still hands over onto an exact match. */
    .detail-hero__img img { --ps: 1.10; }
    .detail-hero.is-loaded .detail-hero__img img { --ps: 1.06; }
    @media (prefers-reduced-motion: reduce) {
      .detail-hero__img img, .walk-img img, .next__media img,
    .work-tile__img img, .statement__media img, .partner__portrait img,
    .person__img img, .leader-portrait img, .sectorlead__frame img {
        --ps: 1 !important;
        --py: 0px !important;
      }
    }
    html.is-zoom-landing body { opacity: 1 !important; transition: none !important; }

    /* ─────────────────────────────────────────────────────────────────
       4 · MOBILE NAV MARK ALIGNMENT
       The mark sat well right of the gutter every other element on the page
       lines up against, and only on phones, where the nav has no centring
       pill to disguise it. Two separate causes, and the per-page fix only
       ever addressed the first:

       1. The nav carried its own outer padding on top of the pill's, so the
          row started further in than .container does. Both are zeroed here
          and the pill takes the page's own gutter instead.
       2. The logo PNG is 1400px of canvas holding 730px of drawing: 24.8% of
          its width on the left edge is transparent. A percentage translate is
          measured against the image's own box, so it stays exact at every
          rendered logo height instead of being pinned to one breakpoint's
          pixel value, and being a transform it moves nothing else in the row.

       Pages whose container gutter is not 1.5rem set --nav-gutter on .nav.
       ───────────────────────────────────────────────────────────────── */
    @media (max-width: 768px) {
      /* The .is-scrolled variants are carried too. Every page but the home page
         ships the header with is-scrolled already on it, and those rules are one
         class more specific than the plain ones, so the per-page alignment fixes
         were being overridden on exactly the pages that needed them. */
      .nav,
      .nav.is-scrolled { max-width: 100%; padding-left: 0; padding-right: 0; }
      .nav .nav-pill,
      .nav.is-scrolled .nav-pill {
        margin-left: 0; margin-right: 0;
        padding-left: var(--nav-gutter, 1.5rem);
        padding-right: var(--nav-gutter, 1.5rem);
      }
      .nav .nav-mark,
      .nav.is-scrolled .nav-mark { margin-left: 0; }
      .nav-mark img { transform: translateX(-24.8%); }
    }

    /* ──────────────────────────────────────────────────────────────
       5. Reveal mask teardown  (2026-08-19)

       [data-reveal] animates a gradient mask into place. A mask clips the
       element to its border box, so any ink outside that box disappears, and
       with headings set at a line-height below 1 the tail of a g, p, y or j
       sits outside it. Every such heading on the site was being cut off flat
       along the baseline. The mask has no job once the sweep has run, so the
       observer on each page adds .reveal-done when it is over and the mask is
       removed. Applies to all 96 pages.
       ────────────────────────────────────────────────────────────── */
    [data-reveal].reveal-done {
      -webkit-mask-image: none !important;
              mask-image: none !important;
      /* will-change is deliberately NOT released here. Dropping the hint once
         the mask is gone leaves Chrome holding a composited layer it never
         repaints, so the element goes blank until something else forces a
         paint, which is why the home page stats only appeared on hover.
         Deferring the release by two frames does not help. The idle GPU
         layers are the cheaper of the two costs. */
    }

/* ──────────────────────────────────────────────────────────────
   6. AVIF <picture> wrapper transparency (2026-08-19)
   A <picture> element is an inline box by default. Wrapping an <img>
   in <picture> without this rule would make width:100% / height:100%
   on the <img> resolve against the new inline wrapper box instead of
   the layout box the <img> used to sit in directly, collapsing or
   overflowing every image on the site. display:contents removes the
   <picture> wrapper from layout entirely so nothing else changes.
   ────────────────────────────────────────────────────────────── */
picture { display: contents; }

/* ─────────────────────────────────────────────────────────────────────────
   BUTTON HOVER — session 18
   The fill no longer switches. A circle grows out of the left edge of the
   button until it covers it, and the label rolls its second copy up into
   place at the same time. One definition for every pill on the site, loaded
   after each page's own sheet so the old instant swaps are overridden in
   place rather than hunted down in ninety-six files.
   The circle is a negative-z pseudo-element, which paints above the button's
   own background but below its text and dot, so nothing needs restacking.
   ───────────────────────────────────────────────────────────────────────── */
.nav-cta,
.foot-cta,
.out-cta__link,
.pscar__btn {
  position: relative;
  overflow: hidden;
  isolation: isolate;
}
.nav-cta::before, .foot-cta::before, .out-cta__link::before, .pscar__btn::before {
  content: ''; position: absolute; left: 0; top: 50%;
  /* The fill starts at the left edge and sweeps across, rather than blooming
     out of the middle. Centred on that edge, the circle has to be wider than
     two button widths before its far side clears the far corners. A square
     button is the tight case: from the middle of its left edge the far
     corners sit 1.12 button widths away, so the radius has to beat that.
     260% gives a radius of 1.3 widths, which clears every pill and the
     round carousel arrows alike. 230% left the arrows' corners bare. */
  width: 260%; aspect-ratio: 1 / 1; border-radius: 50%;
  background: var(--accent); z-index: -1;
  transform: translate(-50%, -50%) scale(0);
  transition: transform 900ms cubic-bezier(0.33, 0.02, 0.15, 1);
  pointer-events: none;
}

/* The rolling label. Two copies of equal height stacked in one strip, moved by
   exactly half its height, so the second lands where the first sat. The old
   version parked the copy at top:100% and moved it by a percentage of a line
   box, which rounded a pixel off and shaved the tops of the capitals. */
.btn-roll {
  --roll-h: 1.35em;
  /* The two copies sit a clear gap apart rather than back to back, so the
     outgoing word has left before the incoming one arrives instead of the
     two appearing hinged together. The strip still shows only --roll-h, so
     the button does not change size. */
  --roll-gap: 0.9em;
  display: block; overflow: hidden; height: var(--roll-h);
}
.btn-roll__i { display: block; transition: transform 780ms cubic-bezier(0.62, 0.02, 0.16, 1); }
.btn-roll__t { display: block; white-space: nowrap; height: var(--roll-h); line-height: var(--roll-h); }
.btn-roll__t--b { margin-top: var(--roll-gap); }

@media (hover: hover) and (pointer: fine) {
  .nav-cta:hover,
  .foot-cta:hover {
    background-color: transparent;
    transform: none;
    box-shadow: none;
    color: #FFFFFF;
    /* The border has to move with the fill. Left at ink, the scrolled nav's
       pill kept a hard dark ring around the new colour. */
    border-color: var(--accent);
  }
  /* The scrolled nav paints its own background and drop shadow at three
     classes, unconditionally, so a two-class hover rule cannot clear them and
     the old shadow sat under the new fill. Matched here at four. */
  .nav.is-scrolled .nav-cta:hover {
    background-color: transparent;
    box-shadow: none;
    border-color: var(--accent);
  }
  .out-cta__link:hover,
  .out-cta__link--ghost:hover {
    background: transparent;
    transform: none;
    color: #FFFFFF;
    border-color: var(--accent);
  }
  .pscar__btn:not([disabled]):hover {
    background: transparent;
    color: #FFFFFF;
    border-color: var(--accent);
  }
  /* The white arrow is a second, fixed image faded in over the ink one, not
     a recolour of it. Chrome would not repaint a stroke, colour or filter
     change on the SVG inside this button, whose fill comes from a scaled
     pseudo-element, and the arrow kept sitting in ink on the new purple.
     Opacity is composited, so this always lands. */
  .pscar__btn:not([disabled]):hover::after { opacity: 1; }
  .pscar__btn:not([disabled]):hover svg { opacity: 0; }
  .nav-cta:hover::before,
  .foot-cta:hover::before,
  .out-cta__link:hover::before,
  .pscar__btn:not([disabled]):hover::before {
    transform: translate(-50%, -50%) scale(1);
  }
  .nav-cta:hover .dot,
  .foot-cta:hover .dot { background: #FFFFFF; }
  .nav-cta:hover .btn-roll__i,
  .foot-cta:hover .btn-roll__i,
  .out-cta__link:hover .btn-roll__i { transform: translateY(calc(-1 * (var(--roll-h) + var(--roll-gap)))); }
}

/* Keyboard gets the same thing a pointer gets. */
.nav-cta:focus-visible::before,
.foot-cta:focus-visible::before,
.out-cta__link:focus-visible::before,
.pscar__btn:not([disabled]):focus-visible::before { transform: translate(-50%, -50%) scale(1); }
.nav-cta:focus-visible,
.foot-cta:focus-visible,
.out-cta__link:focus-visible { color: #FFFFFF; border-color: var(--accent); }
.nav-cta:focus-visible .dot,
.foot-cta:focus-visible .dot { background: #FFFFFF; }
.nav.is-scrolled .nav-cta:focus-visible { background-color: transparent; box-shadow: none; }
.nav-cta:focus-visible .btn-roll__i,
.foot-cta:focus-visible .btn-roll__i,
.out-cta__link:focus-visible .btn-roll__i { transform: translateY(calc(-1 * (var(--roll-h) + var(--roll-gap)))); }

/* The press feedback is defined earlier in each page's own sheet, where the
   hover rule above would now outrank it. */
.nav-cta:active,
.foot-cta:active { transform: scale(0.97); }

/* The custom cursor stands down over an accent-filled button. See the note in
   shared/site-buttons.js: an accent dot on an accent fill is a hole in the
   label. The real pointer comes back so the button still reads as clickable. */
.cursor.is-onfill { opacity: 0; }
.nav-cta,
.foot-cta,
.out-cta__link,
.pscar__btn { cursor: pointer; }
/* The arrow's colour is CSS from end to end. It used to carry
   stroke="currentColor" as a presentation attribute, and Chrome would not
   repaint that against the new fill, so the arrow stayed in ink on purple. */
.pscar__btn svg { stroke: var(--ink); transition: opacity 420ms cubic-bezier(0.33, 0.02, 0.15, 1); }
/* The white twin of each arrow, parked at zero and faded in on hover. */
.pscar__btn::after {
  content: '';
  position: absolute;
  left: 50%; top: 50%;
  width: 16px; height: 10px;
  transform: translate(-50%, -50%);
  background-repeat: no-repeat;
  background-position: center;
  background-size: 16px 10px;
  opacity: 0;
  transition: opacity 420ms cubic-bezier(0.33, 0.02, 0.15, 1);
  pointer-events: none;
}
.pscar__btn[data-pscar-prev]::after { background-image: url("data:image/svg+xml,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 10' fill='none' stroke='%23FFFFFF' stroke-width='1.3' stroke-linecap='round' stroke-linejoin='round'><path d='M15 5H3M6.5 1.5 3 5l3.5 3.5'/></svg>"); }
.pscar__btn[data-pscar-next]::after { background-image: url("data:image/svg+xml,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 10' fill='none' stroke='%23FFFFFF' stroke-width='1.3' stroke-linecap='round' stroke-linejoin='round'><path d='M1 5h12M9.5 1.5 13 5l-3.5 3.5'/></svg>"); }

/* ─────────────────────────────────────────────────────────────────────────
   TAP TARGETS
   The phone numbers in the footer's geography column are set small and came
   out 19px tall, under the size a thumb can reliably hit. An invisible pad
   around each one carries the touch without moving the number or the rule
   under it by a pixel.
   ───────────────────────────────────────────────────────────────────────── */
.foot-geo__tel { position: relative; z-index: 1; }  /* the block below it
   sits later in flow and was winning the hit test under the number. */
.foot-geo__tel::after {
  content: ''; position: absolute; left: 0; right: 0; top: -5px; bottom: -5px;
}

/* ─────────────────────────────────────────────────────────────────────────
   THE DOT IN THE NAV PILL
   Every page set the dot to a fixed colour, which held until the scrolled
   nav painted its own ink background underneath: an ink dot on an ink pill
   is invisible, and the label read as pushed to the right by a gap with
   nothing in it. Following the label's own colour instead keeps it visible
   in all three states, on the hero, on the scrolled nav, and on the fill.
   ───────────────────────────────────────────────────────────────────────── */
.nav-cta .dot { background: currentColor; }

/* ─────────────────────────────────────────────────────────────────────────
   BUTTON HOVER, SECOND PASS — session 21
   The fill and the roll were right. What was still wrong was everything
   travelling on its own clock around them.

   The circle takes 900ms to cross the button. Measured against its own curve
   it reaches the middle at 234ms and clears the far corner at 414ms. The
   label was turning white in 250ms, so for a quarter of a second the right
   half of a word sat white on cream with no fill under it yet, and on the
   scrolled nav the pill's ink backing faded out from under the same half.
   Both now wait for the fill to arrive rather than announcing it.

   Leaving is quicker than arriving. A hover that takes as long to undo as it
   took to do reads as sticky: the pointer has moved on and the button is
   still finishing a sentence. Roughly half, on a curve that lets go at once.
   ───────────────────────────────────────────────────────────────────────── */
.nav-cta, .foot-cta, .out-cta__link {
  transition: color 180ms cubic-bezier(0.4, 0, 0.2, 1),
              border-color 280ms cubic-bezier(0.4, 0, 0.2, 1),
              background-color 180ms cubic-bezier(0.4, 0, 0.2, 1),
              box-shadow 280ms cubic-bezier(0.4, 0, 0.2, 1),
              transform 250ms cubic-bezier(0.25, 1, 0.5, 1);
}
.nav-cta::before, .foot-cta::before, .out-cta__link::before, .pscar__btn::before {
  transition: transform 480ms cubic-bezier(0.4, 0, 0.2, 1);
}
.btn-roll__i { transition: transform 460ms cubic-bezier(0.4, 0, 0.2, 1); }

@media (hover: hover) and (pointer: fine) {
  .nav-cta:hover, .foot-cta:hover, .out-cta__link:hover,
  .nav.is-scrolled .nav-cta:hover {
    transition: color 200ms cubic-bezier(0.33, 0.02, 0.15, 1) 240ms,
                border-color 420ms cubic-bezier(0.33, 0.02, 0.15, 1),
                /* The ink pill behind the label is not faded out, it is
                   dropped once the fill has covered it. Fading it left a pale
                   band on the side the circle had not reached yet. */
                background-color 0s linear 860ms,
                box-shadow 300ms cubic-bezier(0.33, 0.02, 0.15, 1),
                transform 250ms cubic-bezier(0.25, 1, 0.5, 1);
  }
  .nav-cta:hover .dot, .foot-cta:hover .dot {
    transition: background 200ms cubic-bezier(0.33, 0.02, 0.15, 1) 240ms;
  }
  .nav-cta:hover::before, .foot-cta:hover::before,
  .out-cta__link:hover::before, .pscar__btn:not([disabled]):hover::before {
    transition: transform 900ms cubic-bezier(0.33, 0.02, 0.15, 1);
  }
  .nav-cta:hover .btn-roll__i, .foot-cta:hover .btn-roll__i,
  .out-cta__link:hover .btn-roll__i {
    transition: transform 780ms cubic-bezier(0.62, 0.02, 0.16, 1);
  }
  /* The round carousel arrow is a fifth of the width, so the fill reaches its
     centre at 234ms rather than crossing the whole label. The white arrow is
     brought up to meet it there. */
  .pscar__btn:not([disabled]):hover::after { transition: opacity 200ms linear 200ms; }
  .pscar__btn:not([disabled]):hover        { transition: color 200ms linear 200ms,
                                                         border-color 420ms cubic-bezier(0.33, 0.02, 0.15, 1),
                                                         background 0s linear 860ms; }
}
/* Keyboard focus is not a pointer sweeping across the button, so it gets the
   arriving timings without the delay: the label is white the moment the ring
   appears rather than a beat later. */
.nav-cta:focus-visible, .foot-cta:focus-visible, .out-cta__link:focus-visible {
  transition: color 200ms cubic-bezier(0.33, 0.02, 0.15, 1),
              border-color 420ms cubic-bezier(0.33, 0.02, 0.15, 1),
              background-color 0s linear 860ms;
}
.nav-cta:focus-visible::before, .foot-cta:focus-visible::before,
.out-cta__link:focus-visible::before, .pscar__btn:not([disabled]):focus-visible::before {
  transition: transform 900ms cubic-bezier(0.33, 0.02, 0.15, 1);
}
.nav-cta:focus-visible .btn-roll__i, .foot-cta:focus-visible .btn-roll__i,
.out-cta__link:focus-visible .btn-roll__i {
  transition: transform 780ms cubic-bezier(0.62, 0.02, 0.16, 1);
}
@media (prefers-reduced-motion: reduce) {
  .nav-cta, .foot-cta, .out-cta__link, .pscar__btn,
  .nav-cta::before, .foot-cta::before, .out-cta__link::before, .pscar__btn::before,
  .btn-roll__i { transition: none !important; }
}

/* ─────────────────────────────────────────────────────────────────
   4 · LATE OVERRIDES — 22 Aug 2026
   This sheet loads last on all 96 pages, so shared changes that would
   otherwise mean editing five inline <style> blocks plus proj.css live
   here. Nav sizing sits here for the same reason.
   ───────────────────────────────────────────────────────────────── */

/* 4.1 · Nav — larger mark and larger link text.
   The pill was reading small against the hero wordmark. */
.nav-mark img            { height: 62px; }
.nav.is-scrolled .nav-mark img { height: 54px; }
.nav-link                { font-size: 0.92rem; letter-spacing: 0.13em; }
.nav-links               { gap: 1.7rem; }
@media (max-width: 1100px) {
  .nav-link              { font-size: 0.86rem; letter-spacing: 0.11em; }
  .nav-links             { gap: 1.25rem; }
}
@media (max-width: 899px) {
  .nav-mark img          { height: 42px; }
  .nav.is-scrolled .nav-mark img { height: 40px; }
}

/* 4.2 · UAE holds two offices. Side by side rather than stacked, so the
   region reads as one place with two addresses. Collapses on phones. */
.foot-geo__offices--pair {
  display: grid;
  grid-template-columns: repeat(2, minmax(0, 1fr));
  gap: clamp(0.9rem, 1.8vw, 1.6rem);
  align-items: start;
}
/* Side by side the two offices are siblings, so the stacked-office top margin
   pushed Dubai a row below Abu Dhabi. It belongs to the stacked case only. */
.foot-geo__offices--pair > .foot-geo__office + .foot-geo__office { margin-top: 0; }
/* Splitting one of three equal thirds in half left each UAE office about
   190px, and its longest line, "Bakhit Business Center, Arzoo Building,",
   wants 226px. Both addresses came out three and four ragged lines while
   India beside them sat on two. (Singapore was removed on the client's list of 26 Sep 2026, which leaves two regions.) UAE therefore takes a wider
   share of the row. The two UAE addresses also lost the hard line breaks that
   were written for a full-width column, so they now wrap to whatever width the
   pair actually has instead of breaking once and then wrapping again. */
.foot-geo__grid { grid-template-columns: 1fr 1.34fr; }
/* Below 900px the row is already two columns and then one, so the pair has no
   width to give away and the two offices stack, which is the same thing the
   region did before it was paired. */
@media (max-width: 900px) {
  .foot-geo__grid { grid-template-columns: repeat(2, minmax(0, 1fr)); }
  .foot-geo__offices--pair { grid-template-columns: 1fr; }
  .foot-geo__offices--pair > .foot-geo__office + .foot-geo__office { margin-top: 1.1rem; }
}

/* 4.3 · Socials as marks, not words. One row, hairline-weight glyphs
   that inherit the foot-link colour and its hover. */
.foot-soc-row {
  display: flex;
  align-items: center;
  gap: 0.85rem;
  margin-top: 0.35rem;
}
.foot-link.foot-soc {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 34px;
  height: 34px;
  padding: 0;
  border-radius: 50%;
  opacity: 0.62;
  transition: opacity 300ms var(--ease-out-quart, cubic-bezier(0.25, 1, 0.5, 1)),
              transform 300ms var(--ease-out-quart, cubic-bezier(0.25, 1, 0.5, 1)),
              color 300ms ease;
}
.foot-link.foot-soc::after { display: none; }   /* kill the inherited underline */
.foot-soc__ico { width: 21px; height: 21px; display: block; }
.foot-link.foot-soc:hover,
.foot-link.foot-soc:focus-visible { opacity: 1; transform: translateY(-2px); }
@media (prefers-reduced-motion: reduce) {
  .foot-link.foot-soc:hover { transform: none; }
}

/* 4.4 · Ghost wordmark on phones.
   "RAYAN ARCHITECTS" broke to two centred lines below ~760px, which read
   as a headline rather than a watermark. The navbar mark holds one line
   at any width, so it carries the ghost instead. */
.foot-megamark__logo { display: none; }
@media (max-width: 760px) {
  .foot-megamark span      { display: none; }
  .foot-megamark {
    padding: 0;
    margin-bottom: clamp(1rem, 3vw, 1.75rem);
  }
  /* The PNG carries wide transparent margins: its glyph occupies 52.1% of the
     file's width and 42.4% of its height (measured off the alpha channel). To
     read as a full-width ghost the box has to be oversized and the empty
     bands pulled back in, or the mark sits marooned in the middle. */
  .foot-megamark__logo {
    display: block;
    width: 176.5%;
    max-width: none;
    height: auto;
    margin: -14% 0 -15% -39.75%;
    opacity: 0.1;
  }
}

/* 4.5 · The scrolled pill was capped at 720px, which the nav fitted into with
   one pixel to spare. The larger mark and larger link text above take it past
   that, and the first thing to give was the CTA: it shrank and clipped its own
   label to "GET IN TOUC". The cap goes up to cover the new measurements, and
   the CTA is taken out of the shrink pool so it can never lose its label
   again, whatever a future label change costs in width. */
.nav.is-scrolled .nav-pill { max-width: min(820px, calc(100vw - 4rem)); }
.nav-cta { flex: 0 0 auto; white-space: nowrap; }
.nav-mark { flex: 0 0 auto; }

/* 4.6 · The ghost wordmark was set at 12.4vw, and "RAYAN ARCHITECTS" in
   Satoshi at that tracking measures 8.34 times its own font size, measured in
   the browser rather than guessed. At 12.4vw that wants 1490px inside a 1392px
   band on a 1440 laptop, so the closing S was cut off against the band's own
   overflow. Any single vw coefficient only holds at one width, so the size is
   solved instead: divide the space actually available by that 8.34 ratio and
   the mark fills the band with the same 1rem of air at every width, from the
   760px breakpoint up to the 12rem cap. */
@media (min-width: 761px) {
  .foot-megamark span { font-size: min(calc((100vw - 4rem) / 8.34), 12rem); }
}

/* ── Client list, 26 Sep 2026 ───────────────────────────────────────────────
   The footer logo was reading small against the four columns under it; it is
   raised by roughly the golden ratio (50px to 80px), and steps down with the
   viewport so a phone footer is not all logo. */
.site-foot .foot-mark-logo img { height: clamp(58px, 5.6vw, 80px); }

/* The admin number carries a fallback line: RA asked that an unanswered call
   go to a second number. The phone line itself has to forward the call; the
   site can only say where to ring instead. */
.foot-note {
  margin: 0.35rem 0 0;
  font-size: 0.8rem;
  line-height: 1.5;
  color: var(--muted);
}
.foot-note__tel { color: inherit; text-decoration: none; border-bottom: 1px solid currentColor; }
.foot-note__tel:hover { color: var(--ink); }
.foot-note--inline { display: inline; margin: 0 0 0 0.6rem; font-size: 0.72rem; letter-spacing: 0.16em; text-transform: uppercase; }
