/* =============================================================================
   labs.llc — institutional rebuild
   -----------------------------------------------------------------------------
   The previous build was a dark neon console: forty-odd accent colours, glass,
   halos, a live control panel. This one is its opposite and deliberately so.
   The register here is a financial firm's: white paper, one ink, a great deal
   of air, and type doing the work that colour used to do.

   The whole system is five decisions:

     ink      #1E262B   near-black with the blue left in, never pure #000
     paper    #FFFFFF
     deep     #11161C   the dark band, one step colder than the ink
     line     a 10% ink hairline; there are no heavy borders anywhere
     measure  1265px

   Type is DM Sans at four weights. Display sizes run LIGHT (200-300) with
   tight negative tracking, which is what makes a large heading read as calm
   rather than loud; small text runs 500-700 and positive-tracked. That
   inversion -- big text light, small text heavy -- is the single most load-
   bearing rule in the sheet.
   ========================================================================== */

/* ------------------------------------------------------------------ webfont
   DM Sans, self-hosted. It used to come from fonts.googleapis.com, which cost
   a render-blocking stylesheet on a third-party origin, two preconnects, and
   a DNS+TLS round trip before a single glyph could be requested — in a build
   that otherwise has no CDN in it at all.

   These are the same two files Google serves, and they are variable across
   both axes the sheet uses (opsz 9-40, wght 200-700), so the whole four-weight
   inversion the design rests on is 94 KB of woff2 rather than twelve separate
   faces. `font-weight: 200 700` is the range declaration a variable face needs;
   `font-optical-sizing: auto` is the default and is what drives opsz.

   Split on the same unicode ranges Google splits on, so a page of English
   never downloads the accented block. Both are preloaded from the head — a
   font discovered only when the CSS parser reaches this rule is a font
   requested one round trip late, which is exactly the flash of fallback text
   the two ResizeObserver re-measures in site.js exist to clean up after. */
@font-face {
    font-family: 'DM Sans';
    font-style: normal;
    font-weight: 200 700;
    font-display: swap;
    src: url('../fonts/dm-sans-latin.woff2') format('woff2');
    unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA,
                   U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122,
                   U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}
@font-face {
    font-family: 'DM Sans';
    font-style: normal;
    font-weight: 200 700;
    font-display: swap;
    src: url('../fonts/dm-sans-latin-ext.woff2') format('woff2');
    unicode-range: U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF,
                   U+0304, U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF,
                   U+2020, U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F, U+A720-A7FF;
}

:root {
    --ink:        #1E262B;
    --ink-77:     rgba(30, 38, 43, .77);
    /* The secondary ink. It was .55, which measures 3.6:1 on the paper — under
       the 4.5:1 body-text floor, and this token carries most of the small type
       on the site: every eyebrow, every summary line, the tier descriptions,
       the concentration counts and the register's own asset blurbs. At .64 it
       measures 4.7:1 and is still plainly the quieter of the two inks. The
       name is kept: it is referenced in seven files and the number in it was
       never the point. */
    --ink-55:     rgba(30, 38, 43, .64);
    --paper:      #ffffff;
    --deep:       #11161C;
    --deep-soft:  #171E26;
    --line:       rgba(30, 38, 43, .12);
    --line-soft:  rgba(30, 38, 43, .07);
    --line-dark:  rgba(255, 255, 255, .13);
    /* The domain line under a register row. #878787 measured 3.44:1 on the
       paper — the same shortfall --ink-55 had, in a different grey. */
    --muted:      #717171;

    /* One restrained accent, used for rules and live indicators only -- never
       for large fills. The old build's orange survives here as a hairline. */
    --accent:     #C2410C;

    --measure:    1265px;
    --gutter:     clamp(20px, 5vw, 56px);

    /* The height of the sticky header, published as a token because two other
       things have to know it: the header itself, and every anchor target on the
       page. A heading scrolled to the top of the viewport lands underneath a
       sticky bar unless something subtracts the bar first, and the value that
       does the subtracting has to be the same one the bar is drawn at. The
       satellite pages override it — their header carries a second row. */
    --nav-h:      74px;

    /* A section's vertical rhythm. Everything is a multiple of this so bands
       stack without anyone having to think about it. */
    --band:       clamp(64px, 9vw, 128px);

    --ease:       cubic-bezier(.22, .61, .36, 1);
}

*, *::before, *::after { box-sizing: border-box; }

html { -webkit-text-size-adjust: 100%; scroll-behavior: smooth; }
@media (prefers-reduced-motion: reduce) { html { scroll-behavior: auto; } }

/* Every in-page link on this site points at a section, and the header that sits
   above it never scrolls away. Without this the band a visitor asked for
   arrives with its eyebrow and the first line of its heading behind the bar —
   which on a phone, where the header is a larger share of the screen, is most
   of what they came for. Applied to anything with an id rather than to a
   hand-kept list of sections, because a scroll margin costs nothing on an
   element nobody ever scrolls to, and a list is a thing to forget to update. */
[id] { scroll-margin-top: calc(var(--nav-h) + 14px); }

body {
    margin: 0;
    background: var(--paper);
    color: var(--ink-77);
    font-family: 'DM Sans', -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif;
    font-size: 17px;
    line-height: 1.62;
    font-weight: 400;
    -webkit-font-smoothing: antialiased;
    -moz-osx-font-smoothing: grayscale;
}

h1, h2, h3, h4 { color: var(--ink); margin: 0; font-weight: 600; }

/* Display sizes. Light weight, tight tracking, short leading -- the three
   together are the house voice. */
.d1 {
    font-size: clamp(2.1rem, 5.2vw, 3.9rem);
    font-weight: 200; letter-spacing: -.035em; line-height: 1.09;
}
.d2 {
    font-size: clamp(1.75rem, 3.5vw, 2.77rem);
    font-weight: 600; letter-spacing: -.04em; line-height: 1.06;
}
.d3 {
    font-size: clamp(1.25rem, 2vw, 1.6rem);
    font-weight: 300; letter-spacing: -.028em; line-height: 1.22;
}

/* The eyebrow. Every band opens with one; it is the only place small-caps
   tracking appears, so it reads as a system label rather than as text. */
.eyebrow {
    display: flex; align-items: center; gap: .7rem;
    font-size: .688rem; font-weight: 700;
    letter-spacing: .19em; text-transform: uppercase;
    color: var(--ink-55);
    margin: 0 0 1.6rem;
}
.eyebrow::before {
    content: ''; width: 26px; height: 1px; background: var(--accent); flex: 0 0 auto;
}
.eyebrow.on-deep { color: rgba(255,255,255,.62); }

.lede {
    font-size: clamp(1.02rem, 1.35vw, 1.19rem);
    line-height: 1.6; font-weight: 300; color: var(--ink-77);
    max-width: 62ch;
}

a { color: inherit; }

.wrap { max-width: var(--measure); margin: 0 auto; padding-inline: var(--gutter); }
.band { padding-block: var(--band); }
/* ---------------------------------------------------------------------------
   THE DEEP BAND

   The ink tokens are re-declared here rather than a list of `.band--deep .x`
   overrides being kept downstream. Everything on this sheet paints its
   secondary text with var(--ink-55) and its hairlines with var(--line), and
   every one of those was near-black on a near-black ground the moment the
   component was used inside a deep band — the valuation band on the data room
   and the basis band on the register were both effectively unreadable, and
   four of the satellite pages carried a local `.band--deep .lede` patch that
   only ever covered the one element each of them happened to use.

   Re-pointing the tokens fixes the components that exist and the ones written
   next. --ink stays a *fill* colour in a handful of places (the filled button,
   the pressed filter), so those are restated below rather than inheriting a
   white block with white type in it.
   ------------------------------------------------------------------------ */
.band--deep {
    --ink:       #FFFFFF;
    --ink-77:    rgba(255, 255, 255, .72);
    --ink-55:    rgba(255, 255, 255, .58);
    --line:      rgba(255, 255, 255, .16);
    --line-soft: rgba(255, 255, 255, .09);
    /* The accent hairline is a print red, set for white paper. On the deep
       ground it goes to the warm mark the console uses, which holds at the one
       pixel the eyebrow rule and the tier bars draw it at. */
    --accent:    #F97316;

    background: var(--deep); color: var(--ink-77);
}
.band--deep h1, .band--deep h2, .band--deep h3, .band--deep h4 { color: #fff; }

/* The three places --ink is a background rather than a text colour. */
.band--deep .btn { background: #fff; color: var(--deep); }
.band--deep .btn:hover { background: rgba(255,255,255,.86); }
.band--deep .btn--ghost { background: transparent; color: #fff; border-color: rgba(255,255,255,.38); }
.band--deep .btn--ghost:hover { background: #fff; color: var(--deep); border-color: #fff; }
.band--deep .filter[aria-pressed="true"] { background: #fff; border-color: #fff; color: var(--deep); }
.band--deep .practice:hover .practice__i { background: #fff; color: var(--deep); border-color: #fff; }

.band--tint { background: #FAFAFA; }
.rule { height: 1px; background: var(--line); border: 0; margin: 0; }

/* =============================================================================
   NAV
   ========================================================================== */

.nav {
    position: sticky; top: 0; z-index: 60;
    background: rgba(255,255,255,.86);
    backdrop-filter: saturate(180%) blur(14px);
    -webkit-backdrop-filter: saturate(180%) blur(14px);
    border-bottom: 1px solid var(--line-soft);
}
.nav__in {
    max-width: var(--measure); margin: 0 auto; padding: 0 var(--gutter);
    /* 1rem, not 1.5: this gap sits between CONTROLS — the magnifier, the
       crosshair, the edition pill — and a section-sized rule between them
       spent forty-odd pixels of bar on air and pushed the links to two
       lines. */
    height: var(--nav-h); display: flex; align-items: center; gap: 1rem;
}
.nav__mark { display: flex; align-items: center; gap: .8rem; text-decoration: none; margin-right: auto; }
.nav__mark img { display: block; width: 78px; height: 33px; }
.nav__mark span {
    font-size: .6rem; font-weight: 700; letter-spacing: .32em;
    text-transform: uppercase; color: var(--ink-55);
    padding-left: .8rem; border-left: 1px solid var(--line);
}
@media (max-width: 900px) { .nav__mark span { display: none; } }

.nav__links { display: flex; align-items: center; gap: 1.9rem; }
@media (max-width: 1040px) { .nav__links { display: none; } }
.nav__links a {
    text-decoration: none; font-size: .875rem; font-weight: 500;
    color: var(--ink-77); letter-spacing: -.01em; position: relative; padding-block: .4rem;
}
.nav__links a::after {
    content: ''; position: absolute; left: 0; right: 0; bottom: 0;
    height: 1px; background: var(--ink); transform: scaleX(0); transform-origin: left;
    transition: transform .32s var(--ease);
}
.nav__links a:hover::after, .nav__links a[aria-current]::after { transform: scaleX(1); }
.nav__links a[aria-current] { color: var(--ink); }
/* The one nav item that is a different document rather than a section of this
   one. The arrow is a ::before on a trailing pseudo so it cannot be selected
   with the label, and it sits in the accent so the row still reads as five
   quiet anchors plus one destination. */
.nav__links a.nav__out { display: inline-flex; align-items: center; gap: .34rem; }
.nav__links a.nav__out::before {
    content: '↗'; font-size: .78em; line-height: 1; color: var(--accent);
    order: 1; transform: translateY(-.02em);
    transition: transform .32s var(--ease);
}
.nav__links a.nav__out:hover::before { transform: translate(.1em, -.12em); }

/* ---------------------------------------------------------------------------
   THE EDITION PILL

   Both editions of labs.llc run out of one root — this paper-white site and
   the dark homepage at /indexdark.html — and this is the switch between
   them. (The news desk has its own pair; see the router comment in
   index.html.) It
   replaces the old LIVE CONSOLE cross-link: same destination, but a segmented
   control states the relationship the link could only imply, which is that the
   two are the same firm in two registers rather than one site linking out to
   another.

   The moving part is a single thumb behind both segments, not a background on
   whichever segment is lit. That is what lets it slide: on hover it travels to
   the segment under the cursor and previews the switch before the click, and
   the two colour rules below travel with it so the label it lands on turns
   paper and the one it leaves turns back to ink.

   Everything is a hairline and a fill of the ink already in use; no second
   accent enters the header for this.
   ------------------------------------------------------------------------ */
.mode-pill {
    position: relative; flex: 0 0 auto;
    display: inline-flex; align-items: center;
    padding: 3px; border-radius: 999px;
    border: 1px solid var(--line);
    /* A shallow well, so the thumb reads as sitting in the track rather than
       floating on the navbar. Both values are ink at single digits of alpha —
       at anything heavier the pill starts competing with the Contact button. */
    background: linear-gradient(180deg, rgba(30,38,43,.045), rgba(30,38,43,.025));
    box-shadow: inset 0 1px 2px rgba(30,38,43,.055);
}
.mode-pill__thumb {
    position: absolute; z-index: 0;
    top: 3px; bottom: 3px; left: 3px; width: calc(50% - 3px);
    border-radius: 999px; background: var(--ink);
    box-shadow: 0 1px 2px rgba(30,38,43,.26), 0 6px 18px rgba(30,38,43,.14);
    transition: transform .42s var(--ease);
}
/* Which half is lit. The class is on the container and written into the
   markup: on any one page the answer never changes. */
.mode-pill--dark .mode-pill__thumb { transform: translateX(100%); }

/* Hover preview. :has is what makes the container answer for its children;
   where it is unsupported the pill simply does not preview, which costs
   nothing — the segments are still links and still say which is which. */
/* Hovering the unlit segment slides the thumb across and previews the switch.
   There are exactly two, so "the unlit one is hovered" already says which side
   the thumb belongs on and the rule needs no position selector — which matters
   now that the lit segment is a <span> and the unlit one an <a>: the old
   :first-of-type / :last-of-type pair counted types, and with one of each
   element in the track the same anchor answered to both. */
.mode-pill:has(.mode-pill__seg:not(.is-on):hover) .mode-pill__thumb { transform: translateX(100%); }
.mode-pill--dark:has(.mode-pill__seg:not(.is-on):hover) .mode-pill__thumb { transform: none; }

.mode-pill__seg {
    position: relative; z-index: 1;
    display: inline-flex; align-items: center; justify-content: center;
    /* Both segments hold the same width, because the thumb is half the track
       and an uneven split would leave it short of one end. Nothing here sets
       type: the segments are a glyph each, and their words live in aria-label
       and title. */
    min-width: 2.5rem; padding: .4rem .45rem; border-radius: 999px;
    text-decoration: none; color: var(--ink-55);
    transition: color .32s var(--ease);
}
/* The lit segment is a <span>, not a link — it is the page you are already on,
   and a link to here is a reload that costs a round trip and returns the same
   document. It keeps the class, so the track still holds two equal halves. */
.mode-pill__seg.is-on { color: var(--paper); cursor: default; }
/* The fallback where :has is missing and the thumb therefore cannot move: the
   unlit segment darkens in place. It must not go to paper — with no thumb
   underneath, paper on the track is a label that vanishes. */
.mode-pill__seg:not(.is-on):hover { color: var(--ink); }
/* While a segment is hovered the thumb is under *it*, so the lit colour has to
   follow the thumb rather than the class. Equal specificity, later wins. */
.mode-pill:has(.mode-pill__seg:hover) .mode-pill__seg.is-on { color: var(--ink-55); }
.mode-pill:has(.mode-pill__seg:hover) .mode-pill__seg:hover  { color: var(--paper); }
.mode-pill__seg:focus-visible { outline: 2px solid var(--accent); outline-offset: 3px; }
/* 13px, and the same 13 is written on the <svg> tag itself as width and height.
   That duplication is the point: an SVG with a viewBox and no dimensions of its
   own falls back to 300×150 when no stylesheet reaches it, which is how a sun
   and a moon the size of a fist ended up sitting in the middle of the hero. The
   attributes make the glyph 13px with no CSS at all; this rule keeps it 13px
   when the CSS does arrive and something else has set a width on svg. */
.mode-pill__i { width: 13px; height: 13px; flex: 0 0 auto; display: block; }

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

/* No fold. The pill used to be hidden below 1040 and left to the menu, which
   meant the one control that says "there are two of these sites" was invisible
   on every phone — while the console, which this pair is supposed to mirror,
   has always carried it in the bar at every width. Two glyphs are about 110
   pixels all in and fit a 320px navbar; what gives way instead, further down,
   is the Contact button, which the menu already carries twice over. */

/* The menu's copy of the pill sits in the panel's footer; its rules live with
   the rest of the menu further down. */

/* The hamburger. It used to appear only below 1040, where it opened a stacked
   list of the same five links the navbar was hiding. It is at every width now,
   the way the console's is, and it opens the whole menu rather than a fold of
   the header — so on a wide screen the navbar's five section links and the
   menu's forty-six properties are two different offers rather than the same
   one twice. */
.nav__burger {
    display: flex; width: 40px; height: 40px; padding: 0;
    background: transparent; border: 1px solid var(--line); border-radius: 8px;
    cursor: pointer; align-items: center; justify-content: center;
    color: var(--ink);
    transition: border-color .24s var(--ease);
}
.nav__burger:hover { border-color: var(--ink); }
.nav__burger svg { display: block; }
/* The two bars morph into a close cross. transform-box: view-box puts the origin
   in the icon's own coordinate space rather than on the line's own zero-height
   box, and the translate has to come second in the list — CSS applies transform
   functions right to left, so this reads "slide the bar to the centre line,
   then turn it about the centre". */
.nav__burger line {
    transform-box: view-box; transform-origin: 9px 6px;
    transition: transform .28s var(--ease);
}
.nav__burger[aria-expanded="true"] .nav__burger-t { transform: rotate(45deg) translateY(2.5px); }
.nav__burger[aria-expanded="true"] .nav__burger-b { transform: rotate(-45deg) translateY(-2.5px); }
@media (prefers-reduced-motion: reduce) { .nav__burger line { transition: none; } }

/* -----------------------------------------------------------------------------
   THE HEADER ON A PHONE

   Three things have to fit inside 335 usable pixels at 375, and 280 at 320: the
   mark, the edition pill and the way into the rest of the site. Everything here
   is measured against those two numbers rather than eyeballed, because the
   header is the one component that cannot be allowed to wrap on the narrow one.

   The mark loses its rule-and-label at 900 already; below 640 the logo itself
   comes down to 64px, the gaps close to a third of what they are on a desktop,
   and the bar loses twelve pixels of height. That buys about 90 pixels, which
   is what the pill costs.
   -------------------------------------------------------------------------- */
@media (max-width: 1040px) { .nav__in { gap: .9rem; } }

@media (max-width: 640px) {
    :root { --nav-h: 62px; }
    .nav__in { gap: .55rem; }
    .nav__mark img { width: 64px; height: 27px; }

    /* A segment is a glyph in a box, and the box is the tap target. The glyph
       stays at 13px — it is meant to be small — and what grows on a phone is the
       box around it, so the control is thumbable without becoming loud. */
    .mode-pill__seg { min-width: 1.9rem; padding: .58rem .4rem; }

    /* No ring on a phone. On a desktop the hairline box tells you the hamburger
       is a control among other controls; on a phone it is the only thing in that
       corner, the mark and the pill beside it are unboxed, and the outline was
       the heaviest object in the bar for no work it was doing. */
    /* 44px is the smallest a finger reliably hits, and on a phone the
       burger is the first control anybody reaches for. */
    .nav__burger { width: 44px; height: 44px; border-color: transparent; }
    .nav__burger:hover { border-color: transparent; }
}

/* Below 640 the Contact button leaves the bar on the pages that have the menu.
   It is not lost: the menu lists it under This page, the panel's Reach the desk
   section carries the address and the number, and the contact band is the last
   thing on the page. On the satellite pages, which have no menu, the button is
   the only action there is and it stays — see .nav--sat below. */
@media (max-width: 640px) { .nav--menu .nav__in > .btn { display: none; } }

/* -----------------------------------------------------------------------------
   THE SATELLITE HEADER

   network-paper, tech-paper, newsy-paper and windy-paper carry the same header
   as the homepage without the homepage's menu behind it — so when .nav__links
   went out at 1040 those four pages had, on a phone, no navigation at all: a
   mark and one button. The links come back as a second row, scrolled sideways
   rather than wrapped, which is the one shape that holds four items at any
   width without the header growing a third line.

   The row bleeds to both edges and re-pads itself, so the first and last link
   sit on the gutter when the row is at rest and the run passes under the edge
   rather than stopping short of it — which is what tells a reader there is more
   of it to the right.
   -------------------------------------------------------------------------- */
@media (max-width: 1040px) {
    .nav--sat .nav__in {
        flex-wrap: wrap; height: auto;
        padding-top: .6rem; padding-bottom: 0;
    }
    .nav--sat .nav__links {
        display: flex; order: 5; flex: 1 0 100%;
        gap: 1.35rem;
        margin: .4rem calc(var(--gutter) * -1) 0;
        padding: 0 var(--gutter) .6rem;
        overflow-x: auto; overscroll-behavior-x: contain;
        -webkit-overflow-scrolling: touch;
        scrollbar-width: none; -ms-overflow-style: none;
    }
    .nav--sat .nav__links::-webkit-scrollbar { display: none; }
    .nav--sat .nav__links a { font-size: .8rem; white-space: nowrap; padding-block: .35rem; }
    .nav--sat .nav__in > .btn { padding: .6rem .9rem; font-size: .76rem; }
}
/* The two-row header is taller than the token says, and the token is what every
   anchor on the page subtracts. The four satellite pages re-declare it on <body>
   in their own sheets — a custom property inherits, so one line there moves the
   header height and every scroll margin under it together. */
@media (max-width: 640px) {
    .nav--sat .nav__in > .btn { padding: .55rem .8rem; font-size: .72rem; }
    .nav--sat .nav__links { gap: 1.15rem; }
    .nav--sat .nav__links a { font-size: .78rem; }
}
/* 360 and below. The satellite bar is mark, pill and button with no menu to
   fall back on, and at 320 there are 280 usable pixels for all three. Measured,
   with the longest label the four pages carry:

       mark 56 + pill 88 + "Change location" 108 + two 6.4 gaps = 265

   which leaves fifteen. The mark is what gives, because it is the one of the
   three that is still legible a size down and the only one that is not a
   control.

   360 rather than 380, so that a 375 or 390 phone — which is most of them —
   keeps the roomier pill and the full-size mark. At 375 the ≤640 sizes come to
   296 against 335 available, so there is nothing to buy there. */
@media (max-width: 360px) {
    .nav__in { gap: .4rem; }
    .nav__mark img { width: 56px; height: 24px; }
    .mode-pill__seg { min-width: 1.7rem; }
    .nav--sat .nav__in > .btn { padding: .55rem .62rem; letter-spacing: 0; }
}

/* =============================================================================
   THE MENU

   The console's hamburger opens a full-height panel off the right edge with a
   search field at the top and every property in the book below it. This is the
   same instrument in the paper register.

   What did not come across is the colour. The console codes forty-seven tiles
   in forty-seven hues, which is legible there because the whole page is built
   that way; dropped into a site with one ink and one accent it would be the
   loudest thing by an order of magnitude. The tiles carry their practice's
   glyph instead — the same four marks the Practices band uses — and the
   grouping does the work the colour was doing.
   ========================================================================== */
.menu[hidden] { display: none; }
.menu {
    position: fixed; inset: 0; z-index: 200;
    display: block;
    /* The panel parks at translateX(100%) — one panel-width past the right
       edge. Clipped here, because on the way out the scroll lock is released
       the instant close() runs while the panel is still travelling, and an
       unclipped panel sitting outside the viewport during those 360ms is a
       horizontal scrollbar that appears and leaves for no visible reason. */
    overflow: hidden;
}
.menu__scrim {
    position: absolute; inset: 0;
    background: rgba(17, 22, 28, .38);
    -webkit-backdrop-filter: blur(3px); backdrop-filter: blur(3px);
    opacity: 0; transition: opacity .32s var(--ease);
}
.menu.is-open .menu__scrim { opacity: 1; }

.menu__panel {
    position: absolute; top: 0; right: 0; height: 100%;
    width: 100%; max-width: 404px;
    background: var(--paper);
    border-left: 1px solid var(--line);
    box-shadow: -24px 0 60px rgba(30, 38, 43, .10);
    display: flex; flex-direction: column;
    transform: translateX(100%);
    transition: transform .36s var(--ease);
}
.menu.is-open .menu__panel { transform: none; }
/* The panel takes focus itself on open so a screen reader reads the dialog's
   label. It is a container, not a control, and the sheet-wide :focus-visible
   rule was drawing a 2px accent ring around all 404 pixels of it. */
.menu__panel:focus, .menu__panel:focus-visible { outline: none; }
@media (prefers-reduced-motion: reduce) {
    .menu__scrim, .menu__panel { transition: none; }
}

.menu__head {
    flex: 0 0 auto; height: 74px;
    display: flex; align-items: center; gap: .8rem;
    padding: 0 1.15rem 0 1.4rem;
    border-bottom: 1px solid var(--line-soft);
}
.menu__head img { display: block; width: 66px; height: 28px; }
.menu__title {
    font-size: .6rem; font-weight: 700; letter-spacing: .3em; text-transform: uppercase;
    color: var(--ink-55); padding-left: .8rem; border-left: 1px solid var(--line);
    margin-right: auto;
}
/* ------------------------------------------------- the in-house door ----
   In the drawer's header, immediately left of the MENU label and mirroring the
   close button at the far end of the same row: same 34px square, same hairline,
   same radius, so the head reads as a bar with a control at each end rather
   than a wordmark with things attached to it. Every colour is a token, so the
   dark edition inks it without needing a second rule. */
.menu__inhouse {
    width: 34px; height: 34px; flex: 0 0 auto; padding: 0;
    display: flex; align-items: center; justify-content: center;
    background: transparent; border: 1px solid var(--line); border-radius: 8px;
    color: var(--ink-55);
    transition: border-color .24s var(--ease), color .24s var(--ease),
                background .24s var(--ease);
}
/* The one place it parts company with the close button: this is a door, not a
   dismissal, so it fills on hover instead of merely darkening its edge. */
.menu__inhouse:hover,
.menu__inhouse:focus-visible {
    border-color: var(--ink); background: var(--ink); color: var(--paper);
}
.menu__inhouse svg { width: 15px; height: 15px; display: block; }

.menu__x {
    width: 34px; height: 34px; flex: 0 0 auto; padding: 0;
    display: flex; align-items: center; justify-content: center;
    background: transparent; border: 1px solid var(--line); border-radius: 8px;
    color: var(--ink-55); cursor: pointer;
    transition: border-color .24s var(--ease), color .24s var(--ease);
}
.menu__x:hover { border-color: var(--ink); color: var(--ink); }
.menu__x svg { width: 13px; height: 13px; }
/* Same reasoning as the burger: on a touch screen the close button is a 34px
   target in the corner of a full-width sheet, which is the hardest place on the
   panel to hit. The glyph stays 13px; only the box it sits in grows. */
@media (max-width: 640px) {
    .menu__x { width: 44px; height: 44px; }
}

/* The search field. Sticky, because the register below it is forty-six rows
   and a field that scrolls away is a field you have to hunt for again. */
.menu__search {
    flex: 0 0 auto; position: relative;
    padding: .9rem 1.4rem;
    border-bottom: 1px solid var(--line-soft);
    background: var(--paper);
}
.menu__search svg {
    position: absolute; left: 2.05rem; top: 50%; transform: translateY(-50%);
    width: 13px; height: 13px; color: var(--ink-55); pointer-events: none;
}
.menu__q {
    font: inherit; font-size: .88rem; font-weight: 500; width: 100%;
    padding: .6rem 2.1rem .6rem 2.05rem;
    color: var(--ink); background: rgba(30, 38, 43, .035);
    /* Same reasoning as .field: --line is a rule between two things that are
       already there, and a text input is a box that has to announce itself. */
    border: 1px solid rgba(30, 38, 43, .52); border-radius: 8px;
    transition: border-color .24s var(--ease), background .24s var(--ease);
    -webkit-appearance: none; appearance: none;
}
.menu__q::placeholder { color: var(--ink-55); font-weight: 400; }
.menu__q::-webkit-search-cancel-button { display: none; }
.menu__q:focus { outline: none; border-color: var(--ink); background: var(--paper); }
.menu__q:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }

/* Safari on iOS zooms the page when a focused input is under 16px, and a zoomed
   page carries the 404px panel — and the field that was just tapped — off the
   visual viewport. 16px is the threshold at which it does not, so the field
   takes it wherever the zoom can happen. Matched on the pointer rather than on
   a width: a phone held sideways is 932px wide and still zooms. */
@media (hover: none) and (pointer: coarse) {
    .menu__q { font-size: 16px; }
}
.menu__clear {
    position: absolute; right: 1.75rem; top: 50%; transform: translateY(-50%);
    width: 22px; height: 22px; padding: 0; display: flex;
    align-items: center; justify-content: center;
    background: transparent; border: 0; border-radius: 50%;
    color: var(--ink-55); cursor: pointer;
}
.menu__clear[hidden] { display: none; }
.menu__clear:hover { color: var(--ink); background: rgba(30, 38, 43, .06); }
.menu__clear svg { width: 10px; height: 10px; }

/* The count of properties in the panel. Micro-caps, the same size the group
   headings are, sitting on the rule under the search field — it is a reading
   of the panel, not a control in it, so it never takes a line of its own. */
.menu__count {
    flex: 0 0 auto; margin: 0;
    padding: .5rem 1.4rem .55rem;
    border-bottom: 1px solid var(--line-soft);
    font-size: .55rem; font-weight: 700; letter-spacing: .19em; text-transform: uppercase;
    color: var(--ink-55); font-variant-numeric: tabular-nums;
}
.menu__count b { color: var(--ink); font-weight: 700; }
/* Filtering is the state worth marking: the number on screen is no longer the
   number in the book. */
.menu__count[data-filtered="1"] b { color: var(--accent); }

.menu__body {
    flex: 1 1 auto; overflow-y: auto; overscroll-behavior: contain;
    padding: 1.3rem 1.4rem 1.6rem;
}
/* Sections space themselves off the one before. The second selector is the two
   built sections — recents and favourites live inside #menu-marks, so the
   first section after them is a sibling of the DIV and not of a section, and
   without this the register's first heading sat on top of the chips. */
.menu__sec + .menu__sec,
#menu-marks:not(:empty) + .menu__sec { margin-top: 1.7rem; }
.menu__sec[hidden] { display: none; }
.menu__h {
    display: flex; align-items: center; gap: .7rem;
    font-size: .58rem; font-weight: 700; letter-spacing: .22em; text-transform: uppercase;
    color: var(--ink-55); margin: 0 0 .75rem;
}
.menu__h::after { content: ''; height: 1px; background: var(--line); flex: 1 1 auto; }

/* A section that can fold — today only the homepage's "This page". Closed is
   the resting state, written into the markup, so the panel opens on the
   register rather than on a list of anchors the visitor is already inside.
   While a search is running the fold stands aside (.is-searching, set by the
   filter): a match behind a closed fold is a result the visitor typed for,
   and hiding it would make the count lie. */
.menu__fold {
    display: inline-flex; align-items: center; gap: .45rem;
    background: none; border: 0; padding: 0; margin: 0;
    font: inherit; letter-spacing: inherit; text-transform: inherit;
    color: inherit; cursor: pointer;
}
.menu__fold:hover { color: var(--ink); }
.menu__fold:focus-visible { outline: 2px solid var(--accent); outline-offset: 3px; }
.menu__fold-i { flex: 0 0 auto; transition: transform .3s var(--ease); }
.menu__sec--fold:not(.is-closed) .menu__fold-i,
.menu__sec--fold.is-searching .menu__fold-i { transform: rotate(180deg); }
.menu__sec--fold.is-closed:not(.is-searching) .menu__row { display: none; }

/* A row: the shape the console uses for its actions, in hairlines. */
.menu__row {
    display: flex; align-items: center; gap: .85rem; width: 100%;
    padding: .68rem .2rem; text-align: left;
    background: transparent; border: 0; border-bottom: 1px solid var(--line-soft);
    font: inherit; color: var(--ink-77); text-decoration: none; cursor: pointer;
    transition: color .2s var(--ease), padding-left .2s var(--ease);
}
.menu__row:hover { color: var(--ink); padding-left: .5rem; }
.menu__row[hidden] { display: none; }
.menu__row:last-child { border-bottom: 0; }
.menu__i {
    flex: 0 0 auto; width: 26px; text-align: center;
    color: var(--accent); font-size: .62rem; line-height: 1;
}
.menu__l { flex: 1 1 auto; min-width: 0; }
.menu__l b { display: block; font-size: .9rem; font-weight: 600; letter-spacing: -.015em; color: var(--ink); }
.menu__l span {
    display: block; font-size: .72rem; font-weight: 400; color: var(--ink-55);
    margin-top: .1rem; overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.menu__go { flex: 0 0 auto; color: var(--ink-55); font-size: .8rem; font-style: normal; }

/* A menu row that carries a picture of where it goes, rather than the glyph the
   other rows use. One row has it — the news hub — because it is the only
   destination in the menu that is a whole second reading of the site rather than
   a section of this one, and a line of text does not convey that.
   Drawn rather than photographed: an inline SVG of the hub's actual layout (hero
   left, live-feed panel right, chip strip beneath) stays sharp at every density,
   costs no request, needs no cache-busting, and follows the edition's colours
   instead of baking one theme's screenshot into a raster that would go stale the
   next time the page changed. */
.menu__row--thumb { align-items: center; gap: .8rem; padding-top: .8rem; padding-bottom: .8rem; }
.menu__thumb {
    flex: 0 0 auto; width: 62px; height: 42px; border-radius: 5px;
    border: 1px solid var(--line); background: var(--paper);
    overflow: hidden; display: block;
    box-shadow: 0 1px 2px rgba(30, 38, 43, .06);
    transition: border-color .2s var(--ease), box-shadow .2s var(--ease);
}
.menu__row--thumb:hover .menu__thumb {
    border-color: var(--accent);
    box-shadow: 0 2px 8px rgba(30, 38, 43, .1);
}
.menu__thumb svg { display: block; width: 100%; height: 100%; }
/* The mark that says "this one is the paper reading". */
.menu__thumb .t-ink   { fill: var(--ink); }
.menu__thumb .t-line  { fill: var(--line); }
.menu__thumb .t-acc   { fill: var(--accent); }
.menu__thumb .t-card  { fill: rgba(30, 38, 43, .055); }
.menu__thumb .t-bg    { fill: var(--paper); }

/* The property tiles. Three across at the panel's width, which is what fits a
   two-word name at this size without hyphenating it. */
.menu__grid { display: grid; grid-template-columns: repeat(3, 1fr); gap: .4rem; }

/* THE SHELF — nine random books under Diversions, dealt on every open.
   Tighter than property tiles: a book title needs the width more than the
   air, and two clamped lines hold almost any title without growing the row. */
.menu__h--shelf { margin-top: .55rem; }
.menu__shelf .menu__tile { padding: .45rem .5rem; }
.menu__shelf .menu__tile b { font-size: .66rem; line-height: 1.25; white-space: normal;
    display: -webkit-box; -webkit-line-clamp: 2; -webkit-box-orient: vertical; overflow: hidden; }
.menu__shelfmore { display: block; margin-top: .45rem; font-size: .72rem; font-weight: 600;
    color: var(--info); text-decoration: none; }
.menu__shelfmore:hover { color: var(--accent); }
.menu__grid[hidden] { display: none; }
.menu__tile {
    position: relative;
    display: flex; flex-direction: column; align-items: center; justify-content: center;
    gap: .34rem; padding: .72rem .3rem;
    border: 1px solid var(--line); border-radius: 10px;
    text-decoration: none; color: var(--ink); background: var(--paper);
    transition: border-color .2s var(--ease), background .2s var(--ease);
}
.menu__tile:hover { border-color: var(--ink); background: rgba(30, 38, 43, .028); }
.menu__tile[hidden] { display: none; }
.menu__tile i { font-style: normal; color: var(--accent); font-size: .58rem; line-height: 1; }
.menu__tile b {
    font-size: .68rem; font-weight: 600; letter-spacing: -.01em; line-height: 1.2;
    text-align: center; overflow-wrap: anywhere;
}
/* The off-site marker sits in the corner rather than in the stack. In flow it
   is a third line, and only the fifteen external tiles have one — so a row
   with one in it stood taller than a row without. */
.menu__tile em {
    position: absolute; top: .34rem; right: .4rem;
    font-style: normal; font-size: .5rem; line-height: 1; color: var(--ink-55);
}

/* ---------------------------------------------------------------- the marks
   Recents and favourites. Deliberately NOT tiles: a tile is a card two lines
   tall and three to a row, and six of them above the register would push the
   fifty-two properties off the first screen of the panel. A chip is one line,
   they wrap, and eleven of them still cost less height than one row of tiles.

   The heart is a real <button> standing BESIDE the link inside the chip rather
   than inside it — an interactive control nested in an anchor is a control that
   follows the anchor about half the time, on about half the browsers. */
.menu__marks { display: flex; flex-wrap: wrap; gap: .3rem; }
.menu__mark {
    display: inline-flex; align-items: center; gap: .2rem; max-width: 100%;
    padding: .18rem .22rem .18rem .5rem;
    border: 1px solid var(--line); border-radius: 999px;
    background: var(--paper);
    transition: border-color .2s var(--ease);
}
.menu__mark:hover { border-color: var(--ink); }
.menu__mark[hidden] { display: none; }
.menu__mark > a {
    display: inline-flex; align-items: baseline; gap: .32rem; min-width: 0;
    text-decoration: none; color: var(--ink);
}
.menu__mark > a i { flex: 0 0 auto; font-style: normal; font-size: .5rem; color: var(--accent); }
.menu__mark > a b {
    font-size: .7rem; font-weight: 600; letter-spacing: -.01em;
    white-space: nowrap; overflow: hidden; text-overflow: ellipsis; max-width: 11rem;
}
.mk-heart {
    flex: 0 0 auto; width: 20px; height: 20px; padding: 0;
    display: inline-flex; align-items: center; justify-content: center;
    -webkit-appearance: none; appearance: none;
    background: none; border: 0; border-radius: 50%;
    color: var(--ink-55); cursor: pointer;
    transition: color .18s var(--ease), transform .18s var(--ease);
}
/* Outline when it is not kept, solid when it is: the same glyph either way, so
   the change is the state and not the symbol. */
.mk-heart svg { width: 11px; height: 11px; fill: none; stroke: currentColor; stroke-width: 1.7; }
.mk-heart:hover { color: var(--accent); transform: scale(1.16); }
.mk-heart.is-on { color: var(--accent); }
.mk-heart.is-on svg { fill: currentColor; }
.mk-heart:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }

/* The one word of housekeeping in the panel. It sits after the heading's
   hairline, which is a flex spacer, so it lands hard right without a rule. */
.menu__hx {
    flex: 0 0 auto; margin-left: .6rem; padding: 0;
    -webkit-appearance: none; appearance: none;
    background: none; border: 0; font: inherit;
    font-size: .55rem; font-weight: 700; letter-spacing: .2em; text-transform: uppercase;
    color: var(--ink-55); cursor: pointer;
    transition: color .2s var(--ease);
}
.menu__hx:hover { color: var(--accent); }

/* Surfaces at the top of the panel when the query looks like somebody trying
   to reach a person rather than a page. */
.menu__pin {
    display: flex; align-items: center; gap: .85rem;
    margin-bottom: 1.4rem; padding: .85rem 1rem;
    border: 1px solid var(--accent); border-radius: 10px;
    text-decoration: none; background: rgba(194, 65, 12, .045);
}
.menu__pin[hidden] { display: none; }
.menu__pin b { display: block; font-size: 1.05rem; font-weight: 500; letter-spacing: -.02em; color: var(--ink); }
.menu__pin span {
    display: block; font-size: .55rem; font-weight: 700; letter-spacing: .2em;
    text-transform: uppercase; color: var(--accent); margin-bottom: .15rem;
}

.menu__empty {
    margin: 1.4rem 0 0; text-align: center;
    font-size: .82rem; color: var(--ink-55);
}
.menu__empty[hidden] { display: none; }

.menu__foot {
    flex: 0 0 auto; padding: 1rem 1.4rem;
    border-top: 1px solid var(--line-soft); background: var(--paper);
}
/* The footer's copy of the pill used to be stretched to the full width of the
   panel — on a phone that is a 375-pixel track with a 15-pixel sun adrift in one
   half of it and a moon in the other, which reads as a broken button rather than
   as the same control that is in the bar above. It is the same control, so it is
   the same size: a segmented pill, held to its own width and centred, one step
   larger than the header's because a panel footer has the room. */
.menu__foot { display: flex; justify-content: center; }
.menu__foot .mode-pill { display: inline-flex; width: auto; }
.menu__foot .mode-pill__seg { flex: 0 0 auto; min-width: 3.6rem; padding: .6rem .8rem; }

@media (max-width: 460px) {
    .menu__panel { max-width: 100%; }
    .menu__grid { grid-template-columns: repeat(2, 1fr); }
}

/* =============================================================================
   BUTTONS
   ========================================================================== */

.btn {
    display: inline-flex; align-items: center; gap: .6rem;
    font: inherit; font-size: .82rem; font-weight: 600; letter-spacing: .02em;
    padding: .72rem 1.15rem; border-radius: 8px;
    border: 1px solid transparent; background: var(--ink); color: #fff;
    text-decoration: none; cursor: pointer;
    transition: background .25s var(--ease), color .25s var(--ease),
                border-color .25s var(--ease), transform .25s var(--ease);
}
.btn:hover { background: #000; transform: translateY(-1px); }
.btn:active { transform: none; }
.btn--ghost { background: transparent; color: var(--ink); border-color: var(--line); }
.btn--ghost:hover { background: var(--ink); color: #fff; border-color: var(--ink); }

/* The hero's call to action: uppercase, hairline box, arrow that steps out on
   hover. Deliberately not the filled button -- over a photographic field a
   solid block reads as a banner ad. */
.btn--outline {
    background: transparent; color: #fff; border-color: rgba(255,255,255,.45);
    text-transform: uppercase; font-size: .72rem; letter-spacing: .13em; font-weight: 600;
    padding: .82rem 1.35rem;
}
.btn--outline:hover { background: #fff; color: var(--ink); border-color: #fff; }
.btn i { transition: transform .25s var(--ease); }
.btn:hover i { transform: translateX(3px); }

/* =============================================================================
   HERO
   ========================================================================== */

.hero { position: relative; min-height: min(84vh, 720px); display: flex; align-items: flex-end;
        background: var(--deep); overflow: hidden; }
/* The reference site opens on an aerial photograph. There is no photograph to
   use here, and borrowing theirs was never an option, so the field is drawn:
   a slow horizon of fine verticals on a cold charcoal ground, painted to a
   canvas. It carries the same weight as a photo -- something quiet happening
   behind the words -- without pretending to be one. */
.hero__field { position: absolute; inset: 0; width: 100%; height: 100%; display: block; }
/* The scrim exists to protect the headline, and it was doing considerably more
   than that: at 94/72/30 it covered the whole width, so the field behind it
   was washed out everywhere rather than only under the type. Now it is heavy
   where the words are and clears quickly across, and the bottom pass stops
   short of the figures instead of climbing into the middle of the frame. */
.hero__scrim {
    position: absolute; inset: 0;
    background:
        linear-gradient(90deg, rgba(17,22,28,.92) 0%, rgba(17,22,28,.86) 26%,
                               rgba(17,22,28,.42) 62%, rgba(17,22,28,.06) 100%),
        linear-gradient(0deg,  rgba(17,22,28,.80) 0%, rgba(17,22,28,0) 38%);
}
.hero__in { position: relative; width: 100%; padding-block: clamp(56px, 11vw, 118px); }
/* The headline is one of twenty, swapped in by applyLanding() after the markup
   has already laid out — so anything that changes its height between the
   shipped variant and the drawn one is a shift in the largest element on the
   page.

   Two rules stop that. The measure was 20ch, which is narrower than the longest
   of the twenty lines ("One Contract. Ninety Seconds.", 29 characters) — those
   variants silently wrapped to three lines while the rest sat at two, so the
   swap could add a whole display line. At 30ch every variant is the two lines
   its <br> asks for. And min-height holds those two lines whatever happens, so
   a narrow viewport that does wrap one of them grows downward into reserved
   air rather than pushing the standfirst.

   line-height is 1.09, so two lines is 2.18em. */
.hero h1 { color: #fff; max-width: 30ch; min-height: 2.18em; text-wrap: balance; }
/* Same argument, one element down: the standfirst runs 98-140 characters
   across the twenty, which is two lines at the short end and three at the long
   one. Held at three, the block never grows when the copy is replaced — the
   shortest variant leaves a line of air instead, which a hero has to spare. */
.hero p { color: rgba(255,255,255,.74); max-width: 52ch; margin: 1.5rem 0 2.2rem; font-weight: 300; }
#hero-sub { min-height: 4.9em; }

/* The reservations above are the worst case from about 460px up, where the
   twenty variants settle at two display lines and three of standfirst. Below
   that the box narrows faster than the type does and the worst of them takes
   another line, then another — measured across all twenty at each step rather
   than guessed, because a reservation that is one line short is exactly the
   shift it was added to prevent, and one line long is a hole in the hero. */
@media (max-width: 460px) { .hero h1  { min-height: 3.27em; } }   /* 3 display lines */
@media (max-width: 420px) { #hero-sub { min-height: 6.52em; } }   /* 4 body lines    */
@media (max-width: 344px) {
    .hero h1  { min-height: 4.36em; }                             /* 4 display lines */
    #hero-sub { min-height: 8.15em; }                             /* 5 body lines    */
}

/* The live strip along the bottom of the hero — the one place the old build's
   pulse survives, reduced to a line of figures. */
.hero__ticker {
    position: relative; z-index: 2;
    border-top: 1px solid var(--line-dark);
    display: flex; flex-wrap: wrap; gap: 0;
}
.hero__stat {
    flex: 1 1 190px; padding: 1.15rem 0 1.25rem;
    border-right: 1px solid var(--line-dark);
}
.hero__stat:last-child { border-right: 0; }
.hero__stat b {
    display: block; font-size: 1.55rem; font-weight: 300; letter-spacing: -.03em; color: #fff;
    font-variant-numeric: tabular-nums;
}
/* Direct child only. As `.hero__stat span` this also caught the counter's own
   span inside the <b>, which shrank the figure to label size -- the number and
   its caption rendered at the same 10px. */
.hero__stat > span {
    display: block; margin-top: .15rem;
    font-size: .625rem; font-weight: 600; letter-spacing: .17em; text-transform: uppercase;
    color: rgba(255,255,255,.5);
}
/* Two up rather than four down on a phone. At a 190px basis each figure took a
   full row, which pushed the last one most of a screen below the headline and
   turned a four-figure summary into a scroll. */
@media (max-width: 640px) {
    .hero__stat { flex: 1 1 42%; padding: .95rem 0 1rem; }
    .hero__stat:nth-child(2n) { border-right: 0; padding-left: 1.1rem; }
    .hero__stat:nth-child(n+3) { border-top: 1px solid var(--line-dark); }
    .hero__stat b { font-size: 1.32rem; }
}

.live-dot {
    display: inline-block; width: 6px; height: 6px; border-radius: 50%;
    background: var(--accent); margin-right: .5rem; vertical-align: middle;
    animation: pulse 2.6s ease-in-out infinite;
}
@keyframes pulse { 0%,100% { opacity: 1; } 50% { opacity: .35; } }
@media (prefers-reduced-motion: reduce) { .live-dot { animation: none; } }

/* =============================================================================
   SPLIT — the two-column band the reference uses for every prose section
   ========================================================================== */

.split { display: grid; grid-template-columns: minmax(0, 5fr) minmax(0, 7fr); gap: clamp(2rem, 5vw, 5.5rem); }
@media (max-width: 860px) { .split { grid-template-columns: 1fr; gap: 2rem; } }
.split--even { grid-template-columns: 1fr 1fr; }
@media (max-width: 860px) { .split--even { grid-template-columns: 1fr; } }

/* The counter. One oversized figure per cell, hairline-separated. */
.figures { display: grid; grid-template-columns: repeat(auto-fit, minmax(150px, 1fr)); gap: 1px;
           background: var(--line); border-block: 1px solid var(--line); margin-top: clamp(2rem,4vw,3.4rem); }
.figures > div { background: var(--paper); padding: 1.6rem 1.4rem 1.5rem; }
.band--deep .figures { background: var(--line-dark); border-color: var(--line-dark); }
.band--deep .figures > div { background: var(--deep); }
.figure__n {
    font-size: clamp(2.1rem, 4vw, 3.05rem); font-weight: 200; letter-spacing: -.04em;
    line-height: 1; color: var(--ink); font-variant-numeric: tabular-nums;
}
.band--deep .figure__n { color: #fff; }
.figure__l {
    margin-top: .6rem; font-size: .64rem; font-weight: 700; letter-spacing: .17em;
    text-transform: uppercase; color: var(--ink-55);
}
.band--deep .figure__l { color: rgba(255,255,255,.5); }
.figure__d { margin-top: .5rem; font-size: .855rem; line-height: 1.5; color: var(--ink-55); }
.band--deep .figure__d { color: rgba(255,255,255,.55); }

/* =============================================================================
   PRACTICES
   ========================================================================== */

.practices { display: grid; grid-template-columns: repeat(auto-fit, minmax(255px, 1fr)); gap: 1px;
             background: var(--line); border: 1px solid var(--line); margin-top: clamp(2rem,4vw,3.2rem); }
.practice {
    background: var(--paper); padding: clamp(1.6rem, 2.4vw, 2.2rem);
    display: flex; flex-direction: column; min-height: 268px;
    text-decoration: none; transition: background .3s var(--ease);
}
.practice:hover { background: #FAFAFA; }
.practice__i {
    width: 34px; height: 34px; display: grid; place-items: center;
    border: 1px solid var(--line); border-radius: 50%; color: var(--ink);
    font-size: .78rem; margin-bottom: 1.4rem;
    transition: background .3s var(--ease), color .3s var(--ease), border-color .3s var(--ease);
}
.practice:hover .practice__i { background: var(--ink); color: #fff; border-color: var(--ink); }
.practice h3 { font-size: 1.16rem; font-weight: 600; letter-spacing: -.025em; }
.practice p { margin: .7rem 0 0; font-size: .9rem; line-height: 1.56; color: var(--ink-55); }
.practice__n {
    margin-top: auto; padding-top: 1.3rem;
    font-size: .64rem; font-weight: 700; letter-spacing: .17em; text-transform: uppercase;
    color: var(--ink-55);
}

/* =============================================================================
   PORTFOLIO INDEX
   -----------------------------------------------------------------------------
   A register, not a card wall. Forty-six properties on cards would be a grid of
   noise; as hairline-ruled rows they read the way a holdings table reads, which
   is the point of the whole restyle.
   ========================================================================== */

.filters { display: flex; flex-wrap: wrap; gap: .5rem; margin-top: 2.2rem; }
.filter {
    font: inherit; font-size: .755rem; font-weight: 600; letter-spacing: .02em;
    padding: .5rem .95rem; border-radius: 999px;
    border: 1px solid var(--line); background: transparent; color: var(--ink-77);
    cursor: pointer; transition: all .24s var(--ease);
}
.filter:hover { border-color: var(--ink-55); color: var(--ink); }
.filter[aria-pressed="true"] { background: var(--ink); border-color: var(--ink); color: #fff; }
.filter span { opacity: .55; font-variant-numeric: tabular-nums; margin-left: .3rem; }

.register { margin-top: 2.2rem; border-top: 1px solid var(--line); }
.entry {
    display: grid; grid-template-columns: 2.3fr 3.6fr 1.5fr auto;
    align-items: baseline; gap: 1.6rem;
    padding: 1.15rem .4rem 1.2rem;
    border-bottom: 1px solid var(--line-soft);
    text-decoration: none; color: inherit;
    transition: background .22s var(--ease), padding-left .22s var(--ease);
}
.entry:hover { background: #FAFAFA; padding-left: 1rem; }
.entry__name { font-size: 1.02rem; font-weight: 600; letter-spacing: -.022em; color: var(--ink); }
.entry__sum  { font-size: .85rem; line-height: 1.5; color: var(--ink-55); }
.entry__sec  { font-size: .64rem; font-weight: 700; letter-spacing: .15em;
               text-transform: uppercase; color: var(--ink-55); }
.entry__go   { font-size: .8rem; color: var(--ink-55); transition: transform .22s var(--ease), color .22s var(--ease); }
.entry:hover .entry__go { transform: translateX(4px); color: var(--ink); }
.entry__host { display: block; margin-top: .2rem; font-size: .7rem; color: var(--muted); font-weight: 400;
               letter-spacing: 0; }
@media (max-width: 900px) {
    .entry { grid-template-columns: 1fr auto; gap: .5rem 1rem; }
    .entry__sum { grid-column: 1 / -1; }
    .entry__sec { grid-column: 1; }
}
.register__empty { padding: 3rem .4rem; color: var(--ink-55); font-size: .92rem; }

/* =============================================================================
   INSIGHTS
   ========================================================================== */

.insights { display: grid; grid-template-columns: repeat(auto-fit, minmax(270px, 1fr));
            gap: clamp(1.4rem, 2.4vw, 2.2rem); margin-top: clamp(2rem,4vw,3.2rem); }
.insight { display: flex; flex-direction: column; text-decoration: none; }
.insight__t {
    aspect-ratio: 16 / 10; border-radius: 10px; overflow: hidden;
    background: var(--deep-soft); position: relative;
}
.insight__t canvas { width: 100%; height: 100%; display: block; }
.insight__k {
    margin: 1.1rem 0 .5rem; font-size: .625rem; font-weight: 700; letter-spacing: .17em;
    text-transform: uppercase; color: var(--ink-55);
}
.insight h3 { font-size: 1.06rem; font-weight: 600; letter-spacing: -.024em; line-height: 1.32; }
.insight p { margin: .55rem 0 0; font-size: .875rem; line-height: 1.55; color: var(--ink-55); }
.insight:hover h3 { text-decoration: underline; text-underline-offset: 3px; }

/* =============================================================================
   CONTACT + FOOTER
   ========================================================================== */

.offices { display: grid; grid-template-columns: repeat(auto-fit, minmax(190px, 1fr));
           gap: clamp(1.6rem, 3vw, 2.6rem); margin-top: 2.6rem; }
.office h3 { font-size: 1.16rem; font-weight: 700; letter-spacing: -.025em; }
.office p  { margin: .5rem 0 0; font-size: .875rem; line-height: 1.55; color: rgba(255,255,255,.55); }
.office a  { color: #fff; text-decoration: none; border-bottom: 1px solid var(--line-dark); }
.office a:hover { border-color: #fff; }

/* ---------------------------------------------------------------------------
   THE ENQUIRY FORM

   This control was written for the homepage, where the contact band is deep,
   and it was written in white: white type, a translucent white fill and a
   hairline in --line-dark. The data room puts the same form on a paper band,
   and there every one of those is white on white — the fields, their
   placeholders, their borders and the note under the button all vanished, and
   the only thing a visitor could see was the browser's own "please fill out
   this field" bubble pointing at an empty rectangle.

   So the form is drawn in ink here, like every other component on the sheet,
   and the white reading is the override for the ground that needs it. Both
   grounds are stated; neither is assumed.
   ------------------------------------------------------------------------ */
.form { display: grid; gap: .8rem; margin-top: 1.8rem; max-width: 460px; }
.form__row { display: grid; grid-template-columns: 1fr 1fr; gap: .8rem; }
@media (max-width: 560px) { .form__row { grid-template-columns: 1fr; } }
.field {
    font: inherit; font-size: .9rem; width: 100%;
    padding: .78rem .9rem; border-radius: 8px; color: var(--ink);
    /* Heavier than the sheet's hairline on purpose. Everywhere else --line is
       drawing a rule between two pieces of text and wants to be barely there;
       here it is the only thing saying an empty box is a control you can type
       in, so it is set at the 3:1 the boundary of an input is meant to hold. */
    background-color: rgba(30,38,43,.025); border: 1px solid rgba(30,38,43,.52);
    transition: border-color .24s var(--ease), background-color .24s var(--ease);
}
/* background-COLOR, here and in every rule below that repaints a field. The
   select draws its own arrow as two background gradients once the platform
   chrome is off it, and the `background` shorthand resets background-image,
   -size, -position and -repeat along with the colour — so a shorthand in any
   later rule takes the 5px arrow, tiles it at its natural size and fills the
   control with it. */
.field::placeholder { color: var(--ink-55); }
/* Pointer focus gets the border lift and nothing else — a ring drawn every
   time somebody clicks into a field is noise. Keyboard focus gets a real ring:
   `.field:focus` outranks the sheet-wide :focus-visible rule below on
   specificity, so without this pair a tabbing visitor crossed the contact form
   with a one-pixel border tint as their only indicator. */
.field:focus { outline: none; border-color: var(--ink); background-color: rgba(30,38,43,.045); }
.field:focus-visible {
    outline: 2px solid var(--accent); outline-offset: 2px;
    border-color: var(--ink); background-color: rgba(30,38,43,.045);
}
textarea.field { resize: vertical; min-height: 104px; }
/* The native select needs its own arrow once the platform chrome is removed. */
select.field {
    appearance: none; -webkit-appearance: none;
    cursor: pointer;
    padding-right: 2.4rem;
    background-image: linear-gradient(45deg, transparent 50%, var(--ink-55) 50%),
                      linear-gradient(135deg, var(--ink-55) 50%, transparent 50%);
    background-position: calc(100% - 18px) 51%, calc(100% - 13px) 51%;
    background-size: 5px 5px, 5px 5px;
    background-repeat: no-repeat;
}
select.field:invalid { color: var(--ink-55); }
.form .btn { justify-content: center; }
.form__note { font-size: .74rem; color: var(--ink-55); margin: 0; }
.form__status { font-size: .82rem; margin: 0; min-height: 1.2em; }
.form__status[data-state="ok"]  { color: #0F7A52; }
.form__status[data-state="err"] { color: #B3261E; }

/* ---- the same form on the deep band ---------------------------------------
   color-scheme: dark on the select is what keeps the dropped-open list dark
   too; without it the options render black-on-white over a dark form. */
.band--deep .field {
    color: #fff;
    background-color: rgba(255,255,255,.045); border-color: rgba(255,255,255,.34);
}
.band--deep .field::placeholder { color: rgba(255,255,255,.5); }
.band--deep .field:focus { border-color: rgba(255,255,255,.62); background-color: rgba(255,255,255,.08); }
.band--deep .field:focus-visible {
    outline-color: #fff;
    border-color: rgba(255,255,255,.62); background-color: rgba(255,255,255,.08);
}
.band--deep select.field {
    color-scheme: dark;
    background-image: linear-gradient(45deg, transparent 50%, rgba(255,255,255,.55) 50%),
                      linear-gradient(135deg, rgba(255,255,255,.55) 50%, transparent 50%);
}
.band--deep select.field:invalid { color: rgba(255,255,255,.5); }
.band--deep .form__note { color: rgba(255,255,255,.5); }
.band--deep .form__status[data-state="ok"]  { color: #6EE7B7; }
.band--deep .form__status[data-state="err"] { color: #FCA5A5; }

.foot { border-top: 1px solid var(--line-dark); background: var(--deep); color: rgba(255,255,255,.5); }
.foot__in { display: flex; flex-wrap: wrap; align-items: center; gap: 1rem 2rem; padding-block: 2.2rem; }
.foot__in p { margin: 0; font-size: .78rem; }
.foot__links { display: flex; flex-wrap: wrap; gap: 1.4rem; margin-left: auto; }
.foot__links a { font-size: .78rem; text-decoration: none; color: rgba(255,255,255,.5); }
.foot__links a:hover { color: #fff; }

/* =============================================================================
   REVEAL
   -----------------------------------------------------------------------------
   Held to a short rise with no scale and no stagger beyond a beat. The house
   voice is calm; anything springier undoes the type.
   ========================================================================== */
/* The hidden state is gated on html.js — the class the inline <head> latch sets
   before anything paints. Only a browser that is definitely running our script
   gets an element hidden pending that script's reveal; everyone else gets the
   content. Without this the h1, the standfirst and the call to action were all
   opacity:0 until site.js reached its observer, so any failure before that
   point served an empty page rather than an unanimated one. */
html.js .rise { opacity: 0; transform: translateY(14px); transition: opacity .7s var(--ease), transform .7s var(--ease); }
html.js .rise.is-in { opacity: 1; transform: none; }
@media (prefers-reduced-motion: reduce) {
    html.js .rise { opacity: 1; transform: none; transition: none; }
    .entry, .btn, .practice, .filter { transition: none; }
}

/* Focus, once, for everything. */
:focus-visible { outline: 2px solid var(--accent); outline-offset: 3px; border-radius: 3px; }

/* Read, not seen. The pill's lit segment is a span rather than a link, and a
   span carrying only an aria-label is announced by nothing — so it carries its
   words as real text and hides them here instead. */
.vh {
    position: absolute; width: 1px; height: 1px; margin: -1px;
    padding: 0; border: 0; overflow: hidden; white-space: nowrap;
    clip: rect(0 0 0 0); clip-path: inset(50%);
}

.skip {
    position: absolute; left: -9999px; top: 0; z-index: 100;
    background: var(--ink); color: #fff; padding: .8rem 1.2rem; text-decoration: none;
}
.skip:focus { left: 0; }

/* =============================================================================
   THE STRIPS
   -----------------------------------------------------------------------------
   Carried over from 47th.llc, which took it from the console build before it.
   There it was two rows of chips crawling over a dark ground; here it sits on
   paper between the hero and the first band, held by a hairline top and bottom
   like every other division on the page.

   Two rows rather than one, running opposite ways at slightly different speeds,
   so they never march in lockstep and read as a single block. Each property gets
   an enclosed chip rather than a run of text between dots: at crawl speed a
   bare "name · practice" string reads as one continuous smear.

   Speed is not set here. The script measures the laid-out run and writes
   animation-duration, so the strip always crawls at a fixed pixels-per-second
   whatever ended up in it.
   ========================================================================== */
.strips {
    border-top: 1px solid var(--line);
    border-bottom: 1px solid var(--line);
    background: #FCFCFC;
    overflow: hidden;
    padding: calc(1.1rem - 3px) 0;
}
.strip-row {
    display: flex; overflow: hidden;
    /* Three pixels of slack inside the clip. A row is as tall as one chip and a
       chip works out to a fractional height, so without this the clip rounds
       down and shaves the bottom line of pixels off every chip in the top row —
       which reads as the two rows overlapping rather than as one being cut. */
    padding: 3px 0;
    -webkit-mask-image: linear-gradient(90deg, transparent 0, #000 5%, #000 95%, transparent 100%);
            mask-image: linear-gradient(90deg, transparent 0, #000 5%, #000 95%, transparent 100%);
}
.strip-row + .strip-row { margin-top: calc(.7rem - 6px); }

.strip-track {
    display: flex; align-items: center; flex: 0 0 auto;
    gap: .7rem;
    /* Matches `gap` exactly. Without it the track is one gap short of twice a
       run, so the -50% keyframe lands half a gap off and the loop visibly
       hitches once per pass. */
    padding-right: .7rem;
    white-space: nowrap;
    animation: strip-run 120s linear infinite;
    backface-visibility: hidden;
    /* Parked until the script says the rows are actually on screen. The chips
       are built by JS in the first place, so a paused default costs a
       no-script visitor nothing — and it means the compositor is not being
       asked to run two infinite translations behind six screens of copy for
       the whole session. `will-change` rides the same switch: held permanently
       it is a promoted layer that never comes back. */
    animation-play-state: paused;
}
.strips.is-live .strip-track { animation-play-state: running; will-change: transform; }
/* The crawl deliberately does not pause on hover: a chip is a link, and
   stopping the row the moment the pointer arrives made every click stop the
   strip dead. */

.s-chip {
    display: inline-flex; align-items: center; gap: .6rem;
    flex: 0 0 auto;
    padding: .42rem .9rem;
    border: 1px solid var(--line); border-radius: 999px;
    background: var(--paper);
    font: inherit; text-align: left; cursor: pointer; text-decoration: none;
    transition: border-color .22s var(--ease), background .22s var(--ease);
}
.s-chip:hover { border-color: var(--ink); background: #fff; }
.s-chip:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }
.s-chip::before {
    content: '◆'; color: var(--accent); font-size: .58rem; line-height: 1; flex: 0 0 auto;
}
.s-name {
    font-size: .82rem; font-weight: 600; letter-spacing: -.018em; color: var(--ink);
}
.s-meta {
    font-size: .58rem; font-weight: 700; letter-spacing: .15em; text-transform: uppercase;
    color: var(--ink-55);
    padding-left: .6rem; border-left: 1px solid var(--line);
}

@keyframes strip-run {
    from { transform: translate3d(0, 0, 0); }
    to   { transform: translate3d(-50%, 0, 0); }
}
@media (prefers-reduced-motion: reduce) { .strip-track { animation: none; } }

@media (max-width: 640px) {
    .strips { padding: calc(.8rem - 3px) 0; }
    .strip-row + .strip-row { margin-top: calc(.5rem - 6px); }
    .s-chip { gap: .45rem; padding: .36rem .7rem; }
    .s-name { font-size: .76rem; }
    .s-meta { font-size: .53rem; padding-left: .45rem; }
}


/* =============================================================================
   THE ROULETTE — a chamber in the crawl, and a sight in the nav
   ========================================================================== */
.s-chip--roulette {
    font: inherit; cursor: pointer;
    background: transparent;
    border-color: var(--accent);
    color: var(--ink);
}
.s-chip--roulette svg { color: var(--accent); flex: 0 0 auto; }
.s-chip--roulette::before { content: none; }
.s-chip--roulette:hover svg { animation: roulette-spin 1.4s linear infinite; }

/* 26 wide, not 40. The glyph is 15px; a 40px box put eleven pixels of air on
   each side of it, on top of the bar's own gap, and the crosshair read as
   floating rather than as sitting beside the magnifier. Full height stays,
   so the target is still the height of every other control in the bar. */
.nav__roulette {
    display: flex; width: 26px; height: 40px; padding: 0;
    background: transparent; border: 0;
    cursor: pointer; align-items: center; justify-content: center;
    color: var(--accent);
}
.nav__roulette:hover svg { animation: roulette-spin 1.4s linear infinite; }
.nav__roulette svg { display: block; }
@keyframes roulette-spin { to { transform: rotate(360deg); } }
@media (prefers-reduced-motion: reduce) {
    .s-chip--roulette:hover svg, .nav__roulette:hover svg { animation: none; }
}
@media (max-width: 640px) { .nav__roulette { display: none; } }

/* The nav's Contact reads quieter than the hero CTAs — it is a direct child
   of the bar, which nothing else with .btn is. */
.nav__in > .btn { padding: .5rem .95rem; font-size: .78rem; }


/* =============================================================================
   BACK TO TOP — lower right, only when the page is well underway
   ========================================================================== */
.to-top {
    position: fixed; right: 1.1rem; bottom: 1.1rem; z-index: 70;
    width: 42px; height: 42px; padding: 0;
    display: flex; align-items: center; justify-content: center;
    background: var(--paper); color: var(--ink);
    border: 1px solid var(--line); border-radius: 50%;
    cursor: pointer;
    box-shadow: 0 4px 16px rgba(30, 38, 43, .14);
    opacity: 0; transform: translateY(8px); pointer-events: none;
    transition: opacity .25s var(--ease), transform .25s var(--ease), border-color .2s var(--ease);
}
.to-top.is-on { opacity: 1; transform: none; pointer-events: auto; }
.to-top:hover { border-color: var(--accent); }
@media (prefers-reduced-motion: reduce) { .to-top { transition: none; } }

/* =============================================================================
   THE ROULETTE, LOWER LEFT — the to-top's mirror on the other corner
   -----------------------------------------------------------------------------
   Same disc, same measures, same shadow as .to-top, but always present: the
   to-top earns its place by scroll position, while a chamber of the roulette
   is useful from the first paint. It carries the nav roulette's crosshair and
   its data-roulette, so the one delegated listener in site.js fires it — no
   second handler, no second pool. On phones it is the only roulette on the
   page at all: the nav's sight hides under 640px, this one stays.
   ========================================================================== */
.to-roll {
    position: fixed; left: 1.1rem; bottom: 1.1rem; z-index: 70;
    width: 42px; height: 42px; padding: 0;
    display: flex; align-items: center; justify-content: center;
    background: var(--paper); color: var(--accent);
    border: 1px solid var(--line); border-radius: 50%;
    cursor: pointer;
    box-shadow: 0 4px 16px rgba(30, 38, 43, .14);
    transition: border-color .2s var(--ease);
}
.to-roll:hover { border-color: var(--accent); }
.to-roll svg { display: block; }
.to-roll:hover svg { animation: roulette-spin 1.4s linear infinite; }
@media (prefers-reduced-motion: reduce) { .to-roll:hover svg { animation: none; } }

/* ------------------------------------------------------------- a row that acts
   .menu__row was written for <a>. A <button> wearing the same class arrives
   with the user agent's own 2px outset border, its buttonface background, its
   13px Arial and — because .menu__row is a flex container — a width that
   shrinks to its own text. Eight such rows of differing lengths read as a
   staircase of framed grey boxes beside a clean list of links, which is exactly
   what they were.

   Everything here is a reset, not a style. The point is that a button in this
   menu is indistinguishable from a link in it. The bottom rule is re-declared
   because `border: 0` above would take it with the rest, and the existing
   `.menu__row:last-child` still wins on specificity, so the last row of a
   section stays clean. */
button.menu__row {
    width: 100%;
    box-sizing: border-box;
    -webkit-appearance: none;
    appearance: none;
    background: none;
    border: 0;
    border-bottom: 1px solid var(--line-soft);
    border-radius: 0;
    font: inherit;
    text-align: left;
    cursor: pointer;
}

/* ------------------------------------------------------- the register fold --
   A hairline across the band with one small key sitting on its right end, the
   way a drawer handle sits on a drawer. The rule is the element's own border,
   so there is nothing to keep in step when the band's width changes.

   Colours are tokens, so the dark edition inks this from the same declaration.
   The key is 26px with a 44px tap target underneath it — small to look at,
   full-size to hit, which is the pair of things a control this quiet needs. */
.bookfold {
    position: relative; margin: 1.6rem 0 .2rem;
    border-top: 1px solid var(--line);
    display: flex; justify-content: flex-end;
}
.bookfold__b {
    position: relative; top: -13px;
    width: 26px; height: 26px; flex: 0 0 auto; padding: 0;
    display: inline-flex; align-items: center; justify-content: center;
    background: var(--paper); border: 1px solid var(--line); border-radius: 7px;
    color: var(--ink-55); cursor: pointer;
    transition: color .2s var(--ease), border-color .2s var(--ease);
}
.bookfold__b::after {
    /* The tap target, not the button. A 26px key is right to look at and wrong
       to hit; this gives it 44px without changing a pixel of the drawing. */
    content: ''; position: absolute; top: -9px; right: -9px; bottom: -9px; left: -9px;
}
.bookfold__b:hover, .bookfold__b:focus-visible { color: var(--ink); border-color: var(--ink); }
.bookfold__b svg { display: block; transition: transform .28s var(--ease); }
/* Closed: the chevron points up, because pressing it opens the band again. */
.bookfold__b[aria-expanded="false"] svg { transform: rotate(180deg); }

/* The fold itself. A height transition needs a measured height and this band
   has none until the register has rendered, so it folds on the properties that
   do not: opacity and a scale that collapses to nothing. `hidden` alone would
   have lost to .register's own display rule — the estate has met that one. */
#book-body { overflow: hidden; }
#book-body[hidden] { display: none; }

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