/* Amlak — public site.
   Palette and radii are lifted verbatim from the app's "Ivory & Brass-ink"
   system (src/App.css:4-16) so the marketing pages and the product read as one
   company, and a visitor who clicks into the live demo lands somewhere that looks
   like where they just were. If those tokens ever move, move them here too — this
   is a deliberate second copy of ONLY the :root block, nothing else.

   ⚠ TWO TOKENS ARE NOW SITE-ONLY, AND THAT IS A DECISION RATHER THAN DRIFT.
   `--ui` and `--proof` do NOT match the app, and must not be copied back into it.
     • `--ui` — George, 2026-08-23: "the fonts we are using are way too generic."
       He chose Cormorant + Lora from a four-way specimen, so the DISPLAY face is
       still shared with the app and only the body face forks. Lora is a text
       serif: right for a marketing page of headlines and short paragraphs, wrong
       for a product UI that is mostly dense tables and form labels. The site
       reading as more editorial than the app is the intent, not a mistake.
     • `--proof` — a darker warm grey for the hub cards' 10.5px proof line, which
       sits on translucent glass over moving light where `--muted` measures
       2.85 : 1 against AA's 4.5. See the note at `.hcard .sub`.
   Everything else in :root is still the app's, verbatim, and still has to move
   with it. */
:root{
  /* ⚠ MOVED WITH THE APP'S CONTRAST PASS, 2026-08-26. The app's ground went
     #F1ECE1 -> #DED5C0 (reviewed on a dial and taken to its deepest step) and its cards #FBF8F1 -> #FDFBF6, which took the
     card-vs-ground step from 1.11 : 1 to 1.41 : 1 — the flatness George was
     reacting to. These five keys are mirrored, so leaving them here would put a
     visibly lighter marketing page in front of the product it sells.
     ⚠ `--faint` FOLLOWED TOO, #A89F8E -> #847C6B -> #675F50 (2026-08-26, the round
     after: "change all the grey writing to a darker color"). It moved with `--muted`
     BOTH TIMES, and that pairing is now a rule rather than a coincidence — see the
     note under `--muted` below. On a card it is 5.73 : 1, which clears AA for the
     first time; on the bare ground it is 4.06 and still deliberately short. */
  --paper:#DED5C0; --panel:#FDFBF6; --panel-2:#E5DBC2;
  --ink:#1B1813; --faint:#1B1813;
  /* ⚠ NO LONGER SITE-ONLY — THE APP CAME TO IT (2026-08-26). This was a deliberate
     fork: the site's ground is a warm gradient, prose on it measured 3.17 : 1 where
     AA wants 4.5, and #6A6353 fixed it here while the app stayed on #877F70. What
     that fork actually recorded was the APP being wrong — #877F70 measured 3.36 on
     the app's own paper and 3.74 on a panel, and App.css had twice invented a
     darker local grey (`--login-dim`, `--comm-proof`) rather than move the token.
     The contrast pass moved it. Both codebases now say #605949, wiring.test.js:320
     is satisfied by agreement rather than by exemption, and and it went one step
     further with the ground: 4.76 on the deepened ground, 6.72 on a panel.
     ⚠⚠ AND BOTH TIERS MOVED AGAIN, 2026-08-26: #605949 -> #443F32, with `--faint`
     #847C6B -> #675F50 alongside it. 7.18 : 1 on the ground, 10.14 on a panel.

     ⚠ THE TWO TIERS MOVE TOGETHER OR NOT AT ALL, and this is arithmetic rather than
     taste. The old note here deferred `--faint` because a third tier at AA lands on
     top of the second — still true, and precisely why darkening `--faint` ALONE is
     the one forbidden move: it closes the gap and collapses three tiers to two.
     Moving both keeps the ratio-of-ratios at 1.66x, the separation this pair has
     always had, two notches lower. Nudge one of these and you must nudge the other in
     the same commit.
     ⚠ THE OLD CLAIM ABOUT `--faint`'S READERS WAS STALE AND PARTLY WRONG, and it is
     corrected here rather than carried: it said "26 uses, almost all inside the mocks
     … the two that are real prose on the ground use `--muted` instead". The count is
     34, and two live selectors DO put `--faint` on the bare ground — `.sv-tool span`
     (compare's stack picture) and `.legal-meta` (privacy's "Last updated" line). At
     4.32 they are legible now; before this move they were 2.83. */
  --muted:#1B1813;
  /* ⚠ MOVED WITH THE APP, IN THE SAME COMMIT, AND NOT AS A COURTESY. `wiring.test.js:353-389`
     compares every token these two blocks share and fails on a difference — which is what
     forces the marketing page and the product it sells to keep looking like one company.
     Measured on a panel: edge 1.46 → 2.81, rule 1.31 → 2.04, fill 1.12 → 1.33. See the
     matching note in App.css for why none of it is a text colour. */
  --border:rgba(27,24,19,.44); --hair:rgba(27,24,19,.32);
  --accent:#5C6B3C; --accent-soft:#E8EBD9;
  --forest:#2E4636; --forest-soft:#E0E6DE;
  --gold:#94661B; --gold-soft:#EFE5CE;
  --danger:#8E2C22; --danger-soft:#F3E2DF;
  /* ⚠ SITE-ONLY, NOT ONE OF THE APP'S. The proof line under every hub card sits
     on translucent glass over moving light, where `--muted` measures 2.85 : 1
     against AA's 4.5. See the note at `.hcard .sub`.
     ⚠ IT HAS GONE #5D5749 → #4A453A → #5D5749 → #4A453A, AND EVERY STEP TRACKED
     THE CARD BEHIND IT, which is the whole reason the token exists: darker when
     the card is translucent over a moving light, lighter when the card is flat.
     It is back down because the card is transparent again (George, 2026-08-23:
     "matt finishes and transparent glass").
     ⚠⚠ AND THIS IS THE LEVER, NOT THE GLASS. Measured on the new panel, the
     line came out at **4.24 : 1** against AA's 4.5 — a real miss. There were two
     ways to close it: make the panel lighter (raise `brightness`, or raise the
     tint's alpha) or make the ink darker. The first pays for contrast in exactly
     the currency George asked to keep — you see less of the gold moving behind
     the card, which IS the feature — so the ink moved instead and the glass was
     left alone. #4A453A measures **5.62 : 1** on the same worst frame.
     ⚠ SO THE TOKEN IS NO LONGER QUESTIONABLE. It came off the Now-redundant list
     the moment the card went transparent again: `--muted` (#877F70) measures
     2.85 : 1 here and fails outright. */
  --proof:#1B1813;
  --radius:3px;
  /* ⚠ A SECOND RADIUS, AND IT IS DELIBERATELY NOT `--radius`. George,
     2026-08-24: "I want to try rounding the edges of the boxes in a luxury way."
     `--radius:3px` is the whole site's document corner — buttons, nav links,
     inputs, table cells, the mocks — and it is right for all of them: a 3px
     corner reads as PAPER, which is what a page about lease documents should
     read as. It is exactly wrong for a pane of glass, where the corner is a
     GROUND EDGE and a soft one is the tell that the thing has thickness.
     ⚠ SO IT HAS EXACTLY THREE READERS — the eleven cards, the nav mark and the
     hub core — and widening it to anything else re-rounds the whole site by
     accident. The two mark values are proportional to their own boxes (30px and
     ~68px), not copies of the card's, because a fixed 14 on a 30px tile is a
     lozenge and on a 68px one is barely a corner. */
  --glass-r:14px;
  --glass-r-sm:8px;
  --glass-r-lg:16px;
  --shadow:0 1px 2px rgba(40,30,20,.04),0 12px 30px -18px rgba(40,30,20,.16);
  --shadow-lift:0 2px 4px rgba(40,30,20,.05),0 28px 60px -30px rgba(40,30,20,.28);
  --display:'Cormorant Garamond',Georgia,serif;
  /* ⚠ THE SITE'S MOCK FIGURES ARE PICTURES OF THE APP, so this is not decoration — leave it
     Cormorant and the screenshots on the marketing page show a product that no longer
     exists. Three rules use it: `.mock-stat .v`, `.rw-d` and `.sv-one b`. `.yd-cap` is a
     caption, not a figure, and deliberately stays on --display. */
  --figure:'Source Serif 4',Georgia,serif;
  /* ⚠ SITE-ONLY — see the note at the top of this file. A serif fallback stack,
     deliberately: falling back from Lora to a system SANS would change the page's
     whole voice on a slow connection, which is the moment the voice matters. */
  --ui:'Lora',Georgia,'Times New Roman',serif;
  --wrap:1160px;
  --navh:70px;      /* the sticky nav's height — two rows on mobile, see the media query */
}
*{box-sizing:border-box}
/* ── THE GROUND ──────────────────────────────────────────────────────
   George, 2026-08-23: "you did not extend the design to the rest of the
   features page."

   The raking light went into the hero and nothing below it moved, so one
   screen down the page fell off a cliff into flat `--paper` — a hard colour
   step across the full width, exactly where the eye is on its way to the first
   feature. The fix is not to light the whole page (a sweep crossing a table of
   figures is the strobe we spent a round removing, and it is a rAF loop for the
   entire scroll). It is to give the WHOLE DOCUMENT the warm ground the hero
   sits on, and let the hero be the part of it that also has light.

   ⚠ ON `html`, NOT ON `body`, AND FIXED. The root's background is what paints
   the viewport canvas, so putting it here covers the page even where `body` is
   shorter or longer than the window. `fixed` means the gradient is positioned
   against the VIEWPORT rather than the document: every screenful gets the same
   ground, so there is no point at which it restarts and no second seam further
   down. A scrolling gradient over a 9,000px page would be a 9,000px gradient,
   which is either invisible or a rainbow.
   ⚠ AND `body` MUST NOT REPAINT IT. A background on `body` covers the root's
   entirely — that is what `background:var(--paper)` was doing here. */
html{
  scroll-behavior:smooth;
  background:
    radial-gradient(140% 88% at 50% 0%, rgba(182,130,53,.15) 0%, rgba(182,130,53,0) 68%),
    radial-gradient(96% 62% at 84% 52%, rgba(214,196,150,.20) 0%, rgba(214,196,150,0) 74%),
    var(--paper);
  background-attachment:fixed;
}
body{
  margin:0;color:var(--ink);
  font-family:var(--ui);font-size:16px;line-height:1.6;
  -webkit-font-smoothing:antialiased;text-rendering:optimizeLegibility;
}
img{max-width:100%;display:block}
a{color:inherit}

/* ── shared shell ───────────────────────────────────────────────── */
.wrap{max-width:var(--wrap);margin:0 auto;padding:0 28px}
.section{padding:96px 0}
.section.tight{padding:64px 0}
/* A section that opens straight onto a `.feature` block instead of a
   `.section-head` — the homepage since 2026-08-22. The block brings its own
   72px, so the section's own top padding would just stack on top of it. */
.section.deep{padding-top:0}
/* ⚠ SCOPED TO `.deep`, NOT TO `.feature`. The homepage's blocks are a heading
   and a short tick list now; /consultation still runs a paragraph and a tick
   list in the same class and wants its original measure. Widening this
   selector re-types two pages to suit one. */
.section.deep .feature{padding:62px 0}
/* ⚠⚠ A BLANKET `p` RULE LIVED HERE FOR THREE ROUNDS AND IT WAS OVERRIDING TWO
   RULES THAT NOBODY REALISED IT WAS OVERRIDING. It was
   `.section.deep .feature-art p{font-size:17px;line-height:1.55;max-width:38ch}`,
   left behind by the one-sentence deck each feature block used to carry, and at
   (0,2,1) it beat BOTH `.mock-title` (0,1,0) and `.rev-foot` (0,1,0). So every
   mock's section label and the lease-review footnote had been drawing at
   SEVENTEEN pixels while the file said 9.5 and 10.5 — a label the same size as
   the body text it labels, which is most of why the mocks read like slides
   rather than like app screens.

   It was scoped to `.feature-art` (rather than deleted) on 2026-08-24 to
   preserve exactly what was rendering, with a note saying the 9.5px was still
   wrong and unfixed. Deleted on 2026-08-24, second pass, when a two-pane lease
   mock added `.lx-page p` at 8.4px and became the third victim of the same
   trap.

   ⚠ NOTHING NEEDED IT. Every `p` inside `.feature-art` now carries a class with
   its own size: `.mock-title` (11px), `.rev-foot` (10.5px), `.lx-page p` /
   `.lx-hit` (8.4px). All three finally render what they declare.
   ⚠ DO NOT REINTRODUCE A BARE ELEMENT SELECTOR IN THIS SECTION. The mocks are a
   dozen small components sharing one container; a blanket `p`, `span` or `b`
   rule here outranks their own single-class rules and the damage is silent —
   nothing errors, the file just stops describing the page. */
/* (the homepage copy's own measure is below) */
/* The homepage copy's own measure — THE WHOLE COLUMN, deliberately. 38ch was
   the deck's, and a measure is the wrong instrument for this list: the promise
   here is ONE LINE PER BULLET (George, 2026-08-24), so the column width is the
   budget the COPY is written against, not something to narrow afterwards. Every
   bullet was measured at 15.5px against the 476px this leaves at 1440 and cut
   until it fit; capping the list at 46ch would have re-wrapped twenty of them
   and quietly undone the writing. If a future bullet wraps here, shorten the
   bullet. */
/* ⚠⚠ THE LIST IS CENTRED AS A BLOCK; ITS TEXT IS NOT. This is the third and
   correct answer to one question, and both earlier answers are worth keeping
   because each fixed something real and broke something else.

   1. LEFT, FLUSH TO THE COLUMN (original). Every tick in one column, which is
      what a scan-list needs — but the whole block sat on the OUTER edge of its
      column, the page edge on a plain block and the gutter on a `.flip` one, so
      eleven lists zig-zagged down the page. George: *"its sitting on the edge."*
   2. `text-align:center` ON THE LIST (2026-08-24). That fixed the zig-zag and
      broke the column: centring moves the TEXT and leaves an absolutely
      positioned `::before` pinned at `left:0`, so the ticks had to be folded
      back into the flow as `'✓\00a0\00a0'` — after which no two ticks in a block
      shared an x at all. George: *"all the bullet points should start at the
      same point, so all the check marks should be in line."*
   3. `width:fit-content` + `margin-inline:auto` (here). The list shrinks to its
      own longest line and is centred in the column as ONE box, so the block is
      off the edge AND every tick is at the same x. The `::before` goes back out
      of flow, which is also what `/consultation`'s `<strong>`-led items need.

   ⚠ EVERY RULE STAYS SCOPED TO `.section.deep`. `/consultation` runs the same
   class with a bold lead and a following sentence; it must keep the site
   default, and the out-of-flow marker is there for a documented reason (see the
   note above `.ticks li`).
   ⚠ THE ONE-LINE-PER-BULLET PROMISE STILL HOLDS THE MEASURE. `fit-content` caps
   at the column, so a bullet that would have wrapped still wraps — it does not
   widen the page. If one wraps, shorten the bullet (the copy was measured
   against 476px at 1440). */
.section.deep .feature .ticks{
  margin-top:20px;width:fit-content;max-width:100%;margin-inline:auto;
}
/* ⚠ ONLY the heading centres. Centred body text has no fixed left edge for the
   eye to return to, and these are scan-lines: a reader checking whether a
   feature does the thing they need reads the first two words of each bullet,
   which only works if those words are in a column. */
/* ⚠ BIGGER AND HEAVIER ON PURPOSE (George, 2026-08-24: "make the title bigger,
   maybe let's bold it a bit more so it pops out"). 600 is a REAL weight here —
   Cormorant Garamond is loaded at 400/500/600/700 in every page's font link, so
   this is not a browser-synthesised bold. Check the link before raising it to
   700; a weight that is not in the URL gets faked and looks muddy at 42px. */
.section.deep .feature h3{
  text-align:center;font-size:clamp(30px,3.6vw,42px);font-weight:600;
  margin-bottom:16px;
}
.section.deep .feature .ticks li{font-size:15.5px;line-height:1.55}
.section.deep .feature .ticks li::before{font-size:14px;line-height:1.62}
/* ⚠ A VEIL, NOT A SECOND PAPER. It was flat `--panel-2` between two hard
   `--hair` rules — a correct way to mark a section off on a page whose ground is
   one flat colour, and wrong the moment the ground became a warm gradient: an
   opaque panel covers the ground it is supposed to be sitting on, so the section
   read as a grey rectangle pasted over the light. Translucent, it lifts the
   ground instead of replacing it, and the rules soften to a warm hairline that
   fades at both ends rather than cutting the full width. */
.section.alt{
  /* ⚠ NO `backdrop-filter` — it had a 2px blur, which is below the threshold of
     visible and above the threshold of "this element now has its own GPU layer".
     Translucency alone lifts the ground, which is the whole job. */
  background:rgba(251,248,241,.5);
  border-block:0;position:relative;
}
.section.alt::before,.section.alt::after{
  content:'';position:absolute;left:0;right:0;height:1px;
  background:linear-gradient(90deg,transparent,rgba(148,102,27,.20) 16%,
                             rgba(148,102,27,.20) 84%,transparent);
}
.section.alt::before{top:0}
.section.alt::after{bottom:0}

.eyebrow{
  display:inline-flex;align-items:center;gap:8px;
  font-size:11.5px;font-weight:700;letter-spacing:.16em;text-transform:uppercase;
  color:var(--accent);background:var(--accent-soft);
  border-radius:100px;padding:6px 14px;margin:0 0 22px;
}
.eyebrow.plain{background:transparent;padding:0;margin-bottom:14px}
.eyebrow.gold{color:var(--gold);background:var(--gold-soft)}

/* ⚠⚠ 500 → 600, AND IT IS ONE DECLARATION RATHER THAN SIX SELECTORS (George,
   2026-08-24: "make the titles of the following the same bold as the one on the
   features titles page: all at top of the compare. 1-4 on about. an AI forward
   approach. request a consultaion. the little things add up. every feature
   started as a problem").
   He named six headings on four pages, which between them are h1s, h2s and h3s
   — so the fix is the shared weight, not six overrides that would then have to
   be kept in step with each other every time a heading moved. `.section.deep
   .feature h3` is the reference he pointed at and it already declared 600; that
   declaration is now saying what the base says (listed as redundant, left in
   place rather than deleted unasked).
   ⚠ 600 IS A REAL WEIGHT, NOT A SYNTHESISED ONE. Cormorant Garamond is loaded at
   400/500/600/700 in every page's font link. Check that link before raising this
   to 700 — a weight missing from the URL gets faked, and at 74px a faked bold is
   obvious. */
h1,h2,h3{font-family:var(--display);font-weight:600;margin:0;letter-spacing:-.01em}
h1{font-size:clamp(42px,6.2vw,74px);line-height:1.02}
h2{font-size:clamp(32px,4.2vw,48px);line-height:1.08}
h3{font-size:24px;line-height:1.2}
.lede{font-size:19px;line-height:1.62;color:var(--muted);max-width:60ch;margin:22px 0 0}
.section-head{max-width:66ch;margin:0 0 52px}
.section-head .lede{margin-top:16px}
.rule{height:1px;background:var(--border);border:0;margin:0}

/* ── buttons ────────────────────────────────────────────────────── */
.btn{
  display:inline-flex;align-items:center;justify-content:center;gap:9px;
  font-family:var(--ui);font-size:11.5px;font-weight:700;
  letter-spacing:.1em;text-transform:uppercase;text-decoration:none;
  padding:14px 24px;border-radius:var(--radius);border:1px solid transparent;
  cursor:pointer;transition:filter .14s,background .14s,color .14s,border-color .14s;
  white-space:nowrap;
}
.btn-primary{background:var(--accent);color:#FBF8F1}
.btn-primary:hover{filter:brightness(1.1)}
.btn-secondary{background:transparent;color:var(--ink);border-color:var(--ink)}
.btn-secondary:hover{background:var(--ink);color:var(--panel)}
.btn-ghost{background:transparent;color:var(--ink);padding:10px 14px;letter-spacing:.08em}
.btn-ghost:hover{background:var(--accent-soft);color:var(--accent)}
.btn-sm{padding:10px 18px;font-size:12px}
.btn-arrow{font-size:14px;line-height:1;letter-spacing:0}
.btn-row{display:flex;flex-wrap:wrap;gap:12px;margin-top:34px}

.textlink{
  display:inline-flex;align-items:center;gap:7px;color:var(--accent);
  font-weight:600;font-size:13.5px;text-decoration:none;
  border-bottom:1px solid transparent;transition:border-color .14s;
}
.textlink:hover{border-bottom-color:var(--accent)}

/* ── nav ────────────────────────────────────────────────────────── */
/* ⚠ NO `backdrop-filter` HERE — do not put it back (2026-08-22). It was
   `saturate(1.4) blur(12px)`, and a blurred backdrop on a sticky element is
   the classic cause of the stuck coloured line macOS GPUs paint along an edge
   and then leave on screen after the page is gone (George was seeing one, and
   it survived opening a new tab — which is what a compositor artefact does
   and a page artefact cannot). It also forced the nav onto its own GPU layer
   on every page of the site to blur a background that is very nearly opaque
   already. The background is simply more opaque now; over the paper it is
   indistinguishable, and it composites like ordinary paint. */
/* ⚠ AND THE FIX FOR THE SEAM IS NOT TO PUT IT BACK. When the hero became warm
   light, this bar — near-opaque `--paper` — turned into a flat grey band
   straight across the top of it: invisible for as long as the page was flat
   ivory, and the first wrong thing on the page the moment it was not. The
   answer is translucency WITHOUT a blur. Ordinary `rgba` paint composites
   normally and carries none of the risk above, the ground and the sweeps show
   through it, and it warms toward the light instead of sitting on it.
   The hub's cards DO use `backdrop-filter`, and that is not this case: the
   artefact above is specific to a STICKY element promoted to its own layer and
   left on screen. The cards are absolutely positioned inside the hero and go
   away with it. Watch for a stuck line anyway — it is cheap to check. */
/* ⚠⚠ THE BAR IS OPAQUE, AND IT IS THE SAME COLOUR ON EVERY PAGE. George,
   2026-08-23: *"don't let the hue of the hero page affect the top bar with the
   features / compare / about — that should stay the same colour as when we are
   on the features (the off white)."*

   IT WAS `rgba(240,233,219,.32)`, a 32% veil, and on nine of the ten pages that
   is harmless because what shows through is flat warm ground. On the HOME page
   the raking-light canvas runs behind the bar — put there deliberately in round
   5 so the nav would stop reading as a pale lid — and the consequence nobody
   priced is that the navigation then CHANGES COLOUR as the light drifts under
   it. A chrome element that shifts hue is not atmosphere, it is a bug you
   cannot name.

   #EBE2CF is not invented: it is the composited colour the old translucent bar
   already rendered on `/about`, sampled off a screenshot — (235,226,207). So
   the other nine pages are pixel-identical and only the home page changes,
   which is exactly the ask.
   ⚠ IT MUST STAY OPAQUE. Any alpha here and the canvas is back through it. */
.nav{
  position:sticky;top:0;z-index:50;background:#EBE2CF;
  border-bottom:0;
}
/* the rule under it fades at both ends, like every other separator this round */
.nav::after{
  content:'';position:absolute;left:0;right:0;bottom:0;height:1px;
  background:linear-gradient(90deg,transparent,rgba(148,102,27,.24) 12%,
                             rgba(148,102,27,.24) 88%,transparent);
}
.nav-in{display:flex;align-items:center;gap:20px;height:var(--navh)}
.nav-brand{
  display:flex;align-items:center;gap:11px;text-decoration:none;
  font-family:var(--display);font-weight:600;font-size:25px;letter-spacing:.02em;
  margin-right:auto;
}
/* ⚠⚠ IT INHERITS ITS SIZE AND FAMILY RATHER THAN DECLARING THEM, and that is
   the whole point of the rule (George, 2026-08-24: "where it says built for
   independent landlords in the top left, make it the same size font as amlak
   and center it"). It was 12.5px in the UI sans beside a 25px serif wordmark —
   two different typefaces at half the size, which reads as a caption stuck to a
   logo rather than as part of one.
   Declaring nothing means it is 25px serif in the nav and 24px serif in the
   footer, matching each brand it sits in WITHOUT a second number to keep in
   step. Add a `font-size` here and the footer silently stops matching.
   ⚠ THE WEIGHT IS THE ONLY THING THAT SEPARATES THEM NOW — 400 against the
   brand's 600. Same face, same size, lighter: that is a lockup. Matching the
   weight too would make "Amlak · Built for independent landlords" read as one
   long company name.
   ⚠ "CENTER IT" IS SATISFIED BY THE MATCHED SIZE, not by a new rule.
   `.nav-brand` and `.foot-brand` are both `display:flex; align-items:center`,
   so two runs of the same font at the same size now share a baseline. At 12.5px
   against 25px, flex was centring two boxes of very different heights and the
   small one floated visibly off the wordmark's optical centre.
   ⚠ HIDDEN BELOW 1180px NOW, NOT 900 — see the media query. At the old size it
   cost ~150px and cleared 900 comfortably; at 25px serif it is ~350px, which is
   enough to push the nav's wrap point up past two common laptop widths. */
.nav-tag{
  /* ⚠ `--proof`, NOT `--muted` — measured at 3.03 : 1 against the bar over the
     lit hero, where AA wants 4.5. Same fault and same fix as the cards' proof
     line; see the note at `.hcard .sub`. */
  font-family:var(--ui);font-size:12.5px;font-weight:500;color:var(--proof);
  letter-spacing:0;white-space:nowrap;margin-left:2px;
}
/* ⚠ THE NAV'S COPY ONLY. George said "in the top LEFT", and the footer's copy
   of this span has to stay small for a structural reason rather than a
   stylistic one: `.foot-in` is a four-column grid and the brand cell also holds
   a 38ch paragraph. A 350px inline tagline in that cell widens column one and
   squeezes the three link columns beside it. Two sizes for one class is worth a
   comment; one broken footer is not worth the consistency. */
.nav-brand .nav-tag{
  font-family:inherit;font-size:inherit;font-weight:400;
}
/* ── the trademark notice ─────────────────────────────────────────
   `.55em` and a killed letter-spacing so it reads as a mark ON the wordmark
   rather than as a character IN it. Relative sizing on purpose: the same one
   rule covers the 25px nav brand, the 24px footer brand and a 15.5px sentence
   of body prose, with no second number to keep in step.
   `line-height:0` is DEFENSIVE, and measured — not load-bearing. A raised
   superscript with normal leading can grow the line box that contains it, which
   in the nav would push the bar taller and drift the wordmark's optical centring
   (the fault the note at `.nav-tag` above describes). Measured on the live pages
   2026-08-25 it changes nothing at either size in use: the nav brand is 40px with
   it, without it, and with the `.tm` removed entirely, because the 30px mark and
   its padding set that height, not the text. Kept because it costs nothing and
   the guarantee stops mattering the moment someone raises the mark's size — but
   do NOT cite it as the reason the bar is 40px. */
.tm{font-size:.55em;letter-spacing:0;vertical-align:super;line-height:0;font-weight:400}
/* ⚠⚠ THE GAP BEFORE THE ™ IS A FLEX GAP, NOT A SIDE BEARING (George, 2026-08-26:
   "bring in the TM a bit closer to the AMLAK"). This is worth stating because the
   obvious diagnosis is wrong and costs a round: `.tm` already sets
   `letter-spacing:0`, and there is no whitespace in the markup — the wordmark is
   written `>Amlak<sup class="tm">` with nothing between. The space is that
   `.nav-brand` / `.foot-brand` are `display:flex` with `gap:11px`, so the bare
   "Amlak" text node and the `<sup>` are TWO FLEX ITEMS and the lockup's own gap —
   the one spacing the mark from the wordmark — lands between a word and its
   trademark as well. Measured 9.91px at the nav's 25px.
   ⚠ SO THE PULL IS IN px AND MATCHES THAT GAP, not in em. -9.5 of the 11 leaves
   ~1.5px of air, which is what a ™ wants: flush against the K reads as a typo.
   If the lockup gap ever changes, this number changes with it — they are a pair.
   ⚠ SCOPED TO THE TWO LOCKUPS, and that is the whole reason it is not on `.tm`
   itself. The ™ also appears in running prose (compare.html and four others),
   where there is no flex gap to cancel and a -9.5px margin would jam it into the
   preceding word. Only a flex context has the fault, so only a flex context gets
   the fix. */
.nav-brand .tm,.foot-brand .tm{margin-left:-9.5px}
/* ── THE MARK IS GLASS TOO ────────────────────────────────────────────
   George, 2026-08-23: "can you make the logo, both in the top left corner and
   in the middle, a frosted glass green similar to the boxes just so it fits
   with the theme?"

   Same construction as `.hcard a` — a translucent tint, a real blur, a lit top
   edge and a warm underside — only the tint is `--accent` instead of ivory. It
   is the one green thing in a hero full of gold, so it has to belong to the
   same material or it reads as a sticker.

   ⚠ THE ARTWORK HAD TO CHANGE FOR THIS, see `site/mark.svg`. favicon.svg paints
   its own opaque green tile, so frosting the box behind it did nothing at all
   until the tile came out of the file.

   ⚠⚠ THE TINT IS NOT `--accent`, AND THAT IS THE WHOLE TRICK. A translucent
   tile composites with what is behind it, and what is behind this one is a WARM
   GOLD ground. `--accent` (#5C6B3C) at .42 over that lands on **(159,145,89)** —
   measured, not guessed — which is an olive khaki, not the brand green. Tinting
   with the brand colour is exactly how you stop having the brand colour.
   So the tint is a DEEPER, more saturated green than the mark has ever been,
   chosen so that the RESULT is the brand green rather than the INPUT being it.
   Measured off the composited tile faces, as shipped:
       hub core  (101,113,72)
       nav mark  (104,116,74)
   against `--accent` (92,107,60) — a shade lighter and warmer, which is what
   sitting on gold light does and is the point. If the ground's warmth ever
   moves this has to be re-derived: it is the solution to an equation, not a
   colour anybody picked.

   ⚠ AND THE ARCH HAD TO BE MEASURED, because the first pass shipped a comment
   claiming 5.6 : 1 that was never checked and was really **2.97**. Ivory arch
   on the composited tile, sampled off real screenshots:
       accent .42, blurred → 2.97 : 1   (washed out, and olive)
       accent .62, blurred → 3.76 : 1
       this, nav mark      → 4.73 : 1
       this, hub core      → 4.94 : 1
   A logotype is exempt from WCAG's contrast rules; a logo you have to look
   twice at is still a bad logo.

   ⚠⚠ AND IT IS OPAQUE AGAIN AS OF THE ROUND AFTER THAT. George, 2026-08-23:
   *"I want to keep the dark green on the logo but make just it frosted/glossy —
   it shouldn't be transparent though, give it a texture."* So the mark is
   `--accent` at full strength, and "frosted glass" is carried by the three
   things that actually make a surface read as one, none of which is
   transparency:
     • a TEXTURE — a fine fractal-noise tile, the sandblasted tooth of frosted
       glass. It is a data-URI `feTurbulence`, rasterised once by the browser
       and then just a repeating fill.
     • a GLOSS — one specular highlight raked across the top-left, in an
       `::after` so it lies OVER the arch the way a reflection lies over
       everything behind the pane, not just over the green.
     • a BEVEL — lit top edge, shadowed underside.
   Everything below about compositing the tint against the gold ground is
   therefore history now, and is kept because it is the reason the answer is
   "opaque plus texture" rather than "a bit less alpha".

   ⚠⚠⚠ AND THERE IS NO `backdrop-filter` HERE, WHICH IS THE OPPOSITE OF WHAT
   THIS RULE SAID FOR AN HOUR. Both marks shipped with one, and it cost the
   whole frame budget. Measured on the real page, rAF intervals, no readback:

       everything on (11 cards @20px + these 2 marks)   16.7 ms   80/179 frames over 20 ms
       the 2 marks off, cards still @20px                9.3 ms   38/179
       the 11 CARDS off, these 2 marks on               25.0 ms  117/179   ←
       marks off, cards at 12 / 14 / 16 / 20px           8.4 ms    0/179   (all four)

   TWO TINY TILES COST MORE THAN ELEVEN BIG CARDS, and the eleven cards are free
   at ANY radius. Both of these sit directly over the canvas that repaints sixty
   times a second — one of them inside the STICKY nav, which is the same element
   with the documented 2026-08-22 macOS artefact — so their backdrop has to be
   re-sampled and re-blurred every single frame, and the sticky layer has to be
   re-composited with it. The eleven cards do not pay that because whatever
   Chrome does with them, it is not this.

   ⚠ AND DROPPING IT COSTS NOTHING TO LOOK AT, which is the only reason it is
   allowed. What is behind these tiles is a SMOOTH GRADIENT — there is no detail
   in it for a blur to frost. Frozen-light A/B of the hub core, blur on vs off,
   showed max 66/255; that difference is the `saturate(1.4)` leaving with the
   filter, not the blur, and it is paid back in the tint below. "Frosted" here
   was a technically-true label on an effect nobody could see, charged at 60% of
   the frame budget.

   THE GLASS IS STILL GLASS: translucency (the ground genuinely shows through),
   a lit top edge and a shadowed underside. Those are what read as a pane. */
/* ⚠ ONE TEXTURE, DEFINED ONCE, USED BY BOTH MARKS. `feTurbulence` at a high
   baseFrequency is fine grain; the `<feColorMatrix>` flattens it to a grey
   alpha ramp so it darkens and lightens the green underneath rather than
   tinting it. Inline SVG in a data URI means no request, no build step and no
   binary in the repo — and the browser rasterises the filter once, so it costs
   a texture upload rather than a per-frame filter.
   ⚠ IT IS A `--var`, NOT A COPY. The nav mark and the hub core must be the same
   material; two data URIs would drift the moment one was tuned. */
:root{
  --frost:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='90' height='90'%3E%3Cfilter id='n'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='.9' numOctaves='3'/%3E%3CfeColorMatrix type='saturate' values='0'/%3E%3C/filter%3E%3Crect width='90' height='90' filter='url(%23n)' opacity='.42'/%3E%3C/svg%3E");
}
/* ⚠ DARK GREEN MATT — AND IT IS A DIFFERENT MATERIAL FROM THE CARDS, ON
   PURPOSE. George asked for both in one sentence and drew the line himself
   (2026-08-23): *"dark green MATT for the logo and regular matt GLASS finish on
   the cards."* So:

     the cards   matt GLASS — transparent, smooth, you see the light move behind
     the mark    a matt SURFACE — near-opaque, and TEXTURED, because he asked for
                 that explicitly: "it shouldn't be transparent though, give it a
                 texture"

   which is why `--frost` lives here and no longer on the card. A visible grain
   needs a dark ground to read as a finish; on a near-white panel it reads as
   card stock, and that was half of what kept looking wrong.
   ⚠ THE TINT IS DARKER THAN `--accent`, not equal to it: a semi-transparent
   tile composites toward whatever is behind it, so tinting at `--accent` over a
   light ground yields something LIGHTER than `--accent`. Starting from a deep
   green lands the result on the dark green he asked to keep.
   ⚠⚠ AND ON 2026-08-24 THE A BECAME GLASS TOO. George: "it's not giving me
   that TRANSPARENT GLASS (that's what I want) matt look for the cards AND THE
   A." That reverses the "it shouldn't be transparent" from two rounds earlier,
   and it is his to reverse — but the texture he asked for in the same breath
   STAYS, because a dark pane is the one surface on this page that can carry a
   visible grain without reading as card stock.

   .80 → .58 over two rounds, on a deeper tint, so the light behind it genuinely
   moves through the green — plus the
   same three-edge rim as the cards in a green key rather than a white one: a
   near-white rim on a dark pane is jewellery, not glass.
   ⚠ THE FLOOR IS THE ARCH, NOT TASTE. The mark is an ivory `<path>` cut out of
   this green, so every point of transparency takes contrast off the logo
   itself. Measured at every step: .56 on the ORIGINAL tint gave 2.63 : 1 and was
   rolled back; the shipped pairing measures 3.56 : 1 here and 3.25 : 1 on the
   core. **3 : 1 IS THE FLOOR** — below it the arch greys out against a bright
   sweep and the mark stops being a mark.
   ⚠ AND THE HUB CORE HAS A SECOND FLOOR — it is the only element left on screen
   mid-portal, over the veil. See `.hub-core`. */
/* ⚠ `.sv-mark` JOINED THIS RULE ON 2026-08-25 (George: "change the way the A
   looks everywhere to the way it currently looks on the features page"). It is
   the Amlak tile inside Compare's stack picture and it was the last on-page mark
   drawn any other way — flat `--accent`, no border, no inner light, no gloss.
   It overrides only its own size (34px) further down; everything that makes this
   a surface rather than a swatch is here, once.
   ⚠ THE FAVICONS ARE NOT IN THIS SET AND MUST NOT JOIN IT. `favicon.svg` paints
   its own opaque rect and cuts the doorway with a second green path, because
   there is nothing behind a browser tab to frost. */
.nav-mark,.sv-mark{width:30px;height:30px;border-radius:var(--glass-r-sm);position:relative;
  /* ⚠ "make the green glass more transparent and
     reflective of the rakes."
     ⚠ THE ALPHA COMES DOWN AND THE TINT GOES DARKER AT THE SAME TIME, and that
     pairing is the only way to get both things he asked for. Alpha alone was
     tried at .56 and took the ivory arch to 2.63 : 1 — a washed-out logo. A
     semi-transparent tile composites toward what is behind it, so starting from
     a DEEPER green (22,34,10 rather than 44,58,26) lands the same darkness at a
     much lower alpha: more of the rake gets through, and the arch keeps its
     ground. Same equation as deriving the tint from the wanted result.
     `contrast(1.4)` is harder than the cards' 1.2 because a dark pane flattens
     what passes through it more, and `color-dodge` works off what survives. */
  /* ⚠⚠ .58 → .88 GREEN, AND THE FROST TEXTURE IS OFF WITH IT (George,
     2026-08-25, screenshot from Safari: *"the top left corner still looks
     really weird"* — the mark was a green tile crawling with bright
     yellow-green speckle).
     `--frost` is an feTurbulence noise tile laid on with
     `background-blend-mode:soft-light`. Soft-light is a CONDITIONAL curve —
     it lightens where the blend layer is above 50% grey and darkens below —
     so over a nearly-black green every light grain of that noise is thrown
     a long way up while the dark grains barely move. The result is not a
     texture, it is glitter, and it got worse the moment the green went
     opaque because there was no longer any gold behind it to average the
     grains out. It was tuned at .58 against moving light and never
     re-checked against a solid pane.
     ⚠ SO THE TILE GOES, NOT ITS ALPHA. Turning the noise down leaves the
     same speckle, quieter; the blend mode is what makes single pixels jump.
     ⚠ `--frost` STILL HAS OTHER CALLERS — grep before deleting the token. */
  background:rgba(24,36,12,.88);
  background-size:90px 90px,auto;
  background-blend-mode:soft-light,normal;
  -webkit-backdrop-filter:blur(16px) saturate(1.3) contrast(1.4);
  backdrop-filter:blur(16px) saturate(1.3) contrast(1.4);
  /* ⚠⚠ FOUR VALUES CAME ACROSS FROM THE APP'S `.brand-mark`, 2026-08-26 (George: "the
     glassy look with the white halo as we see on the app in the top left corner" —
     asking for it in every place the mark appears).
     THE GLASS ITSELF WAS NEVER THE GAP. The three inset edge-lights and the 112deg
     `::before` sweep were invented HERE and copied INTO the app; both files have had
     them all along. What differed was four numbers, and they are what made the app's
     mark look raised while these sat flat:
       rim         .14 -> .44   the halo, and the reason it was invisible here
       top light   .30 -> .40   the lit top edge — half of "depth", the other half
                                being a shadow cast below
       lift        none -> 0 2px 6px -1px   these three tiles had NO outer shadow at all
       ring        none -> the new 1px dark ring, outside the rim
     ⚠ THE LIFT IS THE APP'S `--mark-lift` VALUE, NOT `--shadow-lift`. The obvious move is
     to reach for the token already in this file, and it is wrong: `--shadow-lift` is
     `0 28px 60px -30px`, sized for a CARD and for the 68px `.hub-core`. Under a 30px tile
     a 60px blur is not a lift, it is a smear starting well below the mark. Small tile,
     tight shadow — 6px blur, 2px down.
     ⚠ THE RING IS NOT DECORATION, IT IS WHAT MAKES THE RIM WORK ON A LIGHT GROUND.
     A halo is a bright ring with dark on BOTH sides. Against the nav bar the white rim
     alone measures 2.51 : 1 — light on its outside — so it dissolves instead of ringing.
     The dark ring supplies the missing outside: 3.32 : 1 against the rim, 8.34 against
     the bar. Same declaration is inert on the app's dark rail (1.35), which is why one
     value serves both and there is no per-surface branch.
     ⚠ THE TINT AND THE `backdrop-filter` ABOVE ARE UNTOUCHED. Two rounds of tuning sit
     behind that .88/blur/contrast pairing; none of this round's four values interact
     with it. */
  border:1px solid rgba(255,255,255,.44);
  box-shadow:0 0 0 1px rgba(24,36,12,.85),
             0 2px 6px -1px rgba(6,12,3,.55),
             inset 0 1.5px 0 rgba(226,236,208,.40),
             inset 1px 0 0 rgba(226,236,208,.14),
             inset 0 -1px 0 rgba(12,20,6,.28);
  display:grid;place-items:center;overflow:hidden;flex:none}
.nav-mark img{width:100%;height:100%}
/* ⚠ the green pane's reflection.
   George, 2026-08-24: "I want the rakes to reflect and shine off the green A
   in the centre, similar to the way they do on the cards."

   ⚠ NOT `overlay`, and that is forced by the base: overlay resolves toward
   SCREEN on a light base and toward MULTIPLY on a dark one, so the exact rule
   that brightens the ivory cards would DARKEN the green marks.

   ⚠⚠ AND NOT `screen` EITHER, WHICH IS WHY THE MARK WAS NOT REACTING. Screen is
   `1-(1-a)(1-b)` — on a dark pane that is very close to plain ADDITION, roughly
   `alpha x 214` levels whatever is behind it. It made the mark permanently a
   bit lighter; it did not make it CATCH anything. That is the whole of George's
   note, and turning the alpha up only made the constant bigger, which is what
   took the ivory arch to 2.15 : 1 an hour earlier.

   `color-dodge` is `b / (1 - a)` — the result depends on the BASE, steeply. Over
   flat paper the green under the streak is dark and the quotient barely moves;
   when a sweep comes through the glass the base rises and the streak FLARES.
   That is literally shining off the green rather than sitting on it.

   ⚠ DODGE BLOWS OUT FAST, so the alpha is a third of what `screen` carried and
   the ramp is shorter. The floor is the ivory arch: it is cut OUT of this green,
   so every level the streak adds comes off the logo's own contrast. Measured at
   the worst frame, not assumed, and rolled back to `plus-lighter` if it cannot
   hold 3 : 1.

   ⚠ WHAT THIS CANNOT DO, stated so nobody tunes for it: the core is 62px and a
   sweep is hundreds of px across, so the rake barely VARIES across the tile. No
   blend mode can show a highlight travelling over something smaller than the
   light hitting it. Dodge buys FLARE — the whole mark brightening as light
   arrives — not TRAVEL. Travel needs site.js sampling the rake canvas, and
   ⚠ `getImageData` in a rAF loop demotes the canvas to CPU (it produced a fake
   15fps on this project once already).
   ⚠ `inset:0` + `border-radius:inherit` rather than `overflow:hidden` — the hub
   core CANNOT clip (it would delete `.hub-ring`, which sits at inset:-7px).
   ⚠ And the mark itself has to be lifted above it, or the reflection paints
   over the arch instead of over the glass around it.
   ⚠⚠ THE PEAK IS .20, DOWN FROM .34, AND THAT NUMBER IS THE LOGO'S. `screen` on
   a dark pane ADDS roughly alpha x 214 levels at the streak's brightest point —
   at .34 that is ~73 levels of lift, which took the green under the streak from
   (120,127,99) to (146,153,124) and the ivory arch on it from 3.95 : 1 down to
   2.15 : 1. It looked like the transparency doing that and it was not: the
   alpha had barely moved. A highlight on a mark is bounded by the mark still
   reading as the mark. */
/* ⚠⚠ `color-dodge` → `screen`, AND THE ALPHAS COME DOWN WITH IT (2026-08-25).
   Dodge is `b / (1 - a)`: it divides by the inverse of the blend layer, so its
   output is unbounded and it blows out the moment the BASE gets brighter. That
   was an acceptable gamble when the base was 42% travelling gold seen through
   glass — the note above explains why it was chosen, and "DODGE BLOWS OUT FAST"
   was written there at the time. It is not acceptable now: the base is an opaque
   green, this pseudo-element sits directly on the frost grain, and dodge turned
   each light grain into a blown pixel. Between them the two made the speckle in
   George's Safari screenshot.
   ⚠ `screen` IS `1-(1-a)(1-b)` — BOUNDED, and it cannot exceed white however
   bright the base gets. On a dark pane it is close to addition, which is exactly
   the soft edge-light this is for.
   ⚠ WHAT IS LOST, SO NOBODY RE-ADDS IT BY ACCIDENT: dodge FLARED — the whole
   mark brightening as a sweep arrived behind it. That effect needed the pane to
   be see-through, and the pane is not see-through any more (see `.hcard a`).
   Putting dodge back without also turning the green back down restores the
   glitter and none of the flare. */
.nav-mark::before,.sv-mark::before,.hub-core::before{
  content:'';position:absolute;inset:0;border-radius:inherit;
  z-index:0;pointer-events:none;
  /* ⚠⚠ THE FAR-END GLINT IS GONE, AND IT IS WHAT GEORGE SAW (2026-08-25, from
     Safari: *"there's a small line under the A in the center, take it out"*).
     This gradient used to carry a second bright pair at 92% / 100%. At 112deg
     the far end of the axis lands along the BOTTOM of the tile, so those stops
     drew a pale band right under the arch — visible at 3x in Chrome too once
     it was looked for, which is how it was confirmed rather than guessed.
     It was a reflection when the pane was see-through and there was moving
     light behind it to justify a second highlight. There is not any more (see
     `.hcard a`), so on a solid tile it is simply a line across the logo.
     ⚠ THE LEADING EDGE-LIGHT STAYS. The 0%/22% pair is the top-left rim
     catching ambient light, which is what stops the tile reading as flat paint,
     and it sits on an EDGE rather than across the middle of the mark. */
  background:linear-gradient(112deg,
    rgba(236,246,214,.16) 0%,
    rgba(236,246,214,.05) 22%,
    rgba(236,246,214,0)   44%,
    rgba(236,246,214,0)   100%);
  mix-blend-mode:screen;
}
.nav-mark img,.sv-mark img,.hub-core img{position:relative;z-index:1}

/* ⚠⚠ THE HOVER SHEEN — TWIN OF `.brand .brand-mark::after` IN src/App.css (George,
   2026-08-26: "when i hover over the amlak logo a glossy sheen ripples through it kind
   of like the rakes we have"). Same keyframes, same tint, same 820ms; move one and
   move both, or the product and the page that sells it shine differently.

   ⚠ `.nav-brand` ONLY, AND THAT IS THE WHOLE SCOPE DECISION. George asked for it "only
   when it appears in the top left", and `.nav-mark` is a SHARED surface — the header
   lockup, the FOOTER lockup (`.foot-brand`, same class) and `.sv-mark` in Compare's
   stack picture all wear it. Selecting on `.nav-mark` would light up three marks on one
   page, two of them nowhere near a top-left corner. The hub core is excluded for a
   second reason as well: it already has the real rakes moving behind it.

   ⚠ IT IS A THIRD LAYER. `::before` is the static edge-light at z-index 0 and the img
   sits at 1, so the sweep takes 2 and passes OVER the arch. `overflow:hidden` on the
   shared tile rule is what clips it to the corner.
   ⚠ `mix-blend-mode:screen` so it brightens rather than veils — a white overlay at this
   opacity turns the frosted green milky. The tint is the arch ramp's own highlight, not
   white, for the same reason the rakes are gold. */
@keyframes brand-sheen{
  0%   {opacity:0;transform:translateX(-120%) rotate(14deg)}
  14%  {opacity:1}
  86%  {opacity:1}
  100% {opacity:0;transform:translateX(300%) rotate(14deg)}
}
.nav-brand .nav-mark::after{
  content:'';position:absolute;z-index:2;pointer-events:none;
  top:-45%;bottom:-45%;left:0;width:36%;opacity:0;
  transform:translateX(-120%) rotate(14deg);
  mix-blend-mode:screen;
  background:linear-gradient(90deg,
    rgba(240,226,190,0)    0%,
    rgba(240,226,190,.10) 26%,
    rgba(240,226,190,.62) 50%,
    rgba(240,226,190,.10) 74%,
    rgba(240,226,190,0)  100%);
}
.nav-brand:hover .nav-mark::after,
.nav-brand:focus-visible .nav-mark::after{animation:brand-sheen 820ms cubic-bezier(.32,.06,.24,1)}
@media (prefers-reduced-motion:reduce){
  .nav-brand:hover .nav-mark::after,
  .nav-brand:focus-visible .nav-mark::after{animation:none}
}
.nav-links{display:flex;align-items:center;gap:4px}
.nav-links a{
  text-decoration:none;font-size:11.5px;font-weight:600;letter-spacing:.1em;
  text-transform:uppercase;color:var(--ink);padding:9px 13px;border-radius:var(--radius);
  transition:background .14s,color .14s;
}
.nav-links a:hover{background:var(--accent-soft);color:var(--accent)}
.nav-links a[aria-current="page"]{color:var(--accent);background:var(--accent-soft)}
.nav-cta{display:flex;align-items:center;gap:10px;padding-left:8px}

/* ── hero ───────────────────────────────────────────────────────── */
.hero{padding:88px 0 0;position:relative;overflow:hidden}
/* ⚠ THE GRID IS CENTRED, NOT ANCHORED TOP-LEFT (George, 2026-08-23: "some of
   the lines on the left don't look the same as the right in relation to the
   branches"). Left at the default `0 0`, the 64px lattice starts at the box's
   left edge, so where a line falls relative to the mark depends entirely on
   how wide the window happens to be — at most widths the left branches sit on
   a different part of a cell from their right-hand twins, and a diagram built
   to mirror to 0.00px reads as crooked because the paper underneath it is.
   At `50% 50%` the tiling is anchored to the centre, so the lines land at
   ±32, ±96, ±160 … from the vertical centreline at EVERY width, and the mark
   sits in the middle of its own cell. The branches are mirrored about that
   same centreline, so the two now agree by construction rather than by luck. */
.hero::before{
  content:'';position:absolute;inset:0;pointer-events:none;
  background-image:linear-gradient(var(--hair) 1px,transparent 1px),
                   linear-gradient(90deg,var(--hair) 1px,transparent 1px);
  background-size:64px 64px;
  background-position:50% 50%;
  mask-image:radial-gradient(120% 78% at 68% 22%,#000 0%,transparent 72%);
  -webkit-mask-image:radial-gradient(120% 78% at 68% 22%,#000 0%,transparent 72%);
  opacity:.85;
}
/* ⚠⚠ ON ABOUT AND COMPARE THE GRID RUNS THE FULL WIDTH AND STOPS UNDER THE
   BUTTONS (George, 2026-08-24: "you can expand the background of the grid for
   compare and about to just right under the buttons, see how it looks and get
   started"). The radial above is anchored at 68% 22% — top-RIGHT — which was
   built for the home page, where the diagram is on the right and the light
   needs to fall on it. On a two-column marketing hero it left the left half and
   the whole lower band bare, so a trace running there had no grid under it and
   read as a stray line rather than as light on a plat map.
   ⚠ `--grid-stop` IS WRITTEN BY `site.js` FROM THE MEASURED BUTTON ROW, not
   guessed here. The two columns are `align-items:center` and their heights
   differ per page and per width, so any hard-coded percentage is right on one
   page at one width. The fallback (70%) is what a page whose script never ran
   gets, and it is deliberately short rather than long: a grid that stops early
   looks intentional, one that runs past the copy looks broken.
   ⚠ THE SAME STOP CLIPS THE TRACE CANVAS. If this fade moves, the light and the
   grid it travels on part company. */
/* ⚠⚠ ONE MASK, DRAWN IN `site.js`, SHARED BY THE LATTICE AND THE LIGHT ON IT.
   `--hero-mask` is an inline SVG: a vertical fade for the bottom edge, with a
   blurred rounded rectangle cut out around every line of copy. It replaced a
   pair of composited radial gradients on 2026-08-25 because an ellipse cannot
   hug a rectangle — fit it to the box and the corners stay covered, grow it to
   reach the corners and the long axes overshoot by 41%. Both were shipped and
   both were wrong; the shape was the problem, not the numbers.
   ⚠ THE FALLBACK IS THE OLD LINEAR FADE, so a page whose script never runs still
   gets a grid that stops under the buttons rather than one that runs to the
   section below.
   ⚠ `mask-size:100% 100%` IS LOAD-BEARING. The SVG is authored at the hero's
   exact pixel size, and without this a data-URI mask is laid out at its
   intrinsic size and tiles. */
.hero:not(.hub-hero)::before{
  mask-image:var(--hero-mask,linear-gradient(180deg,#000 0,#000 70%,transparent 79%));
  -webkit-mask-image:var(--hero-mask,linear-gradient(180deg,#000 0,#000 70%,transparent 79%));
  mask-size:100% 100%;-webkit-mask-size:100% 100%;
  mask-repeat:no-repeat;-webkit-mask-repeat:no-repeat;
}
/* ⚠ `.gflow` (1,900 lines below) SETS THE SAME `mask-image` AT THE SAME
   SPECIFICITY, and must keep pointing at the same `--hero-mask`. If one of the
   two ever stops using the variable, the lattice and the light travelling on it
   clear different areas — which is the bug the shared variable exists to make
   impossible. */
.hero-in{position:relative;display:grid;grid-template-columns:minmax(0,1fr) minmax(0,1.06fr);
  gap:56px;align-items:center;padding-bottom:88px}
/* ⚠ ABOUT ONLY, AND IT IS THE PICTURE THAT NEEDS IT (George, 2026-08-24:
   "you can also make that bigger"). The day grid is 52 columns wide, so its
   cell size is one fifty-second of whatever column it is given — the only way
   to grow the dots is to grow the column. At 1.06fr the cells were 7.3px; at
   1.42 they are near 10, which is the difference between a texture and a
   calendar you can count.
   ⚠ NOT APPLIED TO COMPARE. `.stack-viz` is capped at 470px and would just
   sit in more whitespace, while the copy column would lose the width its
   nine-tool sentence needs. Same hero, two different pictures, two answers. */
/* ⚠ THE STACKED HERO: HEADLINE ACROSS, THEN COPY AND PICTURE SIDE BY SIDE
   (About only, 2026-08-24). In the two-column split the h1 had half the page
   and wrapped to four lines — a lot of vertical space for eight words. Across
   the full measure it is the two lines it was written as, and the block below
   it is the same two columns it always was.
   ⚠ `display:block` OVERRIDES `.hero-in`'s GRID rather than re-declaring it.
   The parent stops being the grid; `.hero-body` is. Leaving `.hero-in` as a
   grid and adding `grid-column:1/-1` to each child would work too and would
   break the moment somebody adds a third child.
   ⚠ `.hero-in.wide-art` IS GONE WITH IT — that rule widened the right column to
   grow the day grid, and a full-width `.hero-body` does the same job better.
   Do not put both back; they fight over the same column. */
.hero-in.stack-hero{display:block}
.hero-head{max-width:none;margin-bottom:34px}
.hero-body{display:grid;grid-template-columns:minmax(0,1fr) minmax(0,1.18fr);
  gap:56px;align-items:center}
/* ⚠⚠ THE WEIGHT IS DECLARED HERE BECAUSE THE ITALIC WAS BEING FAKED, and that is
   a defect rather than a taste call (George, 2026-08-25: "unbold the green
   italics of the title on compare and about").
   Every page loads `Cormorant+Garamond:ital,wght@0,400;0,500;0,600;0,700;1,500`
   — **italic at 500 ONLY**. Taking `h1,h2,h3` to 600 the round before therefore
   took this `<em>` with it, to a weight that is not in the font URL, and the
   browser synthesised it by smearing the 500 italic. That thickened, slightly
   blurred green is what he was looking at.
   ⚠ SO THE FIX IS 500, NOT `1,600` IN THE FONT LINK. Adding the weight would buy
   a real face nobody asked for, on nine pages, at the cost of another font file
   on every one of them.
   ⚠ AND IT COVERS ALL THREE HEROES — index.html, about.html, compare.html — not
   the two he named. This rule is the only thing that styles a hero italic, and
   the cause is a missing font weight rather than a preference, so leaving the
   home page synthesised to honour a page list would be shipping a known
   rendering fault. One word reverts it if he wants index heavier. */
.hero h1 em{font-style:italic;font-weight:500;color:var(--accent)}
.hero .lede{font-size:20px;max-width:52ch}
/* Three CTAs in a 509px column needed 515px — six pixels, and the third button
   orphaned onto its own line. Trimmed here rather than globally so the buttons
   elsewhere on the site keep their proportions. Below ~1080px it wraps anyway,
   which is fine: at that width it wraps 2+1 by choice, not by six pixels. */
.hero .btn-row{gap:10px}
.hero .btn{padding-left:18px;padding-right:18px}
/* ⚠ `--muted`, NOT `--faint`. This is a sentence a visitor is meant to read —
   "Opens straight into a sample portfolio. Nothing in it is real data." — sitting
   directly on the warm ground, where `--faint` measured 2.09 : 1. */
.hero-note{margin:10px 0 0;font-size:13px;color:var(--muted);max-width:52ch;line-height:1.55}
.hero-note b{color:var(--muted);font-weight:600}

/* ── the hub hero (homepage only) ───────────────────────────────────
   The mark in the centre, the nine things Amlak does wired into it, the claim
   above and the two buttons below — George's brief, and the CoreShift
   reference, in this site's own tokens. The other pages keep the two-column
   .hero-in hero; everything below is scoped to .hub-hero / .hero-hub and
   touches none of it.

   ⚠ THE ASPECT RATIO IS THE COORDINATE SYSTEM. Locking the box to the SVG's
   1000×640 is the only reason a card at --ey:45.31% sits on the wire ending
   at y=290. Cap the WIDTH, never the height, or the drawing letterboxes and
   every card slides off its own wire. */
.hub-hero{padding-top:12px}
/* ⚠ THE HUB HERO HAS NO LATTICE ANY MORE (George, 2026-08-23, on the grid still
   showing in the transitions — and it was still here too). It used to re-mask
   `.hero::before`'s 64px plat map to sit under the diagram; the raking light
   buried it completely at intensity 1.9, so what was left was a grid nobody
   could see costing a full-viewport paint. Two grounds, one hero: the light IS
   the ground here now. The other pages keep theirs — they have no canvas, and
   the plat map is the only texture they have. */
.hub-hero::before{display:none}
/* ── fitting the whole hero in the first screen ──────────────────────
   George, 2026-08-22: "make it all fit on the first page so the user sees
   that part immediately." So the diagram is sized by what is LEFT of the
   viewport once the nav, the headline and the buttons have taken theirs —
   `--chrome` is that reservation, measured, and the box takes the smallest
   of the three limits.

   ⚠ THE HEIGHT LIMIT IS APPLIED TO THE WIDTH, never as a max-height. The box
   is an aspect box and its percentages are the coordinate system every card
   sits on; capping its height would letterbox the drawing and slide all ten
   cards off their own wires. Dividing the available height back out through
   the ratio shrinks both together and the mapping holds. The retired scroll-scrub
   diagram was built on the same rule and fell into the same trap.

   ⚠ AND THE CARDS SCALE WITH IT, in `cqw` off this container — a box that
   shrinks while its cards stay 296px wide pushes the outer branches straight
   off the paper. Every clamp below has a floor: the type stops shrinking
   before it stops being readable, which is the fault George called out two
   rounds ago, and below that floor the page simply gets a scrollbar. */
.hero-hub{
  /* nav + H1 + both gaps + the two buttons + the note + the padding under it.
     Measured against the rendered page at several window heights, not
     estimated — every one of those is a real box and the sum is what the
     diagram does not get. Change any of them and RE-MEASURE: dropping the
     "Built for landlords" eyebrow freed 49px, and until this came down with
     it the diagram was simply 49px smaller than it could be with no sign
     anything was wrong. */
  --chrome:312px;
  position:relative;aspect-ratio:1000/536;container-type:inline-size;
  /* ⚠ AND A FLOOR UNDER IT. Below about 730px of box the rows are closer
     together than a card is tall and branches start overlapping, because the
     type has already hit the bottom of its clamp and stopped shrinking with
     everything else. On a window short enough to reach that, the hero spills
     past the fold by a little — which is the right trade: overlapping
     branches are broken, a short scroll is not. ⚠ It went 700 → 800 when the
     eleventh branch arrived: below ~800 of box the rows close to less than a
     card apart. Measured, not guessed — lower it and re-measure.
     ⚠ AND IT IS THE FLOOR, NOT THE RATIO, THAT DECIDES WHERE THE HERO STOPS
     FITTING. Above it the box shrinks with the window and the fold is never
     reached; at it the box is stuck at 800 × 536/1000 = 429px tall and the
     hero is a fixed 741. Every further pixel has to come out of `--chrome` or
     out of the box's height — never out of this, which is the one thing
     protecting the branches from each other.
     ⚠ AND MEASURE AT THE FLOOR, NOT AT YOUR OWN WINDOW. At 800px of box the
     type stops shrinking and two titles wrap to two lines, so those two cards
     stand 98.6 units instead of 71.5. That is not a detail: it sets the ring's
     row spacing at 100 (100 − (98.6+79.6)/2 = 10.9 units of air, and 95 would
     have them touching), and four
     rows of that spacing are most of the box's height. Shorten those two
     titles — "Insurance and service contracts", "Rent ledger, month by month"
     — and the rows could close to ~88, the box to ~488, and the hero to about
     702. None of it is visible at 1440×980, where every card measures 71.5 and
     everything looks roomy. */
  width:max(800px,min(100%,1120px,calc((100vh - var(--chrome)) * 1000 / 536)));
  margin:0 auto;
}
.hub-wires{position:absolute;inset:0;width:100%;height:100%;display:block;overflow:visible}
/* ⚠ RESTING dashoffset IS 0 — DRAWN. The entrance keyframe animates FROM -1
   (in from the core) back to this; kill the animation and the diagram is
   simply complete. Never move the hidden state into this base rule. */
/* ⚠ THE SPOKES ARE BRASS AND THEY FADE — George, 2026-08-23: "the spokes look
   generic and unfitting with our luxury theme," and the same "gold foil" pick
   that took the drawings takes these. Two things were wrong and only one was
   the colour:

   1. THE INK WAS COOL ON A WARM PAGE. `--border` is rgba(27,24,19,.14), a
      NEUTRAL near-black, and on gold light that reads as pencil on tracing
      paper. Every other rule on this site — the nav, the section edges, the
      eleven feature separators — is already rgba(148,102,27,·). The spokes
      were the last cool line left on it.
   2. THEY DID NOT FADE. A line of constant weight is plotted; one that is
      strongest at the mark and gone by the card is radiating, which is the
      thing the diagram is trying to say and which the outward pulse was left
      saying on its own.

   ⚠ ONE radialGradient IN USER SPACE ON THE MARK does all eleven — see the
   <defs> in index.html. Eleven per-path gradients would have to agree with each
   other by hand and would stop agreeing the first time a spoke moved; one
   centred on the hub agrees by construction. */
.hwire{fill:none;stroke:url(#spoke);stroke-width:1.4;stroke-dasharray:1;stroke-dashoffset:0}
/* ⚠ THE BEADS CHANGE OBJECT BUT KEEP THEIR JOB. Eleven grey r=4 circles were
   the most generic thing in a diagram that already has the mark, its ring and
   eleven card corners — but `.hdot.lit` is how the cycle says which branch has
   the floor, so they cannot simply go. Smaller, brass, and the lit state is a
   FILL change rather than an opacity one: reduced motion sets `.hdot{opacity:1}`
   as a backstop, and carrying the state in opacity would light all eleven at
   once for exactly the readers who cannot see the cycle. */
.hdot{fill:rgba(148,102,27,.36);transition:fill .3s ease;r:2.8}
.hdot.lit{fill:#94661B}

/* ⚠ THE ACTIVE WIRE IS NOT COLOURED IN — do not add `.hwire.lit` back
   (George, 2026-08-22). Tinting the whole strand meant one solid accent line
   swinging around the mark as the cycle advanced, which read as a dial hand
   sweeping the diagram rather than as a branch taking its turn. The travelling
   pulse and the lit node say the same thing without the clock. */

/* ── the outward pulse ────────────────────────────────────────────────
   George, 2026-08-22: "can you make the branches kind of pulsate outwards
   from the amlak sign?" So the signal runs OUT of the mark, not into it — a
   short accent segment released from the core and travelling down each
   branch, staggered so the branches read as one radiating beat rather than
   as separate blips.

   ⚠ THE DIRECTION IS THE WHOLE POINT AND IT IS COUNTER-INTUITIVE. Every path
   is authored card→core, so the dash sits at the CARD when the offset is 0.
   Animating the offset upward walks the pattern backwards along the path,
   which moves the visible segment from the core outward. Flip the sign and
   the whole thing runs inward again with nothing to warn you.

   ⚠ THE DASH PATTERN MUST DIVIDE THE PATH, AND THE ANIMATION MUST ADVANCE
   EXACTLY ONE PERIOD OF IT. This was `.13 .87` — summing to 1.0, the whole
   path — so only one dash ever existed and the loop had to hide a jump from
   the card end back to the core. It hid it by fading out at 78% and back in
   at 12%, and that dark stretch IS the stutter George saw: "at the end of the
   pulse it kind of lags and does another pulse." At `.1 .4` the pattern
   repeats every 0.5 of the path, so advancing the offset by exactly 0.5
   lands on a render identical to the start: seamless, no fade needed, one
   segment arriving as the next leaves. Change either number and they must
   still sum to something that divides 1 — and the keyframe's end value must
   equal that sum, or the loop visibly jumps.

   ⚠ AND THE EASING IS `linear`. It was a cubic-bezier, which decelerated into
   the end of every cycle — the other half of the stutter. A stream at a
   constant rate is the only thing that reads as continuous.

   ⚠ IT IS ORNAMENT, AND IT RESTS INVISIBLE. Base opacity is 0 and the opacity
   lives INSIDE the keyframe, so `animation:none` under reduced motion leaves
   nothing behind — not even a stray dash frozen mid-wire. Never move the
   opacity up into the base rule to "simplify" it. */
/* ⚠ AND THE PULSE WENT BRASS WITH THEM, but it does NOT take the fade — it is
   a travelling light and has to stay legible the whole length of the branch, so
   it is `--gold` flat while the wire under it is the same gold at .58 falling to
   .08. That difference in alpha is what makes it visible against its own colour;
   putting the gradient on both would hide the beat inside the spoke. */
/* ⚠⚠ BACK TO `.1 .4` — A REPEATING PATTERN, NOT ONE DASH, and the reason is a
   bug this project has already shipped once. George, 2026-08-23: "go back to
   the original pulse that we had."

   The round trip that lived here used `.055 .945` — summing to the whole path,
   so exactly one dash existed on it. That was safe ONLY because the journey was
   a closed loop: it ended at the mark, where the pulse is supposed to arrive
   and leave anyway, so the wrap had somewhere honest to happen. On a BARE SPOKE
   there is no such point. A single dash reaches the card, vanishes, and
   reappears at the mark — and the fade that hides that jump IS the stutter
   George reported on 2026-08-22 ("at the end of the pulse it kind of lags and
   does another pulse"). `.13 .87` was rejected then for exactly this.
   `.1 .4` repeats every 0.5 of the path, so one segment arrives as the next
   leaves and the loop needs no fade at all.
   ⚠ CHANGE EITHER NUMBER AND THEY MUST STILL SUM TO SOMETHING THAT DIVIDES 1,
   and the keyframe's end value must equal that sum, or the loop visibly jumps.
   ⚠ AND THE PULSES ARE STILL SYNCHRONISED — see the note below. Reverting the
   PATH does not revert that; it was a separate ask and it stands. */
.hpulse{fill:none;stroke:var(--gold);stroke-width:2.4;stroke-linecap:round;
  stroke-linejoin:round;
  /* ⚠⚠ ABSOLUTE PIXELS, NOT `pathLength="1"` FRACTIONS (George, 2026-08-27: *"they look
     uneven… You see how they all reach the dot on the line at different times?"*).
     Every pulse used to carry `pathLength="1"` with `.1 .4`, so each one travelled half
     of ITS OWN path per cycle — equal in fraction, not in pixels. But the beads sit at a
     FIXED radius of 96 from the mark (index.html re-solved them onto r=96 and nobody
     re-solved the timing with them), so the fraction at which a dot sits is 96/L — and L
     runs 195.5 to 252.9 across the eleven spokes. Measured arrival:

       spokes 2 and 7 (the long top diagonals, L 252.87) ...... 1.672s
       the middle nine (L 205-213) ............................ 1.98-2.06s
       spoke 1 (the short vertical, L 195.48) ................. 2.164s

     0.49s of spread — 22% of the cycle, which is exactly the wrongness he saw. It also
     made the beads different LENGTHS (`.1` of a long spoke is a longer dash) and made
     them EMERGE from the mask at different times (r≈58 is 0.30 of the short spoke and
     0.23 of the long ones).

     In pixels the pattern and the offset are identical on every path, so every pulse
     lights its bead at the same instant whatever the spoke's length: synchronised BY
     CONSTRUCTION rather than by tuning, and immune to anyone re-authoring a curve.
     21/85 reproduces what `.1 .4` looked like on a typical 213-unit spoke. */
  stroke-dasharray:21 85;stroke-dashoffset:0;opacity:0;
  /* 2.2s → 1.7s → 3.4s → 2.2s. The whole excursion was arithmetic rather than
     taste: 1.7 was set against a path that had just tripled in length, which
     tripled the speed without anyone changing the number, and 3.4 was the
     correction. The path is a bare spoke again, so the original 2.2 means the
     original pace. ⚠ THIS IS THE ONE PLACE ROUND 8's "faster pulse" IS DROPPED —
     deliberately, because "go back to the original" says so. One number. */
  animation:hubRipple 2.2s linear infinite}
/* ⚠⚠ THE STAGGER IS GONE, AND THAT REVERSES 2026-08-22's ARRANGEMENT. George,
   2026-08-23: "those pulses are way too fast — they should be all at the same
   time too." The eleven used to leave 0.12s apart in mirrored pairs so the beat
   read as one wave radiating outward; fired together they read instead as the
   mark BREATHING, every branch answering at once, which is the thing he asked
   for and is arguably the truer statement about a hub anyway.
   ⚠ NOTHING REPLACES THIS RULE — do not re-add delays "for texture". Eleven
   animations with the same duration and no delay start together at page load
   and stay together for as long as the tab is open, so synchronised is free;
   the moment one of them gets an offset the whole set is a wave again.
   The old table is in the deploy log if the wave is ever wanted back. */
/* The branch the visitor is pointing at runs heavier — but NOT faster any more.
   Speeding one up while the other ten hold is exactly the stagger coming back
   through the side door, and it breaks the synchronised beat the moment the
   mouse lands anywhere on the diagram. */
.hpulse.go{stroke-width:3.6}
/* ⚠ .5 → 0, AND THE DIRECTION OF TRAVEL IS THE SIGN OF THIS (2026-08-23). A
   rising dashoffset walks the dashes toward the path's START; a falling one
   walks them toward its END. The paths were re-authored core → card when the
   portal transition was built, so the END is now the card — and this had to
   flip with them or the beat runs backwards, arriving at the mark instead of
   leaving it. It is still exactly one period, which is the seamlessness — the
   period is 106px now (21 + 85) rather than the .5 of a normalised path, and it
   MUST stay equal to the dasharray's sum or the loop visibly jumps. */
@keyframes hubRipple{
  0%{stroke-dashoffset:106;opacity:.62}
  100%{stroke-dashoffset:0;opacity:.62}
}

.hub-cards{list-style:none;margin:0;padding:0}
/* Positioned by the edge the wire meets: left cards hang their RIGHT edge on
   --ex, right cards their left — so a card can grow longer without crossing
   its own wire. translateY centres the card on the wire's y; the entrance
   uses the standalone `translate` property so the two compose (the same
   two-properties rule as every other moving thing on this site). */
.hcard{position:absolute;top:var(--ey);transform:translateY(-50%);
  width:clamp(196px,26.4cqw,296px)}
.hcard.l{right:calc(100% - var(--ex))}
.hcard.r{left:var(--ex)}
/* ⚠ THE ELEVENTH IS THE CROWN, and it sits ABOVE the ring rather than in it
   (George, 2026-08-23: "the idea I have is like the Lease review is the crown
   on top of the head"). That is what makes an odd branch count work: ten cards
   balance about the mark and the eleventh reads as an ornament on top, where
   eleven-in-a-ring can only ever look lopsided.
   It overrides `transform` (the base rule only shifts Y) and that is safe:
   the entrance animation uses the standalone `translate` property, so the two
   compose instead of one silently winning. Same rule as everything else here.
   ⚠ IT OVERLAPS THE TOP PAIR IN Y — 30 units apart, and a card is 80 tall —
   and clears it only horizontally, 340 against 368. That is the "closer" in
   George's brief, and it means the TOP PAIR'S distance out is load-bearing:
   bring it in past dx 142 and the crown lands on it. See relayout.py.

   ⚠⚠ `-100%`, NOT `-50%`, AND THAT IS THE WHOLE OF THE SPOKE FIX. George,
   2026-08-23: "I can still see the tops of the spokes under each card — make
   them end when they meet each edge." Measured: exactly ONE wire ran under a
   card, and it was this one, by **35.4 user units** — half the card's own
   height, straight up the middle behind the proof line. Invisible while the
   cards were opaque; obvious the moment they went transparent.

   THE CAUSE IS THIS RULE, NOT THE WIRE. Every other card hangs an EDGE on its
   anchor — `.l` its right, `.r` its left — so its spoke stops dead at the
   border by construction. The crown was CENTRED on its anchor, so the anchor
   was its middle and the wire was authored to arrive there. Centring on the
   tip is what made it read as an ornament rather than a twelfth branch, so the
   card's position is worth keeping exactly; what moves is which part of the
   card the anchor names. `-100%` hangs its BOTTOM edge there instead, and
   `--ey` moves from the old centre (9.89%) to that same bottom edge (16.56%)
   so the box lands in an identical place — verified pixel-for-pixel.

   ⚠ THE POINT OF DOING IT THIS WAY IS THAT IT SELF-CORRECTS. Shortening the
   wire by 35.75 units would have fixed today's screenshot and nothing else: the
   card's height is a `clamp()` and it grows again whenever the text wraps to
   another line, which puts the spoke straight back under it. Pinned by an edge,
   a taller crown grows UPWARD and its spoke still ends exactly on the border,
   at every width, with nothing measured at runtime.
   ⚠ SO ITS HEADROOM IS NOW LOAD-BEARING: the card grows up from y 17.3 toward
   the top of the box. Two lines of proof text fit with room to spare; a third
   would need the anchor lowered, not the rule changed. */
.hcard.c{left:50%;transform:translate(-50%,-100%)}
/* ── THE CARDS ARE GLASS ─────────────────────────────────────────────
   George, 2026-08-23: "the boxes don't fit — I can't really describe how but
   they just look out of place."

   WHAT THEY WERE: `--panel` at full opacity, a 1px `--border` and the tight
   `--shadow`. That is a correct card on flat ivory paper with a hairline grid
   under it, which is exactly what this hero used to be. On warm moving light it
   is eleven opaque slabs laid ON TOP of a photograph — nothing about them
   admits the light exists.

   WHAT THEY ARE: translucent, blurred, borderless except for a thin lit top
   edge, with a long warm shadow instead of a tight grey one. The sweep passes
   visibly BEHIND each card, which is the whole difference — they belong to the
   ground rather than sitting on it.

   ⚠ THE BLUR REALLY DOES SAMPLE THE CANVAS, and that was worth checking rather
   than assuming: `backdrop-filter` is disabled by any ancestor that forms a
   backdrop root, and `.hero-hub` sets `container-type:inline-size`. Measured
   live before this shipped — `inline-size` resolves to `contain:layout style
   inline-size`, with no PAINT containment, so it does not form one. If a future
   change adds `contain:paint` or an `opacity` to `.hero-hub`, every card here
   goes flat and this is the reason.
   ⚠ AND THE `-webkit-` PREFIX IS NOT OPTIONAL — Safari still needs it, and
   without it these become plain translucent panels on the one platform most
   likely to be looking. */
/* ⚠ THE ONE LESSON THAT SURVIVED FOUR ROUNDS OF THIS SURFACE: **the tint is
   what dilutes the hue**, and no amount of blur or chroma compensates for it.
   Near-white over the light is a wash that neutralises the gold before the blur
   ever reaches it, so turning the blur up just frosts the same grey. Everything
   else argued here across rounds 6-9 — a `::before` specular sheen, a top-edge
   bevel, `saturate(1.5)`, then `saturate(0.85)` — was superseded; the current
   build and the reasoning for it are in `.hcard a` below.

   ⚠ THE TINT IS A CONTRAST FLOOR, NOT A TASTE SETTING, AND THE WORST FRAME IS
   NOT THE ONE YOU EXPECT. At .38 the proof line measured **3.66 : 1** against
   AA's 4.5 and had to come back up. The frame that produces it is NOT a bright
   sweep core — a sweep makes the glass lighter and the text easier — it is a
   saturated golden MASS sitting behind the card, which is the constant wash and
   therefore the ordinary case rather than an edge one. Measure against the
   DARKEST glass pixel over a couple of sweep cycles, never the average.
   ⚠ AND SINCE 2026-08-23 THE FLOOR IS HELD BY `brightness`, NOT BY ALPHA. That
   is the whole reason the card can now sit at .30 and still measure better than
   it did at .38: lifting the backdrop toward white before the tint composites
   raises the panel's luminance without hiding what is behind it. Alpha was the
   only lever available while the filter had none, and it pays for contrast in
   the exact currency George kept asking to keep. */
.hcard a{
  position:relative;overflow:hidden;
  display:flex;align-items:center;gap:clamp(8px,1.05cqw,12px);text-decoration:none;
  /* ⚠⚠ MATT GLASS — TRANSPARENT, SMOOTH AND SLIGHTLY BRIGHTER THAN ITS GROUND.
     George, 2026-08-23, twice: *"I'm not looking for frosted, I'm looking for
     MATT finish, that's the word. Like semi-transparent"*, then *"that's not
     the matt I want — look up matt finishes and transparent glass, then
     replicate"*, with a dark glass-morphism reference.

     THE PREVIOUS PASS GOT THE WORD RIGHT AND THE NUMBERS WRONG, and looking the
     material up is what showed which numbers. Every published recipe for this
     surface lands in the same band — **tint alpha .10–.35, blur 8–24px, border
     alpha ~.2** — and this card was at **.58 / 26px / .42**: roughly twice as
     opaque and twice as strongly outlined as the material it was imitating. At
     that alpha you are not looking through anything, which is why no amount of
     tuning the blur helped.

     ⚠ AND THE SATURATE WAS BACKWARDS. Last round argued chroma DOWN (0.85) on
     the grounds that a matt surface mutes what is behind it. But **blurring
     already desaturates** — averaging neighbouring pixels pulls every colour
     toward the local mean — which is exactly why the standard recipe pairs a
     blur with `saturate(180%)`: it is compensation, not decoration. Applying
     0.85 ON TOP of that double-desaturates, and a double-desaturated near-white
     panel is not glass, it is PAPER. That was the actual fault George kept
     seeing under three different words.

     So matt is carried by the things that genuinely distinguish it, and none of
     them is chroma:

       transparency  .26 — below the middle of the published band. Every round
                     that failed had this too high; this is the number George is
                     actually describing when he says TRANSPARENT
       blur          28px, LARGE. Matt glass destroys detail rather than
                     softening it. The one number that has been roughly right
                     since the word "matt" first came up
       brightness    1.05 — it lifts the backdrop toward white BEFORE the tint
                     composites, so the panel can be genuinely transparent and
                     still be a light ground for dark text. Kept small: every
                     point of it is a point of the gold washed out
       saturate      1.35, well ABOVE 1. Blurring desaturates on its own; this
                     restores it and then some, so what comes through reads as
                     GOLD rather than as a pale wash. Raised from 1.12
       texture       GONE from the card. Micro-texture is a real property of
                     etched glass, but at screen scale a visible grain on a
                     near-white panel reads as card stock. It stays on the MARK,
                     where a dark ground can carry it
       corner        var(--glass-r) — see the token. A 3px corner is paper
       specular      still none across the FACE. A gloss is a mirror image of
                     the light source, and a matt surface has none */
  /* ⚠ George, 2026-08-24: "try to make the glass
     more transparent." .26 → .16, a ~38% cut in the tint
     and the bottom of the published .10–.35 band rather than its middle.
     `brightness` is deliberately NOT reduced with it: it is the only thing
     holding the proof line above AA once the tint stops doing that job. */
  /* ⚠⚠⚠ .16 → .72, AND THIS IS THE ONE FIX THAT MATTERS ON THIS SURFACE.
     MEASURED 2026-08-25, headed, GPU, both engines, on a fresh control element
     with nothing else on it — same markup, same page, same pixel:

         backdrop-filter   Chrome              Firefox
         none              [233,222,200]       [233,222,200]
         invert(1)         [ 63, 72, 92]       [233,222,200]
         saturate(3)       [252,220,160]       [233,222,200]
         brightness(.4)    [118,113,103]       [233,222,200]

     **Firefox answers `true` to `@supports (backdrop-filter: blur(1px))` and
     then composites none of it.** Not a subtle difference in one function — the
     whole property is inert, including `invert(1)`.

     The block below this rule anticipated exactly this a day earlier and named
     the remedy: *"⚠ THIS CANNOT CATCH 'supported but not applied' … If that
     turns out to be the case on a real machine, the fix is a different one:
     raise the base tint for everybody."* It is the case, on every machine
     George tests (his words, 2026-08-25: *"it's not the display because it's
     happening on multiple devices that I test"*). So the tint is raised.

     ⚠ WHY THIS AND NOT A FIREFOX-ONLY BRANCH. `@supports (-moz-appearance:none)`
     would keep Chrome's 84%-see-through pane and patch Gecko alone — and it is a
     vendor sniff wearing a feature query, wrong the day Firefox ships working
     compositing, and it leaves the SAME hole open for every other engine that
     claims the property and drops it (hardware acceleration off, a blocklisted
     GPU, a remote desktop). One surface, one behaviour, no detection.

     ⚠ WHAT IT COSTS, STATED PLAINLY: the pane is no longer 84% see-through in
     Chrome, so the rake reads through it as a soft warm shift rather than as
     visible travelling light. That is a real loss and it is George's to reverse
     if he would rather have the glass and the Firefox fault. .72 is not a taste
     value — it is the alpha at which the `.sub` proof line clears AA over the
     darkest gold this hero produces, with nothing lifting the backdrop.
     ⚠ `backdrop-filter` STAYS. Where it works it still blurs and lifts the
     remaining 28%, which is why the card is not simply flat paint on Chrome. */
  background:rgba(250,247,239,.72);
  /* ⚠ `contrast(1.2)` is the first half of "show more
     of a reflection when the rakes pass through", and it is the half that is
     genuinely PHYSICAL. Contrast pushes the backdrop's lights up and its darks
     down BEFORE the tint composites, so a sweep passing behind a card arrives
     as a brighter band than the paper around it instead of as the same wash.
     Nothing is animated: the pane reacts because the thing behind it moves. */
  -webkit-backdrop-filter:blur(28px) saturate(1.35) brightness(1.05) contrast(1.2);
  backdrop-filter:blur(28px) saturate(1.35) brightness(1.05) contrast(1.2);
  /* ⚠⚠ THE EDGE IS BACK, AND I WAS WRONG TO TAKE IT OUT. Last round's rule was
     "a bevel is a highlight by another name", so every lit edge went with the
     gloss. That over-generalised, and it is why three rounds of a "matt" card
     kept reading as a flat rectangle:

       a SPECULAR SHEEN across the FACE is a mirror image of the light source.
       Matt glass has none, and deleting it was right.

       a lit EDGE is not a reflection at all — it is the THICKNESS of the pane,
       its ground rim catching ambient light from every direction. Every real
       piece of frosted or acid-etched glass has one. A matt surface scatters
       light; it does not stop having edges.

     So: a bright rim along the top and down the left (where the page's light
     comes from), a warm dark rim underneath (the rim in its own shadow), and a
     faint even border holding the whole outline together. All three follow
     `border-radius`, which is what turns the corner into a ground edge rather
     than a cut one — and is why the radius and the rim had to arrive together.
     ⚠ NOT A BEVEL: a bevel lights the top and shades the bottom to fake a
     RAISED SLAB. This lights the top and shades the bottom of a TRANSPARENT
     one, which is the opposite reading — thickness, not elevation. The
     difference is the face between them, and the face here is doing nothing.
     The drop shadow stays: that is the pane's distance from the page. */
  border:1px solid rgba(255,255,255,.22);
  box-shadow:inset 0 1.5px 0 rgba(255,255,255,.62),
             inset 1px 0 0 rgba(255,255,255,.24),
             inset 0 -1px 0 rgba(122,94,42,.16),
             0 1px 2px rgba(40,30,20,.03),
             0 22px 50px -30px rgba(60,44,20,.42);
  border-radius:var(--glass-r);
  line-height:1.32;
  padding:clamp(8px,1.05cqw,12px) clamp(9px,1.25cqw,14px);
  /* ⚠ ONE HEIGHT FOR ALL TEN. Some proof lines run to two lines and some to
     one, so without this the branches are two different sizes and the diagram
     reads as ragged. Fixing the box rather than trimming the copy — the proof
     line is the half George could not read, so it does not get cut to fit a
     layout. */
  min-height:clamp(58px,7.15cqw,80px);
  /* ⚠ THE TRANSFORM CURVE OVERSHOOTS SLIGHTLY (the 1.12 in the third control
     point) and the other properties do not. A bigger lift wants a little spring
     at the top of its travel; a backdrop blur that overshoots just looks like a
     mis-timed filter. Two curves, one declaration. */
  transition:transform .26s cubic-bezier(.22,.9,.28,1.12),border-color .3s,box-shadow .3s,
             background .3s,backdrop-filter .3s,-webkit-backdrop-filter .3s;
}
/* ⚠ THE SUB-LINE IS THE PROOF, SO IT HAS TO BE LEGIBLE. It was 10.5px in
   --faint and George couldn't read it (2026-08-22). These scale with the box
   but the FLOOR of each clamp is the promise: 11.5 / 10.5 and no smaller,
   whatever the window does. */
/* ⚠ THE REFLECTION. George, 2026-08-24: "show more of a
   reflection on the glass when the rakes pass through."

   ⚠ THIS IS NOT THE `::before` SHEEN THAT WAS DELETED ON 2026-08-23, and the
   difference is the whole reason it is allowed back. That one was a FIXED
   white gradient painted on top: it looked identical whether a sweep was behind
   the card or not, which is exactly why it read as gloss — a gloss is a mirror
   image of a light source, and a mirror image that never changes is a sticker.

   This one is `mix-blend-mode`, so it has no colour of its own. It multiplies
   against whatever the card has ALREADY composited — which includes the rake
   coming through the glass — so where a sweep is behind the card the streak is
   bright, and where the paper is flat it is very nearly invisible. The light
   moves; the reflection moves with it. That is a reflection rather than a
   gloss, and it is what George has been asking for each time he said the glass
   was not reacting to the rakes.

   ⚠ `overlay` ON A NEAR-WHITE BASE RESOLVES TO SCREEN, measured on this page in
   an earlier round — which is the behaviour wanted here rather than a defect:
   it brightens where there is light to brighten and does almost nothing where
   there is not. Do NOT "fix" it to hard-light; that drove the hero chartreuse.

   ⚠ TWO ANGLES, NOT ONE. A single streak reads as a lit corner. The second,
   weaker band on the far side is what makes it read as a PANE catching light at
   two edges — the same reason the rim treatment lights top and left.
   ⚠ IT MUST SIT UNDER THE TEXT. A blend mode over 10.5px type shifts its colour
   and its measured contrast; the rule below lifts both children above it. */
/* ⚠⚠ THE GLOSS IS OFF, AND IT GOES WITH THE RAISED TINT RATHER THAN SEPARATELY.
   Its entire argument was that it reacts to a sweep coming THROUGH the glass:
   `mix-blend-mode:overlay` with no colour of its own. Over a pane that is now
   72% tint there is very little sweep left to react to, so what remains is a
   fixed white smear across every card — the exact "gloss is a sticker" fault the
   matt notes above spent four rounds removing.
   ⚠ IT IS `display:none` RATHER THAN DELETED because it is the first thing to
   restore if George decides the see-through pane was worth the Firefox fault.
   Turn the tint back down and turn this back on together; either alone is worse
   than both. */
.hcard a::before{
  display:none;
  content:'';position:absolute;inset:0;border-radius:inherit;
  z-index:0;pointer-events:none;
  background:linear-gradient(112deg,
    rgba(255,255,255,.62) 0%,
    rgba(255,255,255,.16) 24%,
    rgba(255,255,255,0)   44%,
    rgba(255,255,255,0)   64%,
    rgba(255,255,255,.26) 90%,
    rgba(255,255,255,.05) 100%);
  mix-blend-mode:overlay;
  transition:opacity .3s;
}
/* ⚠ BOTH CHILDREN, and they are the only two — the drawing and the text block.
   Without this the reflection paints OVER them (an absolutely-positioned
   pseudo-element outranks non-positioned in-flow children) and the proof line
   is being blended rather than read. */
.hcard a > *{position:relative;z-index:1}
/* ⚠ THE TEXT BLOCK TAKES THE SLACK. `min-width:0` is the load-bearing half: a
   flex item's default `min-width:auto` refuses to shrink below its longest word,
   so without it a long title would push the card past its clamp and the ring
   would go ragged. */
.htxt{flex:1 1 auto;min-width:0}
/* ⚠ THE CLICK CUE. Same glyph, same size and same travel as `.btn-arrow` on the
   hero CTA — one word for "this goes somewhere", used twice, rather than two.
   ⚠ VISIBLE AT REST, at .55 rather than 0. A cue that only appears on hover is
   not a cue: it reaches the people who were already going to click and nobody
   else, which is the whole fault being fixed here. Hover takes it to full and
   slides it 3px, so the card still has somewhere to go.
   ⚠⚠ ABSOLUTE, NOT A FLEX ITEM, AND THAT IS NOT A STYLE CHOICE — MEASURED.
   In the row it cost 27.5px of the text block (its own 14, the row's 11.5px gap,
   and 2 of margin). "Insurance & Vendor Contracts" needs 192px for one line and
   had 205; at 178 it wrapped, and that ONE card went 78px tall against the other
   ten at 78 — exactly the raggedness `min-height` on `.hcard a` exists to
   prevent, reintroduced by the fix for something else. Out of flow it costs only
   the 8px of extra right padding below, so every title stays on the line it was
   on. Re-measure before changing either number; they are a pair.
   ⚠ `translate` AS ITS OWN PROPERTY, never inside `transform` — `.hcard` and
   `.hcard.c` both use transform for their position, and this is the standing
   rule in this file, so the two compose instead of one silently winning. It is
   also why the vertical centring is `translate:0 -50%` and not a transform.
   ⚠⚠ SELECTOR SPECIFICITY IS LOAD-BEARING HERE. `.hcard a > *` above sets
   `position:relative` on every child and scores (0,1,1); a bare `.harr` scores
   (0,1,0) and LOSES, so `position:absolute` was silently dropped and the arrow
   went back into the flow — with the card measuring 93px again and nothing in
   the CSS looking wrong. Hence `.hcard a .harr`, which scores (0,2,1). Do not
   "tidy" it back to `.harr`. */
.hcard a .harr{position:absolute;right:7px;top:50%;translate:0 -50%;
  font-size:13px;line-height:1;color:var(--accent);opacity:.55;pointer-events:none;
  transition:opacity .26s,translate .26s cubic-bezier(.22,.9,.28,1.12)}
.hcard:hover a .harr,.hcard.on a .harr{opacity:1;translate:3px -50%}
/* The arrow's lane. The base rule's right padding is clamp(9px,1.25cqw,14px);
   this reserves 22px so the text stops clear of the glyph at every card size.
   ⚠ THE ≤680px BLOCK RE-DECLARES `padding` WHOLESALE and would drop this — it
   restates it. Any future rule that sets `padding` on `.hcard a` must too. */
.hcard a{padding-right:22px}
/* The card is a link and now says so with the pointer as well as the arrow. */
.hcard a{cursor:pointer}
/* ⚠ THERE WAS NO FOCUS STYLE ON THESE AT ALL, so a keyboard user tabbing
   through the hero got eleven invisible stops. On the anchor rather than the
   <li> so the ring follows the card's own radius. */
.hcard a:focus-visible{outline:2px solid var(--accent);outline-offset:3px}
/* Hover leans the pane into the light a little harder. */
.hcard:hover a::before,.hcard.on a::before{opacity:1.0}
/* ⚠ THE FLOOR OF A `clamp()` IS A FONT SIZE AND IT HIDES FROM EVERY SIZE SWEEP.
   The 2026-08-26 type-floor pass rewrote 145 plain `font-size:Npx` declarations across
   both stylesheets and did not touch these two, because `clamp(10.5px,…)` does not match
   a px-literal search. The site's smallest rendered text was still 10.5px afterwards —
   the pass reported success and the worst line on the home page had not moved.
   ⚠ ANY FUTURE SIZE SWEEP MUST GREP `clamp(` SEPARATELY. There are ten in this file.

   ⚠⚠ AND THESE TWO ARE CAPPED BY GEOMETRY, NOT BY TASTE — 12px / 11px is the CEILING.
   The eleven hub cards are positioned on a ring and size to their own content. Measured
   pairwise-rectangle collisions at the viewport this hero is tuned for:
       title 11.5 / sub 10.5   0 collisions   card 68px   <- what shipped before
       title 12   / sub 11     0 collisions   card 74px   <- CHOSEN, the last safe step
       title 12   / sub 11.5   5 collisions   card 77px
       title 12.5 / sub 12     7 collisions   card 88px   <- tried first; cards overlapped
   The app's prose floor is 12px and the sub cannot reach it here. Raising it further
   means re-laying-out the hero ring (a wider card, a bigger radius, or fewer spokes) —
   a real job, not a number change. Do not nudge these two without re-running that
   collision measurement; the failure is silent, and it looks like a z-index bug rather
   than a type-size one. */
.hcard b{display:block;font-weight:700;font-size:clamp(12px,1.21cqw,13.5px)}
/* ⚠ AND IT IS NOT `--muted` ANY MORE, BECAUSE IT NEVER PASSED. Measured on the
   glass over the lit ground: `--muted` (#877F70) comes out at 2.85 : 1 and AA
   for small text wants 4.5. It already failed on the old opaque panel (~3.0),
   so this is an inherited fault rather than one the glass introduced — but it
   is the same 10.5px line George could not read on 2026-08-22, and translucency
   was about to make it worse. `--proof` is a site-only token: `--muted` is one
   of the app's and is used across a product full of larger text, so darkening
   it globally is a different change on a different surface.
   ⚠⚠ AND THE COLOUR WAS ONLY EVER HALF THE FIX. That note fixed the CONTRAST of a
   10.5px line and left it 10.5px, which is why George raised the same line again on
   2026-08-26 — this time as size rather than colour: "older people might have trouble
   reading the fine print". The floor is now 12px. If a line has been complained about
   twice, check both properties before deciding which one was wrong.
   ⚠ 11px, NOT the 12px prose floor — capped by the ring's collision limit. See `.hcard b`. */
.hcard .sub{display:block;color:var(--proof);margin-top:2px;
  font-size:clamp(11px,1.07cqw,12px);font-variant-numeric:tabular-nums}
/* ⚠ THE `::before` SPECULAR SHEEN WAS DELETED HERE, NOT DISABLED — see the
   matt-glass note in `.hcard a`. It was a raked highlight across the top-left
   corner, and it was the single most confidently-wrong thing in this file: a
   specular highlight is the defining feature of a GLOSSY surface and the exact
   thing a matt one does not have. If a future round wants gloss back it is in
   the deploy log, but it must not come back alongside the word "matt".

   Hover lifts the pane and lets a little more light through it — a matt surface
   answers by getting slightly clearer, not by catching a shine. ⚠ WHICH MEANS
   HOVER RAISES `brightness`, NOT ALPHA. The old rule went .58 → .72, i.e. it
   made the glass MORE OPAQUE to say "brighter" — the opposite statement. The
   tint holds and the light behind it comes up instead. */
/* ⚠ THE LIFT WENT FROM 3px TO 8px AND GAINED A SCALE (George, 2026-08-24: "I
   want the hover animation to be a bit more exagerated"), which is only safe
   now that the card actually lets go — see the per-card `mouseleave` in
   `site.js`. A 3px lift that got stuck was barely noticeable; an 8px lift and a
   scale that got stuck would have been the loudest bug on the page.
   ⚠⚠ TRANSFORM AND SCALE ONLY. NOTHING HERE MAY USE `opacity`, `filter`,
   `mask`, `mix-blend-mode` OR `will-change` OF ANY OF THEM. Each of those makes
   the element a BACKDROP ROOT, after which its own `backdrop-filter` samples
   nothing and the card renders as flat tint over blank paper — which is exactly
   the bug George reported from a second machine and which took a full round to
   find. `transform` and `scale` are not on that list, so they are the two levers
   available for making this louder. Do not reach for a fade.
   ⚠ THE RETURN IS THE SAME CURVE, because the transition lives on `.hcard a`
   rather than here. That is what makes it fall back into place rather than snap. */
.hcard:hover a,.hcard.on a{transform:translateY(-8px) scale(1.025);
  /* ⚠ .20 → .80, the hover twin of the base tint above. Read that note first. */
  background:rgba(253,251,246,.80);
  -webkit-backdrop-filter:blur(28px) saturate(1.45) brightness(1.11);
  backdrop-filter:blur(28px) saturate(1.45) brightness(1.11);
  border-color:rgba(255,255,255,.34);
  /* the rim brightens with the pane — the same three edges, lit harder, and the
     cast shadow deepens with the extra height so the lift has somewhere to be */
  box-shadow:inset 0 1.5px 0 rgba(255,255,255,.8),
             inset 1px 0 0 rgba(255,255,255,.34),
             inset 0 -1px 0 rgba(122,94,42,.2),
             0 3px 6px rgba(40,30,20,.06),
             0 40px 78px -32px rgba(60,44,20,.62)}

/* ⚠⚠ WHAT THE CARDS LOOK LIKE WHEN THE FILTER NEVER ARRIVES, and this block
   exists because ONE missing feature explains THREE separate symptoms at once.
   George, 2026-08-24, from a second machine: *"The gold on the cards is really
   intense … when I click a card the color switches back to grey … the rakes
   make the cards look yellow."*

   Read the tint on its own and all three fall out of it. `.hcard a` is
   **`rgba(252,250,244,.16)` — 84% see-through.** Everything that makes it read
   as a pale matt pane is in the `backdrop-filter`: the blur destroys the
   detail, `brightness(1.05)` lifts the ground toward white, `contrast(1.2)`
   holds the text off it. Take the filter away and you are looking at raw
   raking gold through a 16% veil, which is *precisely* "intense" and "yellow" —
   and when the portal's `.pz-veil` slides underneath, that same 16% veil shows
   the veil's flat warm paper instead, which against surrounding gold reads
   **grey**.

   So this is not a graceful degradation for an old browser. It is the same
   surface expressed WITHOUT a filter: the tint carries the job the filter was
   doing. .72 is the alpha at which the proof line clears AA over the darkest
   gold this hero produces — the same measurement discipline as the note above,
   just with nothing to lift the backdrop.

   ⚠ THE GLOSS GOES WITH IT. `.hcard a::before` is `mix-blend-mode:overlay`
   with no colour of its own, and its whole argument is that it reacts to the
   sweep coming THROUGH the glass. Over an opaque panel there is no sweep to
   react to, so it would be a fixed white smear — the exact "gloss is a sticker"
   fault the matt note spent four rounds getting rid of.
   ⚠ THIS CANNOT CATCH "supported but not applied" — a browser that answers yes
   to `@supports` and then composites nothing (hardware acceleration off, a
   blocklisted GPU) falls through here and still shows the bare tint. If that
   turns out to be the case on a real machine, the fix is a different one: raise
   the base tint for everybody. */
/* ⚠ THE `@supports not (backdrop-filter)` BLOCK THAT LIVED HERE IS GONE, and it
   is gone because it WON. Its values are now the unconditional ones above —
   .72 / .80 / .88, the same numbers, applied to everybody instead of to the
   browsers honest enough to admit they cannot composite. Re-adding a support
   query here would be a no-op that reads like a safety net. */

/* ── the six card drawings ────────────────────────────────────────────
   One small picture of the thing the card claims — a page with a term
   lifted out of it, a cost cut three ways, a deposit joined to a tenant,
   a calendar with the date that costs money, a signature on the line, a
   question answered. Built here in shapes rather than an icon font
   because they are drawings of THIS product, and because a glyph set
   would be the "eight icons and four ideas" mistake again.

   ⚠ THE RESTING STATE IS THE FINISHED PICTURE. Every rule below draws the
   completed thing; only `.hcard.on` re-plays it. So no-JS and reduced
   motion both show six complete drawings, never six blanks. */
.hvis{width:clamp(31px,3.93cqw,44px);height:auto;aspect-ratio:38/30;
  flex:none;overflow:visible}
/* ── THE DRAWINGS ARE GOLD FOIL ───────────────────────────────────────
   George picked "B — gold foil" off the line-work specimen on 2026-08-23: no
   container, every stroke a gold gradient, the grey and the green gone.

   ⚠ AND THE FIRST ATTEMPT AT IT WAS WRONG IN A WAY WORTH RECORDING. The
   specimen expressed the treatment as three blanket overrides —
   `.hvis *{fill:none;stroke:url(#foil);stroke-width:1}` and two companions —
   which is the cheapest way to recolour eleven drawings and destroys them.
   George, looking at it: *"the lease review, notifications, built-in
   e-signature, CAM and tax, rent ledger, exports, ask, and reads a lease all
   lose part of the personality … the signature just has a little squiggle, but
   it used to have a bit more."*

   HE IS DESCRIBING A HIERARCHY, NOT A COLOUR. Every one of these drawings has
   an internal order — a SURFACE (a page, a shield, a bubble), some SECONDARY
   marks on it (ruled lines, ordinary days, on-time months) and ONE THING THAT
   MATTERS (the tick, the alert, the late month, the signature). Flattening all
   three to one weight and one paint deletes the third tier, and the third tier
   is the drawing's whole point: a signature is a heavy stroke ON a light
   baseline, and two identical hairlines are just a squiggle near a line.

   SO THE FOIL IS THREE TONES, ONE PER TIER, and it is applied per part:

     #foilPale  the secondary marks — ruled lines, ordinary days, on-time months
     #foil      the structure — outlines of pages, shields, bubbles, rows
     #foilDeep  the one thing that matters, and always the heaviest stroke

   Read down this block and the third column is the answer to "what is this
   drawing about". If a new drawing arrives and every part of it is #foil, it
   has not been designed yet. */
.hvis .v-paper{fill:rgba(148,102,27,.09);stroke:url(#foil);stroke-width:1.2}
.hvis .v-line{stroke:url(#foilPale);stroke-width:1.2;stroke-linecap:round;fill:none}
.hvis .v-chip{fill:rgba(199,154,69,.34);stroke:none}
.hvis .v-tick{stroke:url(#foilDeep);stroke-width:1.7;stroke-linecap:round;
  stroke-linejoin:round;fill:none}
/* CAM cut three ways — and the three have to be three TONES or the drawing
   stops saying "split" and starts saying "bar chart". Deep, mid, pale. */
.hvis .v-seg{fill:url(#foilDeep);transform-origin:left center}
.hvis .v-seg.s2{fill:url(#foil)}
.hvis .v-seg.s3{fill:url(#foilPale)}
.hvis .v-row{fill:rgba(148,102,27,.09);stroke:url(#foil);stroke-width:1.2}
.hvis .v-link{stroke:url(#foilDeep);stroke-width:1.7;fill:none;stroke-linecap:round;
  stroke-dasharray:1;stroke-dashoffset:0}
.hvis .v-node{fill:url(#foilDeep)}
/* ⚠ THE ORDINARY DAYS ARE MID, NOT PALE, and that is a correction made by
   looking at all eleven blown up to 96px side by side. At pale they vanished
   into the paper fill and the drawing became "a calendar with one dot on it" —
   the alert has nothing to stand out FROM if the ordinary days are invisible.
   It keeps its rank by being deep AND nearly twice the radius (3 against 1.7),
   which is two signals rather than one. */
.hvis .v-day{fill:url(#foil)}
.hvis .v-alert{fill:url(#foilDeep)}
/* ⚠ THE SIGNATURE IS THE ONE GEORGE NAMED, and the fix is the weight gap, not
   the colour. It is a heavy deep stroke over a pale baseline — 1.9 against 1.2
   — because that gap IS the picture. Equalise them and it reads as a squiggle
   next to a line, which is exactly what he saw. */
.hvis .v-sign{stroke:url(#foilDeep);stroke-width:1.9;fill:none;stroke-linecap:round;
  stroke-dasharray:1;stroke-dashoffset:0}
.hvis .v-bubble{fill:rgba(148,102,27,.09);stroke:url(#foil);stroke-width:1.2}
/* The three dots and the three fan blades both GRADE outward — pale nearest,
   deep furthest — so each reads as one thing travelling rather than three
   identical marks sitting still. */
.hvis .v-dot3{fill:url(#foilPale)}
.hvis .v-dot3.d2{fill:url(#foil)}
.hvis .v-dot3.d3{fill:url(#foilDeep)}
.hvis .v-fan{fill:url(#foilPale)}
.hvis .v-fan.f2{fill:url(#foil)}
.hvis .v-fan.f3{fill:url(#foilDeep)}
.hvis .v-shield{fill:rgba(148,102,27,.09);stroke:url(#foil);stroke-width:1.3}
/* Twelve months as eight cells, one of them behind. The odd one out is the
   whole point of the drawing — a ledger you never look at says nothing — so it
   keeps the deep tone AND the heavier rule AND the darker fill. Three signals,
   because it survived the flattening on none of them. */
.hvis .v-mo{fill:rgba(199,154,69,.26);stroke:url(#foilPale);stroke-width:.8}
.hvis .v-mo.late{fill:rgba(101,69,19,.72);stroke:url(#foilDeep);stroke-width:1.1}
.hvis .v-out{stroke:url(#foilDeep);stroke-width:1.8;fill:none;stroke-linecap:round;
  stroke-linejoin:round}
/* …and the replay. Each keyframe ENDS on the resting values above. */
.hcard.on .v-seg{animation:vGrow .5s cubic-bezier(.2,.72,.3,1) both}
.hcard.on .v-seg.s2{animation-delay:.12s}
.hcard.on .v-seg.s3{animation-delay:.24s}
.hcard.on .v-tick{animation:vPop .45s cubic-bezier(.2,.9,.3,1) both}
.hcard.on .v-link{animation:vDraw .6s ease-out both}
.hcard.on .v-node{animation:vPop .4s cubic-bezier(.2,.9,.3,1) .38s both}
.hcard.on .v-alert{animation:vPulse .9s ease-out}
.hcard.on .v-sign{animation:vDraw .75s ease-out both}
.hcard.on .v-dot3{animation:vBounce .8s ease-in-out}
.hcard.on .v-dot3.d2{animation-delay:.12s}
.hcard.on .v-dot3.d3{animation-delay:.24s}
.hcard.on .v-fan{animation:vPop .45s cubic-bezier(.2,.9,.3,1) both}
.hcard.on .v-fan.f2{animation-delay:.1s}
.hcard.on .v-fan.f3{animation-delay:.2s}
.hcard.on .v-out{animation:vDrop .7s ease-out}
.hcard.on .v-mo{animation:vRise .42s cubic-bezier(.2,.8,.3,1) both}
.hcard.on .v-mo:nth-of-type(2){animation-delay:.05s}
.hcard.on .v-mo:nth-of-type(3){animation-delay:.10s}
.hcard.on .v-mo:nth-of-type(4){animation-delay:.15s}
.hcard.on .v-mo:nth-of-type(5){animation-delay:.20s}
.hcard.on .v-mo:nth-of-type(6){animation-delay:.25s}
.hcard.on .v-mo:nth-of-type(7){animation-delay:.30s}
.hcard.on .v-mo:nth-of-type(8){animation-delay:.35s}
@keyframes vGrow{from{transform:scaleX(0)}}
@keyframes vDraw{from{stroke-dashoffset:1}}
@keyframes vPop{from{opacity:0;transform:scale(.4);transform-origin:center}}
@keyframes vPulse{0%{transform:scale(1)}45%{transform:scale(1.35)}100%{transform:scale(1)}}
@keyframes vBounce{0%,100%{transform:translateY(0)}45%{transform:translateY(-3px)}}
@keyframes vDrop{0%,100%{transform:translateY(0)}45%{transform:translateY(3px)}}
@keyframes vRise{from{transform:scaleY(.15)}}
/* ⚠ SVG children need their own transform box, or scale()/translateY() above
   resolve against the whole viewport and the shape flies off screen. */
.hvis .v-tick,.hvis .v-node,.hvis .v-alert,.hvis .v-dot3,.hvis .v-seg,
.hvis .v-fan,.hvis .v-out,.hvis .v-mo{transform-box:fill-box}
.hvis .v-mo{transform-origin:bottom}
.hvis .v-alert,.hvis .v-dot3,.hvis .v-node,.hvis .v-tick,.hvis .v-fan{
  transform-origin:center}

/* ⚠ THE MARK'S HEIGHT COMES FROM THE PAGE, NOT FROM THIS FILE. Every wire
   converges on the point the layout generator calls CY, and it writes that
   same number onto `.hero-hub` as `--hub-cy`. This rule just reads it, so the
   two cannot disagree. They did once: pinned here at a hard `top:50%`, the
   mark stayed put while the box grew 620 → 640, and for two rounds all eleven
   spokes ended 30 units above it, in mid-air, on the live site.
   ⚠ AND IT IS NOT 50%. The mark is the centre of the TEN-branch ring (51.56%
   of the box); the crown sits above that ring rather than in it, and counting
   it would drag the mark below the thing it is the hub of. The fallback is
   50% only so a page that forgot the variable still looks deliberate. */
.hub-core{
  position:absolute;left:50%;top:var(--hub-cy,50%);translate:-50% -50%;
  width:clamp(48px,6.07cqw,68px);height:clamp(48px,6.07cqw,68px);
  /* ⚠ THE SAME MATT SURFACE AS THE NAV MARK — same tint, same alpha, same
     texture, deliberately. They are one logo appearing twice on one screen;
     two different greens forty pixels apart read as a mistake long before
     anyone works out it was two alphas. See `.nav-mark` for why the tint is
     darker than `--accent`, why this is a surface rather than glass, and why
     `saturate` sits above 1.
     ⚠⚠ IT ALSO HAS TO CARRY THE PORTAL — the single element left on an emptied
     page mid-transition, flying over an opaque veil. THAT IS THE SECOND FLOOR
     ON ITS ALPHA and it is a different one from the hero's: over the veil there
     is nothing behind the glass to see, so the mark is only as strong as its
     own tint. .58 on the deep tint was checked at that moment as well as against a
     bright sweep; anything thinner and the thing carrying the whole transition
     goes ghostly exactly when it is the only thing on screen. */
  z-index:2;border-radius:var(--glass-r-lg);
  /* ⚠ same deeper-tint-at-lower-alpha trade as the nav
     mark. ⚠ This one also has to survive the portal, flying over an opaque veil
     with nothing behind it to see, where it is only as strong as its own tint —
     which is a second reason the tint had to get darker rather than the alpha
     simply lower. */
  /* ⚠⚠ .58 → .88 GREEN, AND THE FROST TEXTURE IS OFF WITH IT (George,
     2026-08-25, screenshot from Safari: *"the top left corner still looks
     really weird"* — the mark was a green tile crawling with bright
     yellow-green speckle).
     `--frost` is an feTurbulence noise tile laid on with
     `background-blend-mode:soft-light`. Soft-light is a CONDITIONAL curve —
     it lightens where the blend layer is above 50% grey and darkens below —
     so over a nearly-black green every light grain of that noise is thrown
     a long way up while the dark grains barely move. The result is not a
     texture, it is glitter, and it got worse the moment the green went
     opaque because there was no longer any gold behind it to average the
     grains out. It was tuned at .58 against moving light and never
     re-checked against a solid pane.
     ⚠ SO THE TILE GOES, NOT ITS ALPHA. Turning the noise down leaves the
     same speckle, quieter; the blend mode is what makes single pixels jump.
     ⚠ `--frost` STILL HAS OTHER CALLERS — grep before deleting the token. */
  background:rgba(24,36,12,.88);
  background-size:90px 90px,auto;
  background-blend-mode:soft-light,normal;
  /* ⚠ HARDER THAN THE CARDS' 1.2, and harder than this was an hour ago. A dark
     pane flattens what passes through it, and `color-dodge` above works off the
     BASE — so the more structure survives the tint, the more there is for the
     streak to flare against. The two changes are one change. */
  -webkit-backdrop-filter:blur(20px) saturate(1.3) contrast(1.4);
  backdrop-filter:blur(20px) saturate(1.3) contrast(1.4);
  /* ⚠ THE RIM, THE TOP LIGHT AND THE RING TRACK `.nav-mark` — see the block there for
     the measurements. They are one logo appearing twice on one screen and the note above
     already insists the SURFACE match; the edge has to match for the same reason. This
     tile always had `--shadow-lift`, so it is the one that only needed three of the four.
     ⚠ THE RING GOES FIRST AND `.hub-ring` IS UNAFFECTED: that is a real child at
     `inset:-7px`, six pixels further out than this 1px shadow, and it is `opacity:0`
     until the portal fires. */
  border:1px solid rgba(255,255,255,.44);
  display:grid;place-items:center;
  box-shadow:0 0 0 1px rgba(24,36,12,.85),
             inset 0 2px 0 rgba(226,236,208,.40),
             inset 1.5px 0 0 rgba(226,236,208,.14),
             inset 0 -1.5px 0 rgba(12,20,6,.28),
             var(--shadow-lift);
}
/* ⚠ NO `overflow:hidden` ON THE CORE, however much the gloss looks like it
   wants one. `.hub-ring` is a child at `inset:-7px` — deliberately OUTSIDE the
   box — and clipping the core deletes the ring flare entirely. The gloss does
   not need it: `inset:0` plus `border-radius:inherit` already keeps it inside
   the tile. (`.nav-mark` does clip, and always has, but it has no ring.) */
.hub-core img{width:74%;height:74%}
/* ⚠ THE RING'S RADIUS TRACKS THE CORE'S PLUS ITS OWN INSET, or the two stop
   being concentric and the flare reads as a rounded rectangle escaping a
   different rounded rectangle. inset -7 + core 16 = 23. */
.hub-ring{position:absolute;inset:-7px;border:1.5px solid var(--accent);border-radius:23px;
  opacity:0;pointer-events:none}

/* The centred copy, in two halves around the diagram: the claim above it, the
   two buttons below it. */
.hero-center{position:relative;text-align:center;max-width:760px;margin:0 auto}
.hero-center .lede{margin-left:auto;margin-right:auto}
.hero-center .btn-row{justify-content:center}
.hero-center .hero-note{margin-left:auto;margin-right:auto}
/* ⚠ THE HUB HERO'S H1 IS SMALLER THAN THE SITE'S, deliberately. At the shared
   clamp it tops out at 74px and two lines of it eat 150px of the one screen
   the whole hero now has to fit inside. The other pages keep the big one. */
.hub-hero h1{font-size:clamp(34px,4.4vw,52px)}
.hub-hero .eyebrow{margin-bottom:14px}
.hero-top{margin-bottom:12px}
/* ⚠⚠ ADDED 2026-08-30, George: "fine print underneath: manage with ease, stay
   in control." Reviewed on localhost against the site's bigger default h1
   size first — that version wrapped the longer tagline to 3 lines and pushed
   the branch diagram below the fold on a normal screen, which is exactly the
   trade-off `.hub-hero h1` above exists to avoid. George picked this size.
   ⚠ FULL-STRENGTH `--muted`, NOT A LIGHTER GREY. This site's own contrast
   pass already established that "fine print" reads as subordinate through
   size and spacing, never through a fainter colour that measures under AA —
   see the `--muted`/`--faint` notes at the top of this file. */
.hero-fineprint{
  margin-top:10px;font-family:var(--ui);font-size:14px;font-weight:500;
  letter-spacing:.02em;color:var(--muted);
}
/* ⚠ A GUARANTEED GAP UNDER THE DIAGRAM. The outer branches are centred on
   their rows, so they reach lower than the last wire does, and as the box
   shrinks that clearance shrinks with it — at 800px tall it was down to 2px
   and the bottom cards were touching "See if we fit". This margin does not
   scale, so the separation has a floor whatever the window does. */
.hero-bottom{padding-bottom:18px;margin-top:14px}
.hero-bottom .btn-row{margin-top:4px}

/* ── the fake-UI mock kit ───────────────────────────────────────────
   Every product visual on this site is built here, in HTML and CSS. No
   screenshots: nothing to re-shoot when a screen changes, nothing to go
   stale, nothing extra to download. */
/* ⚠ THE MOCKS ARE PAPER AND THE HUB'S CARDS ARE GLASS, and the split is on
   purpose. Every one of these is a picture of a real screen — a table of
   figures, a ledger, a reconciliation — and frosting a table of figures is
   precisely the readability trade we spent a round removing from the scroll.
   They stay opaque and simply learn the same language: no hard grey border, a
   warm hairline instead, and the long soft shadow the cards got, so the page
   reads as one design without the data going translucent. */
.mock{
  background:var(--panel);border:1px solid rgba(148,102,27,.14);border-radius:6px;
  box-shadow:0 1px 2px rgba(40,30,20,.05),0 30px 68px -34px rgba(60,44,20,.5);
  overflow:hidden;font-size:12.5px;line-height:1.4;
}
.mock.flat{box-shadow:0 1px 2px rgba(40,30,20,.04),0 16px 38px -24px rgba(60,44,20,.34)}
.mock-chrome{
  display:flex;align-items:center;gap:7px;padding:9px 12px;
  background:var(--panel-2);border-bottom:1px solid var(--hair);
}
.mock-dot{width:8px;height:8px;border-radius:50%;background:var(--border);flex:none}
.mock-url{
  margin-left:8px;font-size:12px;color:var(--faint);letter-spacing:.04em;
  background:var(--paper);border:1px solid var(--hair);border-radius:100px;
  padding:3px 12px;
}
.mock-body{padding:18px}
/* ⚠⚠ THIS RULE HAD NEVER ONCE RENDERED, AND IT WAS FLAGGED FOR THREE ROUNDS
   BEFORE ANYONE FIXED IT. `.mock-title` is (0,1,0); the section's own
   `.section.deep .feature-art p` is (0,2,1) and it sets `font-size:17px`. So
   every one of these headings has been drawing at SEVENTEEN pixels — the same
   size as the mock's body text — while the file said 9.5. That is most of why
   the mocks read like slides rather than like app screens: in a real interface
   a section label is the smallest type on the panel, not the largest.
   ⚠ FIXED BY DELETING THE BLANKET RULE, not by out-specifying it — see the long
   note at the top of this file where `.section.deep .feature-art p` used to be.
   Out-specifying would have left the trap armed for the next component.
   ⚠ 11px, NOT THE 9.5 THE FILE CLAIMED. 9.5px uppercase at .14em tracking is
   below what these mocks can carry at their rendered scale — the drawing is
   shrunk by `.feature-art`'s perspective transform on top of it. 11 is the
   size it should have said all along. */
.mock-title{
  font-family:var(--ui);font-size:11px;font-weight:700;letter-spacing:.14em;
  text-transform:uppercase;color:var(--faint);margin:0 0 12px;line-height:1.4;
}
.mock-title + .mock-title{margin-top:22px}

.mock-row{
  display:flex;align-items:center;gap:12px;padding:10px 0;
  border-bottom:1px solid var(--hair);
}
.mock-row:last-child{border-bottom:0}
.mock-row .grow{flex:1;min-width:0}
.mock-row .nm{font-weight:600;display:block;white-space:nowrap;overflow:hidden;
  text-overflow:ellipsis}
.mock-row .sub{color:var(--faint);font-size:11px;display:block}
.num{font-variant-numeric:tabular-nums;font-weight:700;white-space:nowrap}
.num.pos{color:var(--forest)}
.num.neg{color:var(--danger)}

.pill{
  font-size:11px;font-weight:700;letter-spacing:.1em;text-transform:uppercase;
  padding:3px 9px;border-radius:100px;white-space:nowrap;flex:none;
}
.pill.ok{background:var(--forest-soft);color:var(--forest)}
.pill.warn{background:var(--gold-soft);color:var(--gold)}
.pill.bad{background:var(--danger-soft);color:var(--danger)}
.pill.info{background:var(--accent-soft);color:var(--accent)}

.mock-stats{display:grid;grid-template-columns:repeat(auto-fit,minmax(96px,1fr));
  gap:1px;background:var(--hair);border:1px solid var(--hair);border-radius:var(--radius);
  overflow:hidden;margin-bottom:16px}
.mock-stat{background:var(--panel);padding:12px 13px}
.mock-stat .k{font-size:11px;font-weight:700;letter-spacing:.12em;text-transform:uppercase;
  color:var(--faint);display:block}
.mock-stat .v{font-family:var(--figure);font-size:26px;font-weight:600;line-height:1.15;
  display:block;margin-top:3px;font-variant-numeric:lining-nums tabular-nums}

.mock-bar{height:6px;border-radius:100px;background:var(--panel-2);overflow:hidden;
  border:1px solid var(--hair)}
.mock-bar > i{display:block;height:100%;background:var(--accent)}
.mock-bar.gold > i{background:var(--gold)}

/* the Ask Amlak field and its suggestion chips, as the real screen has them */
.mock-field{
  background:var(--panel-2);border:1px solid var(--border);border-radius:var(--radius);
  padding:10px 12px;font-size:12.5px;color:var(--ink);margin-bottom:10px;
}
.mock-chips{display:flex;flex-wrap:wrap;gap:6px;align-items:center;margin-top:10px}
.mock-chips b{font-size:12px;font-weight:600;color:var(--faint);margin-right:2px}
.mock-chips span{
  font-size:12px;font-weight:600;color:var(--accent);background:var(--accent-soft);
  border-radius:100px;padding:4px 10px;white-space:nowrap;
}

/* ── a compact data table inside a mock ─────────────────────────────
   Added 2026-08-22 so the homepage's pictures could be built from the app's
   REAL screens rather than invented rows (George: "take the images from the
   actual app, don't just throw stuff on there"). Two of them are near-copies
   of live tables — the rent roll (Tenant · twelve months · Collected, with
   the per-month figure in the cell) and the per-tenant CAM table (estimated
   vs actual, the live Difference, and the ⚖ Reconcile that settles it).
   ⚠ If either of those screens changes shape, these are a hand copy of it and
   will not follow. They are marketing, not a second implementation — but a
   wrong picture of your own product is worse than a vague one. */
.mock-tbl{width:100%;border-collapse:collapse;font-size:11.5px}
.mock-tbl th{
  font-size:11px;font-weight:700;letter-spacing:.1em;text-transform:uppercase;
  color:var(--faint);text-align:left;padding:0 0 7px;border-bottom:1px solid var(--hair);
  white-space:nowrap;
}
.mock-tbl td{padding:9px 0;border-bottom:1px solid var(--hair);vertical-align:top}
.mock-tbl tr:last-child td{border-bottom:0}
.mock-tbl td+td,.mock-tbl th+th{padding-left:9px}
.mock-tbl .num,.mock-tbl th.num{text-align:right;font-variant-numeric:tabular-nums;
  white-space:nowrap}
.mock-tbl .nm{font-weight:600;display:block}
.mock-tbl .sub{display:block;color:var(--faint);font-size:12px;margin-top:2px;
  font-variant-numeric:tabular-nums}
.mock-tbl tr.tot td{border-top:1px solid var(--border);border-bottom:0;font-weight:700}
/* A month that stepped up, and one that came in under the bill. */
.mock-tbl .up{color:var(--forest);font-weight:700}
.mock-tbl .short{color:var(--gold);font-weight:700}
.mock-raise{
  display:inline-block;margin-top:5px;font-size:12px;font-weight:700;letter-spacing:.06em;
  color:var(--gold);background:var(--gold-soft);border-radius:100px;padding:2px 8px;
}
.mock-raise.ok{color:var(--forest);background:var(--forest-soft)}
/* the ⚖ Reconcile action, exactly as the table offers it */
.mock-act{
  display:inline-block;font-size:11.5px;font-weight:700;letter-spacing:.06em;
  border:1px solid var(--border);border-radius:var(--radius);padding:3px 8px;
  color:var(--ink);background:var(--panel-2);white-space:nowrap;
}

/* ══ FIVE MOCKS, FIVE DRAWINGS ════════════════════════════════════════════
   Everything from here to `.mock-steps` was built on 2026-08-24, when George
   said *"every visualization on the features page should be unique to its
   description… it looks exactly the same as the AI lease extraction."* Four of
   the eleven mocks were `mock-row` label/value lists, so a visitor scanning the
   page saw one drawing four times.

   ⚠ THE TEST IS NOT "IS EACH ONE GOOD", IT IS "CAN ANY TWO BE MISTAKEN FOR EACH
   OTHER." Before adding a sixth thing here, check it against the ten already on
   the page: a two-pane document (lease extraction), a proportional bar
   (CAM), a bank table, a month grid (ledger), a proportional-gap timeline
   (notifications), a compose window (announcements), a form-grid certificate
   plus term bars (insurance), signing steps and a pad (e-signature), a
   spreadsheet (exports), a marked-up page beside severity flags (review), and
   chips-and-answer (Ask). That list is the constraint.
   ⚠ NO NEW COLOUR AND NO NEW TYPE SCALE. All of this is `--gold` / `--accent` /
   `--forest` / `--danger` and their `-soft` pairs, at the sizes the mocks
   already use. The uniqueness is meant to come from SHAPE. */

/* ── lease extraction: the page, the gutter, the fields ──────────────
   ⚠ THE ARROW LIVES IN THE GAP AND IS ANCHORED TO THE HIGHLIGHT, not to the
   grid. `.lx-hit::after` sits at `right:calc(-1 * var(--lx-gap) …)`, so the two
   panes can change width freely and the arrow stays on the clause it points at.
   Anchoring it to the column instead would need the highlight's height known in
   CSS, which it is not. */
.lx{--lx-gap:20px;display:grid;grid-template-columns:1fr 1fr;gap:var(--lx-gap);padding:16px}
.lx-page{
  position:relative;background:var(--panel-2);border:1px solid var(--hair);
  border-radius:4px;padding:11px 12px 12px;font-size:12px;line-height:1.55;
  color:var(--faint);
}
.lx-fn{
  display:block;font-family:var(--ui);font-size:11px;font-weight:700;letter-spacing:.1em;
  text-transform:uppercase;color:var(--faint);margin-bottom:8px;
  padding-bottom:6px;border-bottom:1px solid var(--hair);
  white-space:nowrap;overflow:hidden;text-overflow:ellipsis;
}
.lx-page p{margin:0 0 7px}
.lx-page p:last-child{margin-bottom:0}
/* the clause the field on the right came from */
.lx-hit{
  position:relative;color:var(--ink);background:var(--gold-soft);
  box-shadow:inset 2px 0 0 var(--gold);
  border-radius:2px;padding:5px 6px;margin-inline:-6px;
}
.lx-hit b{font-weight:700}
.lx-hit::after{
  content:'→';position:absolute;top:50%;translate:0 -50%;
  right:calc(-1 * (var(--lx-gap) + 6px));width:calc(var(--lx-gap) + 12px);
  text-align:center;font-size:11px;line-height:1;color:var(--gold);
}
.lx-out{display:grid;align-content:start;gap:0}
.lx-f{
  display:grid;grid-template-columns:1fr auto;align-items:baseline;
  column-gap:8px;padding:7px 0;border-bottom:1px solid var(--hair);
}
.lx-f .k{font-size:12px;color:var(--faint)}
.lx-f .v{font-weight:700;font-size:11.5px;text-align:right;font-variant-numeric:tabular-nums}
.lx-f .c{
  grid-column:2;font-family:var(--ui);font-size:11.5px;font-weight:700;letter-spacing:.06em;
  color:var(--gold);text-align:right;
}
/* the one field whose clause is lit on the left */
.lx-f.on .v{color:var(--gold)}
.lx-f.on .c{background:var(--gold-soft);border-radius:100px;padding:1px 6px;justify-self:end}
.lx-done{
  display:flex;align-items:baseline;justify-content:space-between;gap:10px;
  margin-top:9px;font-family:var(--ui);font-size:11px;font-weight:700;
  letter-spacing:.09em;text-transform:uppercase;
}
.lx-done b{color:var(--forest)}
.lx-done span{color:var(--faint)}

/* ── deadline notifications: a rail whose GAPS ARE THE DAYS ──────────
   ⚠ `--g` IS THE WHOLE IDEA. Each item's top margin is its distance from the
   one above in days, scaled. Equalise these and the timeline silently becomes
   the list it replaced. The scale is small on purpose: 74 days at 1px/day would
   push the last item off the mock. */
.rw{list-style:none;margin:0;padding:0 0 0 2px;position:relative}
/* ⚠ THE RAIL IS ONE LINE ON THE LIST, NOT A SEGMENT PER ITEM. It was
   `.rw-i::before{top:-100%}` — a percentage of the ITEM's own height, which is
   not the gap above it, so the rail over-ran on short items and fell short on
   tall ones. Drawn once here it is continuous by construction, whatever the
   `--g` gaps do. It stops half a node short at each end so the first and last
   dots sit ON the line rather than in the middle of it. */
.rw::before{
  content:'';position:absolute;left:5px;top:14px;bottom:14px;width:1px;
  background:var(--hair);
}
.rw-i{position:relative;display:grid;grid-template-columns:38px 1fr;gap:11px;
  padding-left:16px;margin-top:calc(var(--g,0) * 1.05px + 12px)}
.rw-i:first-child{margin-top:0}
.rw-i::after{
  content:'';position:absolute;left:0;top:calc(50% - 3.5px);
  width:7px;height:7px;border-radius:50%;background:var(--faint);
}
.rw-i.bad::after{background:var(--danger)}
.rw-i.warn::after{background:var(--gold)}
.rw-i.ok::after{background:var(--forest)}
/* ⚠ NO `tabular-nums` HERE. Cormorant's tabular figures are far wider than its
   proportional ones, and at 17px "12" and "47" read as "1 2" and "4 7" — two
   digits with a space between them. Nothing in this column needs to align
   digit-for-digit with anything, so the alignment the property buys is worth
   nothing and the spacing it costs is visible. */
.rw-d{
  text-align:right;font-family:var(--figure);font-size:18px;font-weight:600;
  font-variant-numeric:lining-nums;
  line-height:1.05;
}
.rw-d small{
  display:block;font-family:var(--ui);font-size:11px;font-weight:700;
  letter-spacing:.1em;text-transform:uppercase;color:var(--faint);margin-top:2px;
}
.rw-i.bad .rw-d{color:var(--danger)}
.rw-i.warn .rw-d{color:var(--gold)}
.rw-b{display:block;min-width:0}
.rw-b b{display:block;font-size:12px;font-weight:700;line-height:1.3}
.rw-b span{display:block;font-size:12px;color:var(--faint);line-height:1.4;margin-top:2px}
/* ⚠ THE CONSEQUENCE LINE, NOT A REPEAT OF THE TITLE. The feature's claim is
   that alerts are ranked by what missing them costs; without this line the mock
   is a countdown list and the claim is unsupported. */
.rw-b .rw-cost{
  margin-top:4px;font-family:var(--ui);font-size:12px;font-weight:700;
  letter-spacing:.04em;text-transform:none;color:var(--ink);
}
.rw-i.bad .rw-cost{color:var(--danger)}
.rw-i.warn .rw-cost{color:var(--gold)}
.rw-i.ok .rw-cost{color:var(--forest)}

/* ── tenant announcements: a compose window ──────────────────────────
   ⚠ `.cmp-send` IS THE ONLY FILLED CONTROL IN ANY MOCK ON THIS PAGE, and that
   is what George asked for ("add a send button"). Keep it the only one: a
   second filled button somewhere else and this stops being the block with the
   button in it. */
.cmp-head{
  display:flex;align-items:center;justify-content:space-between;gap:10px;
  padding:10px 14px;background:var(--panel-2);border-bottom:1px solid var(--hair);
  font-family:var(--ui);font-size:11px;font-weight:700;letter-spacing:.12em;
  text-transform:uppercase;color:var(--faint);
}
.cmp-head b{color:var(--accent);letter-spacing:.08em}
.cmp-f{
  display:flex;align-items:baseline;gap:10px;padding:8px 0;
  border-bottom:1px solid var(--hair);
}
.cmp-f .k{
  flex:none;width:44px;font-family:var(--ui);font-size:11px;font-weight:700;
  letter-spacing:.12em;text-transform:uppercase;color:var(--faint);
}
.cmp-to{display:flex;flex-wrap:wrap;gap:4px}
.cmp-to i{
  font-style:normal;font-size:12px;font-weight:600;color:var(--accent);
  background:var(--accent-soft);border-radius:100px;padding:2px 8px;
}
.cmp-to i.more{color:var(--faint);background:var(--panel-2);border:1px solid var(--hair)}
.cmp-subj{font-weight:700;font-size:12px}
.cmp-body{padding:12px 0 2px;font-size:11.5px;line-height:1.55;color:var(--ink)}
.cmp-ai{
  display:flex;align-items:flex-start;gap:6px;margin-top:10px;
  font-size:12px;line-height:1.45;color:var(--gold);
}
.cmp-ai i{font-style:normal;flex:none}
.cmp-foot{
  display:flex;align-items:center;justify-content:space-between;gap:12px;
  margin-top:14px;padding-top:12px;border-top:1px solid var(--hair);
}
.cmp-note{font-size:12px;color:var(--faint);line-height:1.35}
.cmp-send{
  flex:none;background:var(--accent);color:#FBF8F1;border-radius:var(--radius);
  padding:8px 14px;font-family:var(--ui);font-size:11px;font-weight:700;
  letter-spacing:.1em;text-transform:uppercase;white-space:nowrap;
}

/* ── insurance: the certificate, then the contracts as time ──────────
   ⚠ A FORM GRID IS THE POINT. A certificate of liability is a boxed form, and
   nothing else on this page is one — the uniqueness is the ruled cells, not the
   words in them. Keep the hairline grid if the fields ever change. */
.coi{border:1px solid var(--border);border-radius:4px;overflow:hidden;margin-bottom:20px}
.coi-head{
  display:flex;align-items:center;justify-content:space-between;gap:10px;
  padding:8px 11px;background:var(--panel-2);border-bottom:1px solid var(--border);
  font-family:var(--ui);font-size:11px;font-weight:700;letter-spacing:.11em;
  text-transform:uppercase;color:var(--ink);
}
.coi-head b{color:var(--forest)}
.coi-grid{display:grid;grid-template-columns:1fr 1fr;gap:1px;background:var(--hair)}
.coi-c{background:var(--panel);padding:8px 11px;min-width:0}
.coi-c.wide{grid-column:1 / -1}
.coi-c .k{
  display:block;font-family:var(--ui);font-size:11px;font-weight:700;
  letter-spacing:.11em;text-transform:uppercase;color:var(--faint);
}
.coi-c .v{
  display:block;margin-top:3px;font-size:11.5px;font-weight:700;
  font-variant-numeric:tabular-nums;
}
.coi-c .v.yes{color:var(--forest);font-size:12px}
.coi-c.bad{background:var(--danger-soft)}
.coi-c.bad .v{color:var(--danger)}
.coi-stamp{
  display:inline-block;margin-top:5px;font-family:var(--ui);font-size:11px;
  font-weight:700;letter-spacing:.08em;text-transform:uppercase;color:var(--danger);
  border:1px solid var(--danger);border-radius:3px;padding:2px 6px;
  /* the tilt is what makes it read as a stamp rather than as another badge */
  rotate:-1.5deg;
}
/* a contract as a length of time, with the window that costs money shaded on it */
.ctr{padding:10px 0;border-bottom:1px solid var(--hair)}
.ctr:last-child{border-bottom:0;padding-bottom:0}
.ctr-top{display:flex;align-items:baseline;justify-content:space-between;gap:10px}
.ctr-top .nm{font-weight:600;font-size:11.5px}
.ctr-top .num{font-size:11px}
/* ⚠ THE ELAPSED PORTION IS FILLED. Without it the bar is a pale track with a
   gold patch on it and nothing says which way time runs; with it the eye reads
   left-to-right, sees where today is, and sees that the window is still ahead.
   That reading is the entire reason this is a bar rather than a date. */
.ctr-bar{
  position:relative;height:8px;margin:7px 0 6px;border-radius:100px;
  background:var(--panel-2);border:1px solid var(--hair);overflow:hidden;
}
.ctr-bar::before{
  content:'';position:absolute;left:0;top:0;bottom:0;width:var(--t);
  background:var(--accent-soft);
}
.ctr-win{
  position:absolute;top:0;bottom:0;left:var(--a);right:calc(100% - var(--b));
  background:var(--gold);opacity:.85;
}
.ctr-step{position:absolute;top:0;bottom:0;left:var(--s);width:2px;background:var(--forest)}
/* ⚠ `--t` IS TODAY AND IT MUST SIT BEFORE `--a`. The whole statement of this
   picture is "the window has not closed yet"; a marker past the window says the
   opposite and nobody would notice in a screenshot. */
.ctr-now{
  position:absolute;top:-3px;bottom:-3px;left:var(--t);width:1.5px;
  background:var(--ink);
}
.ctr-foot{
  display:flex;align-items:center;justify-content:space-between;gap:10px;
  font-size:11.5px;color:var(--faint);
}
.ctr-foot b{font-family:var(--ui);font-size:11px;letter-spacing:.09em;text-transform:uppercase}
.ctr-foot b.warn{color:var(--gold)}
.ctr-foot b.info{color:var(--accent)}

/* ── CAM: the split, drawn ───────────────────────────────────────────
   ⚠ THE SEGMENTS ARE `--w` AND THEY SUM TO 100. They are a hand-drawn twin of
   the percentages in the table below; if one moves, move the other. */
.shr{display:flex;height:26px;border-radius:4px;overflow:hidden;margin-bottom:16px;
  border:1px solid var(--hair)}
.shr i{
  flex:0 0 var(--w);display:grid;place-items:center;min-width:0;
  font-style:normal;font-size:11.5px;font-weight:700;letter-spacing:.04em;
  border-right:1px solid rgba(251,248,241,.5);
}
.shr i:last-child{border-right:0}
.shr i b{padding:0 4px;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}
.shr .s1{background:var(--gold);color:#FBF8F1}
.shr .s2{background:var(--forest);color:#FBF8F1}
.shr .s3{background:var(--accent);color:#FBF8F1}
.shr .rest{background:var(--panel-2);color:var(--faint);font-weight:600}
/* over/under against the estimate, from a common centre line */
.vb{display:block;position:relative;height:3px;margin:4px 0 3px;background:var(--hair)}
.vb i{position:absolute;top:-1px;height:5px;width:var(--v)}
.vb.over i{left:50%;background:var(--gold)}
.vb.under i{right:50%;background:var(--forest)}

/* ── the signer's four steps ────────────────────────────────────────
   The real /sign page states them up front, before the document, so the
   "tap where your signature goes" step is known about before it is reached. */
.mock-steps{list-style:none;margin:0;padding:0;display:grid;gap:10px}
.mock-steps li{display:flex;align-items:center;gap:10px;font-size:12.5px;font-weight:600}
.mock-steps i{
  width:17px;height:17px;border-radius:50%;flex:none;display:grid;place-items:center;
  font-size:12px;font-style:normal;font-weight:700;
  background:var(--forest-soft);color:var(--forest);
}
.mock-steps li.todo{color:var(--faint);font-weight:500}
.mock-steps li.todo i{background:var(--panel-2);color:var(--faint);
  border:1px solid var(--border)}
.mock-pad{
  margin-top:14px;border:1px dashed var(--border);border-radius:var(--radius);
  background:var(--panel-2);padding:10px 12px 6px;
}
.mock-pad svg{width:120px;height:34px;display:block}
.mock-pad .v-sign{stroke:var(--ink);stroke-width:1.6;fill:none;stroke-linecap:round}
.mock-pad span{display:block;font-size:11.5px;color:var(--faint);margin-top:2px}

.mock-doc{
  background:var(--panel-2);border:1px solid var(--hair);border-radius:var(--radius);
  padding:12px 14px;font-size:11.5px;color:var(--muted);line-height:1.55;
}
.mock-doc mark{background:var(--gold-soft);color:var(--ink);padding:1px 3px;
  border-radius:2px;font-weight:600}
.mock-cite{font-size:12px;color:var(--faint);margin-top:8px;display:block}

.mock-flag{display:flex;gap:11px;padding:11px 0;border-bottom:1px solid var(--hair)}
.mock-flag:last-child{border-bottom:0}
.mock-flag .dot{width:7px;height:7px;border-radius:50%;margin-top:7px;flex:none}
.mock-flag .dot.bad{background:var(--danger)}
.mock-flag .dot.warn{background:var(--gold)}
.mock-flag .dot.ok{background:var(--forest)}
.mock-flag b{display:block;font-weight:600}
.mock-flag span{color:var(--faint);font-size:11px;line-height:1.45}

/* a card floating over the mock, the way a notification would — the wrapper
   is still used by the features/compare/about heroes. (The homepage's four
   .float cards became the hub's ten .hcard branches, 2026-08-22.) */
.mock-stack{position:relative}


/* ══ THE HERO PANELS BORROW THE BRANCH CARDS' GLASS ════════════════════════
   George, 2026-08-24, about both of them: *"give it the look that the cards
   have in the main hero on the features, like the cards with the branches."*

   ⚠ THIS IS THE SAME RECIPE AS `.hcard a`, DELIBERATELY, AND IT IS THE ONE
   PLACE IT IS COPIED. Read the long note there before touching either: the
   numbers are a published material (tint alpha .10–.35, blur 8–24px, border
   alpha ~.2) and two earlier passes got the word "matt" right and the numbers
   wrong. The tint is BRIGHTNESS, never more alpha — hover on a matt surface
   means more light through it, not more paint on it.

   ⚠⚠ WHY THE TINT IS HIGHER HERE (.62 against the cards' .16), and this is the
   whole difference: the branch cards sit over the home page's RAKING LIGHT, a
   canvas of moving gold that gives the blur something to blur. About and
   Compare have no canvas — their ground is near-flat paper plus a 10% plat
   grid — so a .16 tint over it is a pane with nothing behind it, and the text
   inside loses its contrast for no visible gain. The blur still does real work
   on the traces passing underneath, which is what ties the panel to the page.
   ⚠ IT MUST STAY OPAQUE ENOUGH TO READ. Both panels carry body copy, unlike a
   branch card's two lines. Lowering the tint to match the cards exactly is the
   change that quietly fails a contrast check.
   ⚠ NO `opacity`, `filter` OR `mix-blend-mode` ON THESE OR ON ANY ANCESTOR.
   Each makes a backdrop root and the `backdrop-filter` below then samples
   nothing — the flat-panel bug that took a round to find on the home page. */
.stack-viz,.ydot{
  /* ⚠ .62 → .93 — same reason as `.hcard a`. These carry body copy. */
  background:rgba(252,250,244,.93);
  -webkit-backdrop-filter:blur(22px) saturate(1.3) brightness(1.04);
  backdrop-filter:blur(22px) saturate(1.3) brightness(1.04);
  border:1px solid rgba(255,255,255,.5);
  border-radius:var(--glass-r);
  /* the same three lit edges as a branch card: a bright top, a lit left, and a
     warm underside where the paper shows through */
  box-shadow:inset 0 1.5px 0 rgba(255,255,255,.75),
             inset 1px 0 0 rgba(255,255,255,.3),
             inset 0 -1px 0 rgba(122,94,42,.16),
             0 2px 4px rgba(40,30,20,.04),
             0 30px 68px -32px rgba(60,44,20,.44);
}
/* ⚠ AND THE FALLBACK, for the same reason the cards have one: with no
   `backdrop-filter` the tint is all there is, so it has to be opaque on its
   own or the panel reads as a grey wash over the page. */
/* ⚠ Its `@supports not` fallback is gone too — .93 is the value above now. */

/* ══ a year of one building, as 365 days ═══════════════════════════════════
   The About hero's picture, third attempt (2026-08-24). The first was a
   five-row readout tracing a figure back to its clause; the second was a
   twelve-month strip beside six labelled obligations, whose labels were still
   the content. Both were things to READ. George, both times: *"people need to
   glance at something and have it capture their attention, make it a visual not
   a readout."*

   ⚠ THE MARKED DAYS DIFFER IN SIZE AND SHAPE, NOT ONLY IN COLOUR, so the
   picture survives a grayscale print and a colour-blind reader. A gold or red
   dot is also bigger and squarer than the 361 that are nothing.
   ⚠ 52 COLUMNS IS THE HARD CASE AT 390px, and the cell size is derived rather
   than chosen: `--d` is a `min()` of a fixed size and 1/52nd of the container,
   so the grid fits by construction at every width and can never force a
   horizontal scroll. A year that wraps to a second block is not a year.
   ⚠ IT IS NOT A CONTRIBUTION GRAPH. No shading scale, no legend, no hover, no
   tooltip. Exactly two states: a day, and a day that costs money. Adding a
   third state is how this becomes a chart nobody reads.
   ⚠ THE GRID IS BUILT BY `site.js`. 365 hand-written spans is a page nobody can
   edit; a `.yd-grid` left empty by a failed script leaves the caption and the
   count, both of which still say the thing. */
.ydot{position:relative;display:grid;gap:16px;margin:0;padding:30px 28px}
/* ⚠ BIGGER THAN IT WAS (George, 2026-08-24: "make that description bigger").
   It was 10.5px / 13px, which is caption scale — correct for a legend under a
   chart and wrong for the only sentence a picture gets. */
/* ⚠⚠ IT IS A TITLE NOW, NOT A LEGEND (George, 2026-08-24, naming it among six
   headings that should carry "the same bold as the one on the features titles
   page"). It was 11.5px UI-font uppercase at `--faint` — the treatment this site
   gives a legend under a chart, which is exactly what it looked like: a label on
   a picture rather than the picture's own line. The other five he named are real
   headings, so this one becomes one: display face, 600, ink.
   ⚠ IT IS STILL A `<figcaption>` AND MUST STAY ONE. It titles the figure it is
   inside; promoting it to an `<h3>` would put a heading in the document outline
   that belongs to a picture, and the section above it already has the h2. The
   weight comes from the type, not from the tag. */
.yd-cap{font-family:var(--display);font-size:clamp(22px,2.4vw,28px);font-weight:600;
  letter-spacing:-.01em;line-height:1.15;color:var(--ink)}
.yd-foot{margin:0;font-size:15.5px;color:var(--muted);line-height:1.55;
  border-top:1px solid rgba(122,94,42,.14);padding-top:14px}
.yd-foot b{display:block;color:var(--ink);font-weight:600;font-size:16.5px;margin-bottom:2px}
/* ⚠ `grid-auto-flow:column` WITH A FIXED ROW COUNT is what makes the days run
   down each week and then across, the way a calendar does. Flowing by row
   instead would put Jan 1–52 on the top line, which is not a week and not a
   year, just 365 dots.
   ⚠⚠ THE ROWS ARE `auto` AND THE SQUARENESS COMES FROM `aspect-ratio`, WHICH IS
   NOT A STYLE CHOICE. `--d` contains a percentage, and a percentage resolves
   against the axis it is USED on: in `grid-auto-columns` that is the grid's
   width (definite, fine), but in `grid-template-rows` it is the grid's HEIGHT,
   which is determined by its own content — circular, so it resolves to zero.
   The first build of this shipped `grid-template-rows:repeat(7,var(--d))` and
   measured 7.43px wide by 0px tall: 365 dots, all of them invisible, and no
   error anywhere. One custom property cannot serve both axes when it holds a
   percentage. */
.yd-grid{
  /* ⚠ NO `min()` CAP ANY MORE (George: "you can also make that bigger").
     The cell is whatever 1/52nd of the panel allows, so widening the column
     widens the days instead of adding whitespace beside them. */
  --d:calc((100% - 51 * 2px) / 52);
  display:grid;grid-auto-flow:column;grid-template-rows:repeat(7,auto);
  grid-auto-columns:var(--d);gap:2px;justify-content:space-between;
}
.yd-grid i{display:block;aspect-ratio:1;border-radius:1px;background:rgba(27,24,19,.15)}
/* the days that bite. `scale` rather than a bigger cell, so the grid's geometry
   is untouched and one heavy day cannot shift the week beside it.
   ⚠ SIZE AND SHAPE, NOT ONLY COLOUR — a marked day is bigger and square-cornered
   as well as gold, so the picture survives a grayscale print and a colour-blind
   reader. That is the whole device; do not reduce it to a hue. */
.yd-grid i.hot{background:var(--gold);border-radius:0;scale:1.4}
.yd-grid i.bad{background:var(--danger);border-radius:0;scale:1.4}
/* ⚠⚠ THE EIGHT JUMP; THE 364 DO NOT MOVE AT ALL. This is the correction of a
   round that had it exactly backwards (George, 2026-08-25: "undo what you did to
   the little things add up visual, make all the other boxes grey again — I meant
   I wanted the COLORED boxes to pulse randomly, like imagine a little
   notification jump pulse where they enlarge slightly and go back, but make the
   pattern random").
   What was here was `ydBreathe`: 364 grey days fading between 1 and .34 on random
   clocks while the eight marked days sat still. It was defensible and it was the
   wrong half — the eight are the only thing on this panel with anything to say,
   so they are the only thing that should ask for attention. The greys are the
   ground the message sits on and they are back to dead flat.
   ⚠ 0% AND 100% MUST BE 1.4, the value `.hot`/`.bad` already declare. A keyframe
   that starts anywhere else makes every marked box snap at each loop boundary.
   ⚠ THE UNDERSHOOT AT 16% IS WHAT MAKES IT A JUMP. Up fast, slightly past the
   resting size on the way back, then settle: that is the shape of a notification
   badge landing. Straight up and straight down is a throb.
   ⚠ AND THE JUMP IS A SMALL FRACTION OF A LONG CYCLE — 26% of a 4 to 8 second
   period, so a box is at rest roughly three quarters of the time. A marked day
   that is always moving is a marked day nobody looks at twice.
   ⚠ `scale`, NOT `transform:scale()`. `.hot`/`.bad` use the standalone property
   so the grid's geometry is untouched and one heavy day cannot shift the week
   beside it; writing a transform here would be a second mechanism for one job.
   ⚠ `yearPulse()` in site.js sets a random delay AND a random period per box —
   both, for the same reason as before: one shared duration with staggered delays
   resolves into a travelling wave, which is the opposite of random.
   ⚠⚠ THE `.pulse` CLASS IS THE REDUCED-MOTION GATE AND IT IS NOT DECORATION.
   `yearPulse()` lives below `if (reduce) return`, so a visitor with the setting
   on never gets the class and the eight simply sit there — which is the whole
   picture, intact, minus the movement. Put this animation straight on `.hot` /
   `.bad` and it runs for everyone, because a stylesheet cannot see that guard.
   The old `.breathe` class did the same job; this is that mechanism, moved. */
.yd-grid.pulse i.hot,.yd-grid.pulse i.bad{
  animation:ydJump var(--yb-t,6s) ease-out var(--yb-d,0s) infinite;
}
@keyframes ydJump{
  0%,100%{scale:1.4}
  6%     {scale:1.85}
  16%    {scale:1.32}
  26%    {scale:1.4}
}
@media (max-width:680px){
  .ydot{padding:22px 18px}
  .yd-grid{--d:calc((100% - 51 * 1px) / 52);gap:1px}
}

/* ══ five subscriptions, or one ════════════════════════════════════════════
   The Compare hero's picture (2026-08-24). It replaced a five-row readout of
   the spreadsheet failure mode, which was a thing to READ where the page needed
   a thing to SEE — George: "in this day people need to glance at something and
   have it capture their attention, make it a visual not a readout."

   ⚠ THE DRAWINGS ARE THE HUB'S IDIOM, NOT A NEW ONE: same 1.5px strokes, same
   `.v-*` class names, same gold. A third drawing style on one site is how a
   page stops looking like it belongs to the product it is selling.
   ⚠ BUT THE STROKE IS FLAT `--gold`, NOT `url(#foil)`, AND THAT IS NOT A
   SHORTCUT. The foil gradient lives in an inline `<defs>` inside index.html and
   **SVG ids are document-scoped** (the note above that block says so) — a
   `url(#foil)` on this page resolves to nothing, and a stroke that resolves to
   nothing on a `fill:none` shape draws an invisible icon. The alternative was a
   second copy of the paint server on every page that wants one, which is the
   drift the "defined once" note exists to prevent. At 34x28 a vertical ramp is
   indistinguishable from its own midpoint anyway.
   ⚠ AND THE ROW WRAPS RATHER THAN SCROLLS. Five labels at 390px do not fit on
   one line and a sideways scroll inside a hero is a thing nobody finds. */
/* ⚠ `width:100%` IS LOAD-BEARING AND `margin:0 auto` ALONE WAS NOT ENOUGH.
   An auto inline margin on a grid item DISABLES its default stretch, so this
   panel was sizing to its own max-content — which meant the glass box grew
   and shrank with whatever the widest thing inside it happened to be, and
   narrowing the card below narrowed the panel with it. Stated width, capped,
   centred: the frame is now a fixed thing the contents sit inside. */
.stack-viz{position:relative;display:grid;justify-items:center;gap:0;
  width:100%;max-width:470px;margin-inline:auto;padding:28px 26px 26px}
/* ⚠ A GRID, NOT A WRAPPING FLEX ROW, AND THAT IS NOT A STYLE PREFERENCE. It was
   `flex-wrap` and at 1440 it broke 4 + 1 — one tool orphaned on a second line,
   which destroys the only thing the picture says (a stack of them, converging
   on one). A grid cannot orphan: the count is in the template.
   ⚠ 3 × 3 SINCE THE LIST GREW TO NINE (2026-08-24). Nine across at this width
   is nine 45px columns and every label wraps to three lines. The same template
   holds on a phone, so the picture never re-flows into a different arrangement
   of itself. */
.sv-row{display:grid;grid-template-columns:repeat(3,minmax(0,1fr));gap:18px 10px}
.sv-tool{display:grid;justify-items:center;align-content:start;gap:6px;text-align:center}
.sv-tool svg{width:30px;height:25px;display:block;opacity:.6}
.sv-tool span{font-family:var(--ui);font-size:12px;letter-spacing:.03em;color:var(--faint);
  line-height:1.3}
/* Three short rules converging, one under each column of the grid above, so the
   fan stays centred on the card below at every width. */
.sv-arrows{display:grid;grid-template-columns:repeat(3,minmax(0,1fr));gap:10px;
  margin:16px 0 14px;height:26px;justify-items:center}
.sv-arrows i{display:block;width:1px;height:26px;
  background:linear-gradient(180deg,rgba(148,102,27,.05),rgba(148,102,27,.45))}
.sv-arrows i:nth-child(1){transform:rotate(20deg)}
.sv-arrows i:nth-child(3){transform:rotate(-20deg)}
@media (max-width:680px){
  .sv-row{gap:14px 8px}
  .sv-arrows i:nth-child(n){transform:none}
  .sv-arrows{height:18px}
  .sv-arrows i{height:18px}
}
/* ⚠ NARROWER THAN THE GLASS AROUND IT, on purpose. Once `.stack-viz` became a
   glass panel (2026-08-24) this card filled its entire inner width, so the glass
   read as a 1px frame welded to a solid box rather than as a surface the card is
   resting on. The gap either side is what makes the two read as two things.
   ⚠ AND IT STAYS OPAQUE. Everything above it in this picture is a line drawing
   at 60% opacity; the card is the one solid object, because the whole argument
   is that nine faint things resolve into one real one. */
.sv-one{display:grid;justify-items:center;gap:9px;text-align:center;
  padding:20px 22px;border-radius:var(--glass-r-sm);
  background:var(--panel);border:1px solid var(--border);box-shadow:var(--shadow-lift);
  max-width:288px}
.sv-one b{font-family:var(--figure);font-size:26px;font-weight:600;line-height:1}
.sv-one .sv-note{font-size:13px;color:var(--muted);line-height:1.5}
/* ⚠ SIZE ONLY. The SURFACE comes from the shared `.nav-mark,.sv-mark,.hub-core`
   rule far above — this was the one mark on the site that never got the frosted
   treatment (flat `--accent`, no border, no inner light, no gloss) and it sat
   at the bottom of the Compare hero next to a nav that had it. George,
   2026-08-25: "change the way the A looks everywhere to the way it currently
   looks on the features page." Do not put a `background` back here. */
.sv-mark{width:34px;height:34px}
.sv-mark img{width:74%;height:74%}
.stack-viz .v-paper,.stack-viz .v-line,.stack-viz .v-sign,.stack-viz .v-shield{
  fill:none;stroke:var(--gold);stroke-width:1.5;
  stroke-linecap:round;stroke-linejoin:round}

/* ── benefit cards ──────────────────────────────────────────────── */
.cards{display:grid;grid-template-columns:repeat(auto-fit,minmax(min(100%,248px),1fr));gap:1px;
  background:var(--border);border:1px solid var(--border);border-radius:var(--radius);
  overflow:hidden}
.card{background:var(--panel);padding:30px 26px}
.card .glyph{font-size:22px;line-height:1;display:block;margin-bottom:16px}
.card h3{font-size:21px;margin-bottom:9px}
.card p{margin:0;color:var(--muted);font-size:14.5px;line-height:1.6}

/* ── alternating feature blocks ─────────────────────────────────── */
.feature{
  display:grid;grid-template-columns:minmax(0,1fr) minmax(0,1.08fr);
  gap:64px;align-items:center;padding:72px 0;position:relative;
  /* Every hub card, and the nav's Features link, is an in-page anchor onto one
     of these blocks; without this the sticky nav sits on top of whichever
     heading the anchor lands on. --navh is 112 once the nav wraps to two rows,
     which is exactly when the overlap would be worst. */
  scroll-margin-top:calc(var(--navh) + 12px);
}
/* ⚠ THE SEPARATOR FADES AT BOTH ENDS. Eleven hard grey hairlines drawn edge to
   edge is what you do on flat paper; over a warm ground each one reads as a
   ruled line on a photograph. A warm rule that arrives and leaves gives the same
   separation without ever declaring an edge the ground does not have. */
.feature::before{
  content:'';position:absolute;top:0;left:0;right:0;height:1px;pointer-events:none;
  background:linear-gradient(90deg,transparent,rgba(148,102,27,.22) 14%,
                             rgba(148,102,27,.22) 86%,transparent);
}
.feature:first-of-type::before{display:none}
/* ⚠ THE POSITIONING CONTEXT FOR `.tele-back`, WHICH HANGS BELOW THIS COLUMN
   OUT OF FLOW — see the note at `.tele-back`. Without it the button resolves
   against `.feature` (which is also `position:relative`) and `top:100%` puts
   it under the whole block instead of under the last bullet. */
.feature-copy{position:relative}
.feature.flip .feature-copy{order:2}
.feature.flip .feature-art{order:1}
.feature h3{font-size:clamp(26px,3vw,34px);font-family:var(--display);margin-bottom:14px}
/* ⚠⚠ `.feature p{color:var(--muted);font-size:16px;margin:0}` WAS HERE AND IT
   WAS THE SECOND HALF OF THE SAME TRAP. It is the other leftover of the
   one-sentence deck each block used to carry, and at (0,1,1) it beat
   `.mock-title`, `.rev-foot` and every `p` inside a mock — so deleting the
   `.section.deep .feature-art p` blanket at the top of this file moved those
   headings from 17px to 16px instead of to the 11px and 10.5px they declare.
   Two blanket rules, one symptom, and each one hid the other.
   ⚠ NOTHING IN `.feature-copy` IS A `p` ANY MORE — the copy column is a
   heading, a tick list and a link. Every `p` inside a `.feature` is now in the
   art, and every one of them carries a class with its own size and margin.
   ⚠ SO: NO BARE ELEMENT SELECTORS UNDER `.feature`. If a paragraph is ever
   needed in the copy column again, give it a class. */
.ticks{list-style:none;margin:22px 0 0;padding:0;display:grid;gap:10px}
/* ⚠ NOT `display:flex` ON THE ITEM. It was, and it made the tick, the <strong>
   lead and the sentence after it into three separate flex items — so a bold
   lead and its own explanation stacked into two columns instead of running on
   as one line of prose. Fourteen of these across the site read fine only
   because every bold lead was short enough to fit its column; the first long
   one (on /consultation) exposed it. The marker is positioned out of flow now,
   so the text is a single paragraph at any length. */
.ticks li{position:relative;padding-left:24px;font-size:14.5px;line-height:1.5}
.ticks li::before{
  content:'✓';position:absolute;left:0;top:0;
  color:var(--accent);font-weight:700;font-size:13px;line-height:1.55;
}
.only-here{
  display:inline-flex;align-items:center;gap:7px;margin-top:20px;font-size:11px;
  font-weight:700;letter-spacing:.1em;text-transform:uppercase;color:var(--gold);
  background:var(--gold-soft);border-radius:100px;padding:5px 12px;
}

/* ── the comparison chart (Compare) ─────────────────────────────── */
/* ⚠⚠ THIS CHART WAS REPLACED BY SIX CARDS ON 2026-08-25 AND PUT BACK THE SAME
   DAY. George: *"the compare should be the same format as before: that big chart
   with the columns. i just mean make the format of that chart different like the
   cards, but i like the info just revert it back to the chart."*

   So the argument the cards made — that thirteen rows of ✓/✓ spend the page
   establishing that the competition is capable — LOST on the thing it was
   actually about. A chart earns its length by being scannable: a reader picks
   their own row and reads across, which is not something six paragraphs can do.
   What was worth keeping from that round was the SURFACE, and that is what this
   is: the chart, wearing the hero cards' glass.

   ⚠ THE PANE IS `.hcard a`'s TREATMENT, DELIBERATELY AND ALMOST VERBATIM. Same
   tint, same rim, same `--glass-r`, same drop shadow. It works here without the
   hero's moving light because the WARM GROUND IS ON `html`, fixed, for the whole
   document (see the note at `html{}` near the top) — there is something behind
   the pane on every page, not just the home one.
   ⚠ NO `backdrop-filter`. The hero's cards have one because a rake travels behind
   them; here nothing moves, and a blur of a still gradient is a composited layer
   that buys nothing.
   ⚠ AND THE PANE IS THE ONLY CARD ON DESKTOP. Giving each row its own rim as
   well would put fourteen competing edges inside one, which is the exact
   busy-ness the six-card round was trying to cure. The rows are separated by a
   hairline and lit on hover; the pane holds the outline. */
.cmp-pane{
  border-radius:var(--glass-r);
  background:rgba(250,247,239,.72);
  border:1px solid rgba(255,255,255,.22);
  box-shadow:inset 0 1.5px 0 rgba(255,255,255,.62),
             inset 1px 0 0 rgba(255,255,255,.24),
             inset 0 -1px 0 rgba(122,94,42,.16),
             0 1px 2px rgba(40,30,20,.03),
             0 22px 50px -30px rgba(60,44,20,.42);
  /* ⚠ `clip`, NOT `hidden`. The Amlak column's tint runs to the pane's edge and
     has to be cut to the rounded corner, but `overflow:hidden` would make the
     pane a scroll container in its own right — a second one nested inside the
     `.cmp-scroll` below, which is where the narrow band's scrolling actually
     belongs. `clip` cuts without ever scrolling. Where `clip` is unsupported the
     declaration is simply dropped and `.cmp-scroll`'s own radius still cuts the
     table, so nothing here depends on it. */
  overflow:clip;
}
.cmp-scroll{overflow-x:auto;border-radius:var(--glass-r)}
table.cmp{border-collapse:collapse;width:100%;min-width:700px;font-size:14px}
table.cmp th,table.cmp td{padding:15px 18px;text-align:left;vertical-align:top;
  border-bottom:1px solid var(--hair)}
/* The column heads take the cards' micro-label idiom — gold, spaced, uppercase —
   rather than the old grey one, so the chart and the hero cards share a voice. */
table.cmp thead th{
  font-family:var(--ui);font-size:11.5px;font-weight:700;
  letter-spacing:.13em;text-transform:uppercase;color:var(--gold);
  padding-top:19px;padding-bottom:15px;
  border-bottom:1px solid rgba(122,94,42,.18);
}
table.cmp thead th small{display:block;font-family:var(--ui);font-size:11.5px;
  font-weight:400;letter-spacing:0;text-transform:none;color:var(--proof);
  margin-top:5px;line-height:1.4}
/* ⚠ NO position:sticky HERE, deliberately. `.cmp-scroll` sets overflow-x:auto,
   which makes it a scroll container on BOTH axes for sticky purposes — so a
   sticky thead resolves against a box that never scrolls vertically, and simply
   travels off screen doing nothing. It looked like it worked because the header
   is on screen when the table starts. Every cell here carries its own text label
   rather than a bare tick, so a reader eight rows down still knows what the cell
   means without one. */
table.cmp tbody th{font-weight:600;font-size:14.5px;width:28%;color:var(--ink)}
table.cmp tbody th small{display:block;font-weight:400;color:var(--proof);font-size:12px;
  margin-top:3px;line-height:1.45}
/* ⚠ THE AMLAK COLUMN IS THE CARD INSIDE THE CHART. A lit band with the same
   rim light down its left edge and the same warm shadow down its right, so the
   eye reads it as a pane standing in front of the others rather than as a
   highlighted table column. `background-color` and not a gradient, because the
   hover below raises exactly this value and a gradient would fight it. */
table.cmp .us{
  background-color:rgba(255,255,255,.5);
  box-shadow:inset 1px 0 0 rgba(255,255,255,.5),inset -1px 0 0 rgba(122,94,42,.1);
  transition:background-color .25s;
}
table.cmp thead th.us{color:var(--accent);box-shadow:inset 1px 0 0 rgba(255,255,255,.5),
  inset -1px 0 0 rgba(122,94,42,.1),inset 0 1.5px 0 rgba(255,255,255,.62)}
/* The row lift. A `<tr>` cannot be transformed reliably across engines, so the
   card's hover reads here as light rather than as movement. */
table.cmp tbody tr{transition:background-color .25s}
table.cmp tbody tr:hover{background-color:rgba(255,255,255,.34)}
table.cmp tbody tr:hover .us{background-color:rgba(255,255,255,.68)}
table.cmp tbody tr:last-child th,table.cmp tbody tr:last-child td{border-bottom:0}
/* ⚠⚠ GROUP DIVIDERS — promoted 2026-08-30 from the localhost /design-options round
   (George picked "band + heavier weight"). One `<tr class="cmp-group"><th colspan>`
   per category, reusing `thead th`'s own gold caps-tracked treatment for the label so
   it reads as "another header," not an invented style — the only new declaration is
   the banding fill, built from the same warm-brown hue already saturating this
   component's shadows (not a new palette entry). Weight/size/space/banding only, per
   CLAUDE.md's hierarchy rule — no colour outside the token set. */
table.cmp tr.cmp-group th{
  padding:34px 18px 14px;
  border-bottom:1.5px solid rgba(122,94,42,.35);
  background:rgba(122,94,42,.08);
  text-align:left;
}
table.cmp tbody tr.cmp-group:first-child th{padding-top:20px}
table.cmp tr.cmp-group:hover{background-color:rgba(122,94,42,.08)}
.cmp-group-label{
  display:block;font-family:var(--ui);font-size:12.5px;font-weight:700;
  letter-spacing:.14em;text-transform:uppercase;color:var(--gold);
}
.cmp-group-take{
  margin-top:6px;font-family:var(--figure);font-size:15px;font-weight:600;
  color:var(--ink);max-width:56ch;line-height:1.4;
}
@media (max-width:760px){
  table.cmp tbody tr.cmp-group{
    background:none;border:0;box-shadow:none;padding:26px 2px 4px;display:block;
  }
  table.cmp tbody tr.cmp-group th{
    display:block;padding:0;border-bottom:0;font-size:15px;
  }
}

.mark{display:flex;gap:9px;align-items:flex-start;font-size:13.5px;line-height:1.45;
  color:var(--ink)}
.mark i{font-style:normal;flex:none;line-height:1.4;position:relative;top:1px}
/* ⚠⚠ BADGE ICONS — promoted 2026-08-30 (George: "make the emoticons more professional
   and brighter colors"). Diagnosed before touching anything: the old ✓/✗/◐ characters
   inherited the body serif (Lora) and rendered as thin, uneven strokes — the ◐
   especially read as a decorative dot rather than a clear half-filled indicator — and
   the old colours (--forest/--danger/--gold) measured 22-34% lightness against the
   pane, genuinely dark. Small filled-circle SVG badges fix both at once: a shape that
   renders identically in every browser regardless of font, in a colour set chosen to
   be visibly brighter while still clearing ~4:1 contrast against the pane (a fully
   saturated bright green/red dropped under 3:1, which is why these aren't neon):
     yes  #1E8449 (was --forest #2E4636, L 22.7%→31.8%, contrast 9.1→4.2 : 1)
     no   #B8362A (was --danger #8E2C22, L 34.5%→44.3%, contrast 7.3→5.2 : 1)
     part #A66E1B (was --gold   #94661B, L 34.3%→37.8%, contrast 4.5→3.8 : 1)
   The colour lives only inside these three SVG data URIs — a background-image loads
   as an isolated resource and cannot read the page's CSS custom properties (no
   currentColor, no var()), so there's no live token to point at; that's also why this
   is a comment and not a `--mark-*` var nobody else can consume. Swapping the icon is
   pure CSS: the ✓/✗/◐ characters are still in the HTML (`<i>✓</i>` etc.) but hidden via
   `font-size:0`, so no markup change was needed to ship this. */
.mark i{
  font-size:0;width:18px;height:18px;display:inline-block;
  background-repeat:no-repeat;background-size:18px 18px;background-position:center;
}
.mark.yes i{background-image:url("data:image/svg+xml,%3Csvg%20xmlns%3D%27http%3A//www.w3.org/2000/svg%27%20viewBox%3D%270%200%2020%2020%27%3E%3Ccircle%20cx%3D%2710%27%20cy%3D%2710%27%20r%3D%279%27%20fill%3D%27%231E8449%27/%3E%3Cpath%20d%3D%27M6%2010.5l2.5%202.5L14.5%207%27%20stroke%3D%27%23FDFBF6%27%20stroke-width%3D%272%27%20fill%3D%27none%27%20stroke-linecap%3D%27round%27%20stroke-linejoin%3D%27round%27/%3E%3C/svg%3E")}
.mark.no i{background-image:url("data:image/svg+xml,%3Csvg%20xmlns%3D%27http%3A//www.w3.org/2000/svg%27%20viewBox%3D%270%200%2020%2020%27%3E%3Ccircle%20cx%3D%2710%27%20cy%3D%2710%27%20r%3D%279%27%20fill%3D%27%23B8362A%27/%3E%3Cpath%20d%3D%27M7%207l6%206M13%207l-6%206%27%20stroke%3D%27%23FDFBF6%27%20stroke-width%3D%272%27%20fill%3D%27none%27%20stroke-linecap%3D%27round%27/%3E%3C/svg%3E")}
.mark.part i{background-image:url("data:image/svg+xml,%3Csvg%20xmlns%3D%27http%3A//www.w3.org/2000/svg%27%20viewBox%3D%270%200%2020%2020%27%3E%3Ccircle%20cx%3D%2710%27%20cy%3D%2710%27%20r%3D%279%27%20fill%3D%27none%27%20stroke%3D%27%23A66E1B%27%20stroke-width%3D%271.6%27/%3E%3Cpath%20d%3D%27M10%201.5a8.5%208.5%200%20000%2017z%27%20fill%3D%27%23A66E1B%27/%3E%3C/svg%3E")}
/* ⚠ `--proof`, NOT `--faint`. Same reason as `.hcard .sub`: small text on a
   translucent warm pane, where the greys measure under AA. `--proof` is the
   site-only token that exists for exactly this surface. */
.mark.no span{color:var(--proof)}

/* ⚠⚠ UNDER 760px THE CHART BECOMES THE CARDS, and that is the fix for the one
   fault the six-card round correctly identified: the old table set
   `min-width:720px` and arrived one column at a time on a phone. Sideways
   scrolling is not a way to read a comparison. Here the `thead` goes away, each
   row becomes its own glass card, and every cell re-labels itself from its
   `data-col` — so the phone gets genuine cards and the desktop keeps the chart,
   out of one piece of markup. */
@media (max-width:760px){
  .cmp-pane{background:none;border:0;box-shadow:none;border-radius:0;overflow:visible}
  .cmp-scroll{overflow-x:visible;border-radius:0}
  table.cmp{display:block;min-width:0}
  table.cmp thead{display:none}
  table.cmp tbody{display:grid;gap:14px}
  table.cmp tbody tr{
    display:block;padding:18px 18px 4px;
    background:rgba(250,247,239,.72);
    border:1px solid rgba(255,255,255,.22);
    border-radius:var(--glass-r);
    box-shadow:inset 0 1.5px 0 rgba(255,255,255,.62),
               inset 1px 0 0 rgba(255,255,255,.24),
               inset 0 -1px 0 rgba(122,94,42,.16),
               0 1px 2px rgba(40,30,20,.03),
               0 22px 50px -30px rgba(60,44,20,.42);
  }
  table.cmp tbody tr:hover{background-color:rgba(250,247,239,.72)}
  table.cmp tbody th{display:block;width:auto;padding:0 0 13px;border-bottom:0;font-size:16px}
  table.cmp tbody td{
    display:block;width:auto;padding:12px 0;
    border-bottom:0;border-top:1px solid var(--hair);
    background-color:transparent;box-shadow:none;
  }
  /* ⚠ THE AMLAK CELL KEEPS ITS BAND ON A PHONE. With the columns gone, the three
     answers are otherwise three stacked lines of equal weight, and the one the
     visitor came to read should still be the one the eye lands on. Same tint as
     the desktop column, drawn as a panel rather than a band, because there is no
     column here for it to be. Hover is pinned to the same value: a phone has no
     hover, but a tap fires `:hover` on the way past and a cell that changes
     colour under a finger reads as something that was pressed. */
  table.cmp tbody td.us{
    background-color:rgba(255,255,255,.55);
    box-shadow:inset 0 0 0 1px rgba(255,255,255,.55);
    border-top:0;border-radius:10px;padding:12px 13px;margin-top:3px;
  }
  table.cmp tbody tr:hover .us{background-color:rgba(255,255,255,.55)}
  /* The column heading, restored per cell. Without this a phone reader sees
     three ticks and no idea which product each belongs to. */
  table.cmp tbody td::before{
    content:attr(data-col);display:block;margin-bottom:5px;
    font-family:var(--ui);font-size:11px;font-weight:700;letter-spacing:.12em;
    text-transform:uppercase;color:var(--gold);
  }
  table.cmp tbody td.us::before{color:var(--accent)}
  table.cmp tbody tr:last-child td{border-bottom:0}
}

.honest{
  background:var(--panel);border:1px solid var(--border);border-left:3px solid var(--gold);
  border-radius:var(--radius);padding:26px 30px;
}
.honest h3{font-size:22px;margin-bottom:10px}
.honest p{color:var(--muted);margin:0 0 14px;font-size:15px}
.honest ul{margin:0;padding-left:20px;color:var(--muted);font-size:14.5px;display:grid;gap:7px}

/* ── timeline (About) ───────────────────────────────────────────── */
/* ⚠⚠ IT IS A TIMELINE NOW, NOT FOUR STACKED ROWS (George, 2026-08-24: "can you
   add some sort of connector between the numbers sort of like a timeline kind
   of thing between one through four on the about page"). The four blocks are a
   sequence — the job, what it cost, the realization, the product — and until
   this they were four numbers with a full-width rule between them, which reads
   as a LIST. The rule ran across the whole measure and the numbers were loose
   type beside it; the connector runs THROUGH the numbers instead, which is the
   thing that says these four are one line of reasoning in order.
   ⚠ THE HORIZONTAL RULES CAME OFF IN THE SAME CHANGE. Keeping both gives a grid
   of cells, and a cell is the one shape a timeline must not look like. */
/* ⚠ `position:relative` IS LOAD-BEARING, NOT TIDINESS — it is the containing
   block for `.tl-spark`, and `timelineSpark()` in site.js also measures the two
   numbers' offsets through it. See the note there before removing it. */
.timeline{position:relative;display:grid;gap:0;margin-top:8px}
/* ⚠ 44px, WHICH IS THE RING'S OWN WIDTH. The column was 120px when it held
   loose 30px type that needed room to breathe; with a ring in it that leaves
   76px of empty gutter between the spine and the copy, which reads as two
   unrelated columns rather than as a line with entries hanging off it. */
.tl-item{position:relative;display:grid;grid-template-columns:44px minmax(0,1fr);
  gap:28px;padding:26px 0}
/* ⚠⚠ THE SPINE IS DRAWN PER ITEM AND TRIMMED AT THE ENDS, which is what lets it
   stop exactly at the first and last numbers without JavaScript measuring
   anything. A single line on `.timeline` would have to start and end at those
   two centres, and their offsets depend on the type — so it would either
   overshoot into whitespace above 01 and below 04, or need a magic number that
   goes stale the moment a font size moves.
   ⚠ `--tl-mid` IS THE NUMBER'S CENTRE MEASURED FROM THE ITEM'S TOP: 26px of
   padding plus half of the 44px ring. It is a variable because THREE rules use
   it (the two end trims and the ring's own offset) and three copies of 48 would
   drift apart the first time the padding changed. */
.tl-item{--tl-mid:48px}
.tl-item::before{
  content:'';position:absolute;left:21px;top:0;width:1px;height:100%;
  background:linear-gradient(180deg,rgba(92,107,60,.42),rgba(92,107,60,.42));
}
/* ⚠⚠ `-of-type`, NOT `-child`, AND THAT IS NOT A STYLE PREFERENCE. `.tl-spark`
   is appended into `.timeline` by `timelineSpark()`, so from that moment the
   fourth `.tl-item` is no longer its parent's `:last-child` and the two end
   trims below stop matching: the last segment reverts to `height:100%` and the
   spine runs on past 04 to the bottom of the block, un-faded. It shipped that
   way for exactly one screenshot. `:last-of-type` counts among ELEMENTS OF THE
   SAME TYPE, and the items are `<div>` while the spark is an `<i>` — so an
   ornament dropped into this container can never move the ends of the line
   again. Anything else added here must not be a `<div>`. */
.tl-item:first-of-type::before{top:var(--tl-mid);height:calc(100% - var(--tl-mid))}
/* ⚠ THE LAST SEGMENT FADES OUT RATHER THAN STOPPING DEAD. It ends at the last
   number, and a 1px line that simply halts there reads as a line that was cut;
   dying over its final third reads as a sequence that has arrived. */
.tl-item:last-of-type::before{
  height:var(--tl-mid);
  background:linear-gradient(180deg,rgba(92,107,60,.42) 0,rgba(92,107,60,.42) 62%,
             rgba(92,107,60,0) 100%);
}
.tl-when{
  position:relative;z-index:1;
  width:44px;height:44px;border-radius:50%;
  display:flex;align-items:center;justify-content:center;
  /* ⚠ AN OPAQUE FILL, NOT `transparent`. The ring sits ON the spine, so the
     line has to be hidden behind it or it runs straight through the digits.
     ⚠ AND IT IS DELIBERATELY A NODE RATHER THAN A HOLE. There is no token that
     matches the ground behind it — `html`'s background is `--paper` plus two
     warm radial gradients at `background-attachment:fixed`, so any flat fill
     shows as a disc however it is chosen. `--panel` makes that disc read as one
     of the site's own cards sitting on the line, which is what a timeline node
     should look like anyway. Do not go hunting for a colour that disappears. */
  background:var(--panel);border:1px solid rgba(92,107,60,.34);
  font-family:var(--display);font-size:19px;font-weight:600;color:var(--accent);
  line-height:1;
  /* ⚠⚠ LINING FIGURES, EXPLICITLY. Cormorant Garamond ships OLDSTYLE numerals as
     its default, so "01" renders with a short o and a descending 1 — at ring
     size it reads as "oI", which is the one thing a numbered step must not do.
     It went unnoticed while these were 30px of loose type and became obvious the
     moment they were centred in a 44px circle.
     ⚠ BOTH PROPERTIES ON PURPOSE: `font-variant-numeric` is the modern spelling
     and `font-feature-settings` is what an older engine reads. They do not
     conflict — the low-level one only applies where the high-level one did not. */
  font-variant-numeric:lining-nums;
  font-feature-settings:'lnum' 1;
}
.tl-item h3{font-size:22px;margin-bottom:8px}
.tl-item p{margin:0;color:var(--muted);font-size:15px}

/* ── the light that runs 01 → 04 ───────────────────────────────────────
   George, 2026-08-24: "add a line like the ones that float around on the grid
   on the timeline from 1-4, just have to start at one and drop to 4 then
   restart, changing colors as it goes."

   ⚠ IT IS THE HERO TRACES' THREE INKS, NOT THREE NEW ONES. Gold 148,102,27 ·
   olive 92,107,60 · rust 142,44,34 are the exact set `traces()` picks from in
   site.js, which is what makes this read as the same light that moves on the
   About and Compare grids rather than as a second effect that happens to be
   nearby. Change the palette there and change it here.
   ⚠ ONE ANIMATED `color`, AND EVERYTHING ELSE READS IT. The bead's fill, its
   glow and the tail's gradient are all `currentColor`, so the colour is
   interpolated ONCE per frame instead of three times and they can never drift
   out of step with each other mid-run.
   ⚠ `--tl-a` / `--tl-b` COME FROM `timelineSpark()` and the fallbacks are only
   there so a page whose script never ran does not draw a bead in the corner.
   ⚠ THE LAST QUARTER OF THE CYCLE IS EMPTY ON PURPOSE. The light arrives at 04,
   goes out, and the line rests for about a second before the next one leaves 01
   — without the gap it is a loop rather than a journey, and a loop is the thing
   the eye stops seeing after twice round.
   ⚠ NO `z-index`. It is a plain child of `.timeline`, painted after the items'
   spines and UNDER `.tl-when` (which is z-index 1 over an opaque fill), so it
   passes behind each ring. That is the point: four stations on one line. */
.tl-spark{
  position:absolute;left:21.5px;top:var(--tl-a,48px);
  width:7px;height:7px;margin:-3.5px 0 0 -3.5px;border-radius:50%;
  background:currentColor;color:#94661B;opacity:0;pointer-events:none;
  filter:drop-shadow(0 0 6px currentColor);
  /* ⚠ 5.2s → 3.2s (George, 2026-08-25: "make the rerun of it all quicker").
     The shape is unchanged — travel over the first ~78%, dark rest for the
     last ~22% — so the gap that keeps this a journey rather than a loop is
     still there, at ~0.7s instead of ~1.25s. */
  animation:tlRun 3.2s linear infinite;
}
/* The tail, hanging back up the line the way the traces' do. ⚠ WIDER THAN THE
   SPINE (3px against 1px) so the light reads as passing OVER the hairline
   rather than as a segment of it that happens to be coloured in. */
.tl-spark::before{
  content:'';position:absolute;left:50%;bottom:3px;translate:-50% 0;
  width:3px;height:42px;border-radius:3px;opacity:.6;
  background:linear-gradient(180deg,rgba(148,102,27,0),currentColor);
}
/* ⚠⚠ TWO INKS, NOT THREE — THE RUST CAME OUT (George, 2026-08-25: "the red
   doesn't fit on the timeline line drop from 1-4"). It was `58% {color:#8E2C22}`,
   and he is right about it: `--danger` earns its place on the year matrix, where
   a red day means a deadline that costs money, and on the hero grid, where eleven
   independent traces read as weather. On a single line running through a founding
   story it reads as an alarm going off between "the realization" and "the
   product". Gold → olive → gold still changes colour as it goes and still ends on
   the ink it starts with, which is what keeps the loop seamless.
   ⚠ RUST STAYS IN `traces()` — this removed it from the timeline, not from the
   palette. Do not go and take it out of site.js as a tidy-up. */
@keyframes tlRun{
  0%   {top:var(--tl-a,48px);opacity:0;color:#94661B}
  7%   {opacity:1}
  40%  {color:#5C6B3C}
  68%  {opacity:1}
  78%  {top:var(--tl-b,100%);opacity:0;color:#94661B}
  100% {top:var(--tl-b,100%);opacity:0;color:#94661B}
}

/* ── the founder's note (/consultation) ─────────────────────────── */
/* ⚠ ONE CALLER, AND IT SHOULD STAY THAT WAY. This is the loudest block on the
   site that is not a button, which only works while it is rare — a second
   `.pledge` anywhere makes both of them decoration. The reasoning behind the
   copy (and behind its first person) is in the comment at its call site in
   `consultation.html`; read that before touching either.
   ⚠ IT IS NOT `.honest`. That class has the same shape — panel, gold left rule —
   and is dead as of 2026-08-24; reviving it under a name that means the opposite
   of what this says would be a trap for whoever greps for it next. */
.pledge{
  /* ⚠ GOLD-SOFT RATHER THAN `--panel`. The form beside it is already a panel,
     and a second flat panel in the other column reads as the same object twice.
     The tint is what makes this the thing the eye lands on first, which is the
     whole brief. */
  background:var(--gold-soft);
  border:1px solid rgba(148,102,27,.28);border-left:3px solid var(--gold);
  border-radius:var(--radius);padding:20px 24px;margin:0 0 26px;
}
.pledge-tag{
  display:inline-block;margin-bottom:9px;
  font-size:11px;font-weight:700;letter-spacing:.16em;text-transform:uppercase;
  color:var(--gold);
}
/* ⚠ ON `--gold-soft` (#EFE5CE), NOT ON THE GROUND. `--muted` measures 4.3 : 1
   here — under AA — so this paragraph is full `--ink`, which is also right for
   the one block on the page that is meant to be read first. */
.pledge p{margin:0;color:var(--ink);font-size:15.5px;line-height:1.6}
.pledge p strong{font-weight:600}
/* ⚠ `.pledge p.pledge-sign`, NOT `.pledge-sign` — `.pledge p` above is (0,1,1)
   and would outrank a bare class, so the margin would silently not apply. Two
   classes, no `!important`; this file has been bitten by that arithmetic three
   times already (see the notes at `.mock-title`). */
.pledge p.pledge-sign{
  margin-top:12px;font-family:var(--display);font-size:16px;color:var(--gold);
}
.pledge-sign span{color:var(--muted);font-family:var(--ui);font-size:13px}

/* ── form ───────────────────────────────────────────────────────── */
.form-grid{display:grid;grid-template-columns:minmax(0,1fr) minmax(0,1fr);gap:56px;
  align-items:start}
form.access{background:var(--panel);border:1px solid var(--border);
  border-radius:var(--radius);padding:30px;box-shadow:var(--shadow)}
.field{display:grid;gap:7px;margin-bottom:18px}
.field label{font-size:11.5px;font-weight:700;letter-spacing:.12em;text-transform:uppercase;
  color:var(--muted)}
.field input,.field select,.field textarea{
  font-family:var(--ui);font-size:15px;color:var(--ink);background:var(--panel-2);
  border:1px solid var(--border);border-radius:var(--radius);padding:11px 13px;width:100%;
}
.field textarea{resize:vertical;min-height:92px}
.field input:focus,.field select:focus,.field textarea:focus{
  outline:2px solid var(--accent);outline-offset:-1px;background:var(--panel)}
.field-row{display:grid;grid-template-columns:1fr 1fr;gap:14px}

/* ══ the painted select ════════════════════════════════════════════════════
   George, 2026-08-24: *"change the native selection for the properties and
   mostly."* Same complaint, same answer, and the SAME MECHANISM as the app —
   see the long note at the top of `src/components/SelectMenu.js`, which records
   the trade in full. The closed face of a select has been ours all along; what
   is never ours is the popup, because the OS draws that and ignores every rule
   written here.

   ⚠⚠ THE REAL `<select>` STAYS AND KEEPS THE VALUE. Its popup is suppressed
   (`preventDefault` on mousedown, honoured by every browser this targets) and a
   list is painted over it. That is what preserves real keyboard behaviour, real
   screen-reader behaviour, real form semantics, and `form.properties.value` in
   the page's own submit handler. A from-scratch listbox would have had to
   re-earn all four, on a form whose failure mode is a lead nobody receives.
   ⚠ AND IT IS PROGRESSIVE: with no JavaScript there is no `.selx` wrapper, the
   select is a plain select, and the form still submits.
   ⚠ THE ARROW IS ON THE WRAPPER, NOT THE SELECT. `appearance:none` removes the
   native one; drawing the replacement as a `::after` on the wrapper keeps it
   out of the select's own padding box, where a background-image chevron has to
   be positioned by hand at every font size. */
.selx{position:relative;display:block}
.selx > select{
  appearance:none;-webkit-appearance:none;-moz-appearance:none;
  padding-right:34px;cursor:pointer;
}
.selx::after{
  content:'';position:absolute;right:13px;top:50%;translate:0 -50%;
  width:7px;height:7px;border-right:1.5px solid var(--gold);
  border-bottom:1.5px solid var(--gold);rotate:45deg;margin-top:-3px;
  pointer-events:none;
}
.selx-menu{
  position:absolute;left:0;right:0;top:calc(100% + 5px);z-index:60;
  background:var(--panel);border:1px solid var(--border);border-radius:var(--radius);
  box-shadow:var(--shadow-lift);padding:5px;margin:0;list-style:none;
  max-height:260px;overflow:auto;
}
.selx-menu li{
  padding:8px 11px;border-radius:var(--radius);font-family:var(--ui);font-size:14.5px;
  color:var(--ink);cursor:pointer;white-space:nowrap;overflow:hidden;text-overflow:ellipsis;
}
.selx-menu li:hover{background:var(--panel-2)}
/* ⚠ THE TICKED ROW IS READ FROM THE SELECT'S OWN `value`, never tracked
   separately — one source of truth, the same rule the app's version keeps. */
.selx-menu li[aria-selected="true"]{background:var(--accent-soft);color:var(--accent);font-weight:600}
.hp{position:absolute;left:-9999px;width:1px;height:1px;overflow:hidden}
/* prose on the ground, like `.hero-note` — see the token note in :root */
.form-note{font-size:12.5px;color:var(--muted);margin:14px 0 0;line-height:1.5}
.form-msg{border-radius:var(--radius);padding:14px 16px;font-size:14px;line-height:1.55;
  margin-bottom:18px}
.form-msg.err{background:var(--danger-soft);color:var(--danger)}
.form-done{
  background:var(--panel);border:1px solid var(--border);border-left:3px solid var(--accent);
  border-radius:var(--radius);padding:30px;box-shadow:var(--shadow);
}
.form-done h3{margin-bottom:10px}
.form-done p{color:var(--muted);margin:0 0 10px;font-size:15px}

/* ── closing CTA ────────────────────────────────────────────────── */
.cta{background:var(--forest);color:var(--panel);border-radius:var(--radius);
  padding:66px 56px;text-align:center;box-shadow:var(--shadow-lift)}
.cta h2{color:var(--panel);max-width:20ch;margin:0 auto}
.cta p{color:rgba(251,248,241,.72);font-size:17px;max-width:56ch;margin:18px auto 0}
.cta .btn-row{justify-content:center;margin-top:32px}
.cta .btn-primary{background:var(--panel);color:var(--forest)}
.cta .btn-primary:hover{filter:brightness(.94)}
.cta .btn-secondary{color:var(--panel);border-color:rgba(251,248,241,.45)}
.cta .btn-secondary:hover{background:var(--panel);color:var(--forest);border-color:var(--panel)}

/* ── footer ─────────────────────────────────────────────────────── */
.foot{border-top:1px solid var(--border);background:var(--panel-2);padding:56px 0 34px}
.foot-in{display:grid;grid-template-columns:minmax(0,1.5fr) repeat(3,minmax(0,1fr));gap:40px}
.foot-brand{display:flex;align-items:center;gap:11px;font-family:var(--display);
  font-weight:600;font-size:24px;text-decoration:none;margin-bottom:14px}
.foot p{color:var(--muted);font-size:14px;margin:0;max-width:38ch}
.foot h4{font-family:var(--ui);font-size:11.5px;font-weight:700;letter-spacing:.13em;
  text-transform:uppercase;color:var(--faint);margin:0 0 14px}
.foot ul{list-style:none;margin:0;padding:0;display:grid;gap:9px}
.foot ul a{color:var(--muted);text-decoration:none;font-size:14px}
.foot ul a:hover{color:var(--accent)}
.foot-base{margin-top:44px;padding-top:22px;border-top:1px solid var(--hair);
  display:flex;flex-wrap:wrap;gap:12px;justify-content:space-between;
  font-size:12.5px;color:var(--muted)}   /* prose on the ground — see :root */

/* ══ motion ═══════════════════════════════════════════════════════════════
   Added 2026-08-21 after George asked whether Amlak's site could move the way
   petralabs.com does. It can, and this is all of it: no library, one 4KB
   script, every effect built out of `transform` and `opacity` so it composites
   on the GPU and never reflows the page mid-scroll.

   ⚠ NOTHING HERE HIDES ANYTHING BY ITSELF. `.rv` is added by `site.js`
   immediately before the observer that removes it, so a page with no working
   JS has no `.rv` on it and renders complete. Read the header of site.js
   before changing this — the two files are one mechanism. */

/* ── the nav, once it is floating over content ─────────────────────────── */
.nav{transition:box-shadow .22s ease,background .22s ease}
/* once the page has moved, the bar firms up and casts — it has content under it */
/* ⚠ THE TWO ALPHAS DO DIFFERENT JOBS, and that is why they are so far apart.
   At REST the bar is over the hero's own light with nothing else behind it, so
   it needs almost no panel — measured, .32 leaves a step of 61 against the
   ground either side where .80 left 127, and the light simply carries on
   through. Once the page has moved there is CONTENT underneath and the bar has
   to be able to sit on top of a table without it showing through, which is what
   .93 is for. One value cannot be both. */
/* ⚠ `.stuck` NO LONGER CHANGES THE COLOUR, only lifts the shadow. The bar was
   translucent at rest and opaque once scrolled, so `.stuck` had to close that
   gap; now that the base is opaque there is no gap, and re-stating a background
   here would just be a second place to keep the same hex in step. The shadow
   alone says "this is floating over content", which is all it ever meant. */
.nav.stuck{box-shadow:0 14px 34px -28px rgba(40,30,20,.55)}

/* ── scroll reveal ─────────────────────────────────────────────────────── */
.rv{opacity:0;transform:translateY(16px);
  transition:opacity .62s cubic-bezier(.22,.61,.36,1),transform .62s cubic-bezier(.22,.61,.36,1)}
.rv.in{opacity:1;transform:none}
/* Cards arrive left-to-right rather than all at once — they are a row, and a
   row that lands as one block reads as a single image rather than four things. */
.cards .card:nth-child(2){transition-delay:.07s}
.cards .card:nth-child(3){transition-delay:.14s}
.cards .card:nth-child(4){transition-delay:.21s}

/* A revealed mock draws its own contents once it has arrived: the bars grow
   from the left and the twelve months land one at a time. This is the rent
   ledger — the screen the product is really about — so it is the one place on
   the site worth animating in detail. */
.rv .mock-bar>i{transform-origin:left;transform:scaleX(0);
  transition:transform .85s cubic-bezier(.22,.61,.36,1) .22s}
.rv.in .mock-bar>i{transform:scaleX(1)}
/* ── the hub's entrance ────────────────────────────────────────────────────
   Pure CSS, runs on load with or without JS: the wires draw OUT of the core
   (dashoffset -1 → 0 slides the dash in from the path's END, which is the
   core end), the dots land where the wires bend, and each card arrives just
   after its own wire. Every keyframe animates FROM the hidden state TO the
   element's natural one, so animation:none — reduced motion — is the
   finished diagram, not a blank.

   ⚠ THE CARD ENTRANCE IS `translate`, NOT `transform`. transform already
   carries the card's translateY(-50%) positioning; fold the entrance into it
   and every card jumps half its height on arrival. Same split as everything
   else that moves on this site.

   ⚠ NO IDLE BOB ON THE CARDS, deliberately — unlike the old floats and
   chips. Each card's edge is pinned to a wire that does not move; a card
   drifting 4px off its own wire tip reads as a bug, not a mood. */
.hwire{animation:hubDraw .55s cubic-bezier(.3,.2,.2,1) both}
.hub-base .hwire:nth-child(1){animation-delay:calc(0.10s + var(--boot,0s))}
.hub-base .hwire:nth-child(2){animation-delay:calc(0.16s + var(--boot,0s))}
.hub-base .hwire:nth-child(3){animation-delay:calc(0.22s + var(--boot,0s))}
.hub-base .hwire:nth-child(4){animation-delay:calc(0.28s + var(--boot,0s))}
.hub-base .hwire:nth-child(5){animation-delay:calc(0.34s + var(--boot,0s))}
.hub-base .hwire:nth-child(6){animation-delay:calc(0.40s + var(--boot,0s))}
.hub-base .hwire:nth-child(7){animation-delay:calc(0.19s + var(--boot,0s))}
.hub-base .hwire:nth-child(8){animation-delay:calc(0.25s + var(--boot,0s))}
.hub-base .hwire:nth-child(9){animation-delay:calc(0.31s + var(--boot,0s))}
.hub-base .hwire:nth-child(10){animation-delay:calc(0.37s + var(--boot,0s))}
.hub-base .hwire:nth-child(11){animation-delay:calc(0.43s + var(--boot,0s))}
.hdot{animation:hubFade .3s ease-out both}
.hub-dots .hdot:nth-child(1){animation-delay:calc(0.34s + var(--boot,0s))}
.hub-dots .hdot:nth-child(2){animation-delay:calc(0.40s + var(--boot,0s))}
.hub-dots .hdot:nth-child(3){animation-delay:calc(0.46s + var(--boot,0s))}
.hub-dots .hdot:nth-child(4){animation-delay:calc(0.52s + var(--boot,0s))}
.hub-dots .hdot:nth-child(5){animation-delay:calc(0.58s + var(--boot,0s))}
.hub-dots .hdot:nth-child(6){animation-delay:calc(0.64s + var(--boot,0s))}
.hub-dots .hdot:nth-child(7){animation-delay:calc(0.43s + var(--boot,0s))}
.hub-dots .hdot:nth-child(8){animation-delay:calc(0.49s + var(--boot,0s))}
.hub-dots .hdot:nth-child(9){animation-delay:calc(0.55s + var(--boot,0s))}
.hub-dots .hdot:nth-child(10){animation-delay:calc(0.61s + var(--boot,0s))}
.hub-dots .hdot:nth-child(11){animation-delay:calc(0.67s + var(--boot,0s))}
.hcard{animation:hubCardIn .5s cubic-bezier(.2,.72,.3,1) both}
.hub-cards .hcard:nth-child(1){animation-delay:calc(0.42s + var(--boot,0s))}
.hub-cards .hcard:nth-child(2){animation-delay:calc(0.48s + var(--boot,0s))}
.hub-cards .hcard:nth-child(3){animation-delay:calc(0.54s + var(--boot,0s))}
.hub-cards .hcard:nth-child(4){animation-delay:calc(0.60s + var(--boot,0s))}
.hub-cards .hcard:nth-child(5){animation-delay:calc(0.66s + var(--boot,0s))}
.hub-cards .hcard:nth-child(6){animation-delay:calc(0.72s + var(--boot,0s))}
.hub-cards .hcard:nth-child(7){animation-delay:calc(0.51s + var(--boot,0s))}
.hub-cards .hcard:nth-child(8){animation-delay:calc(0.57s + var(--boot,0s))}
.hub-cards .hcard:nth-child(9){animation-delay:calc(0.63s + var(--boot,0s))}
.hub-cards .hcard:nth-child(10){animation-delay:calc(0.69s + var(--boot,0s))}
.hub-cards .hcard:nth-child(11){animation-delay:calc(0.75s + var(--boot,0s))}
/* ⚠ `1`, NOT `-1`, AND THE SIGN IS THE PATH'S DIRECTION (2026-08-23). With
   `stroke-dasharray:1` on a `pathLength="1"` path, dashoffset k renders the
   segment [0, 1-k] — so this grows the wire out of the path's START. Every
   branch was re-authored core → card when the portal transition was built, so
   the start IS the mark; at -1 it renders [1-k, 1] instead and all eleven wires
   draw inward from their cards. Nothing errors, the diagram just runs inside
   out. Same convention the portal's retract uses: dashoffset = 1 - distance. */
@keyframes hubDraw{from{stroke-dashoffset:1}}
@keyframes hubFade{from{opacity:0}}
@keyframes hubCardIn{from{opacity:0;translate:0 12px}}
/* The core's tick when a signal lands — site.js adds .tick, the ring flares
   once, and the class comes off on a timer so the next flare can restart. */
.hub-core.tick .hub-ring{animation:ringOut .7s ease-out}
@keyframes ringOut{from{opacity:.9;scale:1}to{opacity:0;scale:1.4}}

/* ── COMING HOME: the mark first, then the beat, then the branches ─────────
   George, 2026-08-23: "when I click Amlak the screen should start with the A,
   then pulsate and materialize the branches."

   So the return is a THIRD thing, not the first-load entrance replayed and not
   the pixel curtain: the mark lands on empty paper, the ring flares out of it,
   and only then do the eleven wires draw and the cards arrive. It reads as the
   diagram being switched on rather than uncovered.

   ⚠ IT IS ONE VARIABLE, NOT A SECOND SET OF DELAYS. Every nth-child delay in
   the entrance above is written `calc(<its own> + var(--boot,0s))`, so this
   class pushes all thirty-three of them behind the mark by changing one
   number. Copying the delay ladder to re-time it would be two ladders to keep
   in step, and they would not stay in step.
   ⚠ AND `--boot` DEFAULTS TO 0s. First load is unchanged — mark already there,
   branches arriving — which is the sequence that has been tested since the hub
   shipped. This only ever applies while the class is on. */
.hero-hub.hub-boot{--boot:.62s}
.hero-hub.hub-boot .hub-core{animation:coreLand .62s cubic-bezier(.2,.86,.3,1) both}
.hero-hub.hub-boot .hub-ring{animation:ringOut .95s ease-out .5s}
@keyframes coreLand{
  0%{opacity:0;scale:.35}
  55%{opacity:1}
  100%{opacity:1;scale:1}
}

/* ══ moving between the hub and a feature ══════════════════════════════════
   George, 2026-08-23: "when we click on a branch I want a cool way for it to
   materialize in front of the viewer like a teleport or a pixelated
   materialisation … is there a way for you to get those design features from
   somewhere?" — no library, and none needed.

   ⚠ FOUR WERE BUILT, ONE WAS CHOSEN, THE OTHER THREE ARE GONE (2026-08-23: "no
   more curtain or any other version, I choose [the portal]"). The tile curtain
   that shipped live (`.tele`), the particle canvas, the decode trace and its
   scrambled headline all left with the JS that drove them. What survives is
   below: the mark itself, in flight.

   ⚠ AND IT IS ALL ORNAMENT: EVERY DEFAULT IS THE ARRIVED STATE. Every node
   here is made and removed by site.js and the stylesheet paints none of it on
   its own. No JS, or reduced motion, and the branch cards are ordinary in-page
   anchors that scroll. */

/* ── THE PORTAL: the mark, in flight ────────────────────────────────────────
   ⚠ THIS IS THE REAL `.hub-core`, NOT A PROXY. The A is the one element that
   survives the whole sequence — it is re-parented into `.pz-layer` for the trip
   and handed back to the hub at the end, never cloned and never replaced, so
   what dilates open into the block is the same node the visitor clicked
   towards. The move is forced, not stylistic: `.hero-hub` sets
   `container-type:inline-size`, which makes it a containing block for
   fixed-position descendants, so the mark cannot leave the diagram by going
   `position:fixed` where it stands.

   ⚠ THE LAYER SITS UNDER THE NAV (50) AND UNDER THE BACK BUTTON (45). Those two
   are the things that do NOT travel; the mark passing over them would say they
   did. */
/* ⚠ ABOVE THE NAV (z 50), not under it. The sheet below has to take the whole
   screen — a sticky nav bar left riding over an emptied page is precisely the
   "something in the background" George asked to be rid of (2026-08-23), and the
   mark travelling UNDER the bar it is trying to be the only thing on screen with
   was the other half of it. Everything the layer holds is `pointer-events:none`,
   so being on top costs no clickability. */
/* ⚠⚠ THE RETURN-TRIP WASH, AND IT IS A SCRIM RATHER THAN `opacity` ON `main` FOR
   TWO REASONS, BOTH MEASURED (2026-08-25). Fading `main` made it a BACKDROP ROOT,
   so all eleven cards' glass collapsed for the whole trip — Safari and Firefox
   only, which is why it read as "the cards change colour on other browsers" — and
   it forced the entire page into ONE composited layer that had to be
   re-rasterised every frame, which is the pulse lag George reported alongside it.
   One rectangle over the top costs neither.
   ⚠ **z-index 40 IS EXACT, NOT ROUGHLY RIGHT.** It has to sit ABOVE `main` (which
   is at the auto level) and BELOW the sticky `.nav` at 50, because the bar does
   not move and washing it out just looks like the site flickering — the rule the
   old `dim()` kept by listing `main` and `.foot` by hand. `.pz-layer` at 60 stays
   above both, so the flying mark crosses in front of the wash.
   ⚠ IT LIVES ON `body`, NEVER INSIDE `main`. Inside, it would be a descendant of
   the thing it is replacing and would bring the backdrop root straight back. */
.pz-dim{
  position:fixed;left:0;top:0;right:0;bottom:0;z-index:40;
  pointer-events:none;opacity:0;background:var(--paper);
}
.pz-layer{position:fixed;left:0;top:0;right:0;bottom:0;z-index:60;pointer-events:none}
/* ⚠ THE PAGE IS TAKEN AWAY FOR THE TRIP, BUT NOT THE GROUND IT STANDS ON
   (George, 2026-08-23: "when we click back to features the A should still be
   seen with the background"). This was flat `--paper` and nothing else, so the
   mark crossed a blank sheet and the journey read as the site having been
   switched off rather than travelled across.
   ⚠ AND THE GROUND IS NO LONGER A GRID (George, 2026-08-23: "clicking one of
   the boxes still has the grid in the transitions"). It carried a copy of the
   64px plat lattice, which was correct for exactly as long as the hero had one.
   The hero is warm light now, so a sheet of graph paper sliding over the page
   mid-flight was the last surviving piece of a design that had already been
   replaced twice — and the one place it was still unmissable, because it covers
   the whole window. It is the SAME warm ground the document has, stated the
   same way, so the sheet reads as the page emptied rather than as a new
   surface. ⚠ Keep it in step with `html`'s background above: two copies of one
   ground, and the handover shows the moment they disagree. */
.pz-veil{
  position:absolute;left:0;top:0;right:0;bottom:0;opacity:0;
  background:
    radial-gradient(140% 88% at 50% 0%, rgba(182,130,53,.15) 0%, rgba(182,130,53,0) 68%),
    radial-gradient(96% 62% at 84% 52%, rgba(214,196,150,.20) 0%, rgba(214,196,150,0) 74%),
    var(--paper);
}
/* ⚠ ONE STILL FRAME OF THE HERO'S RAKING LIGHT, blitted here once per trip —
   see `stillFrame` in site.js for why this is not a second canvas field. It
   paints OVER the veil's paper and its two radials (a later child, same
   stacking context) and UNDER the mark, which lives in `.pz-layer` after it.
   The canvas is transparent everywhere the hero's own canvas did not reach, so
   below the hero the paper still shows through — which is correct: that is what
   is down there on the real page too. */
.pz-still{position:absolute;left:0;top:0;width:100%;height:100%;pointer-events:none}

/* The name, stated once and quietly, at the bottom of the About page. It was
   the H1 and a whole paragraph until 2026-08-24; a name is a footnote until the
   reader has a reason to care what it means. */
/* ⚠ DOUBLE, LITERALLY (George, 2026-08-24: "make the أملاك · amlāk, Arabic for
   properties bigger — double the size"). 14.5px → 29px, and the clamp only
   protects the phone; the top of the range IS twice what it was.
   ⚠ THE MEASURE COMES IN WITH IT. At 14.5px, 62ch was a comfortable single line
   for a one-sentence note; at 29px the same 62ch is 900px of centred type, wider
   than any other block on the page, and the sentence wraps in the wrong place.
   34ch keeps it to two balanced lines at every width — the size changed, so the
   measure had to.
   ⚠ STILL THE BODY FACE, NOT THE DISPLAY ONE. At 29px in Cormorant this would
   read as a heading, and the whole point of the note is that the name is stated
   quietly, low on the page, after the reader has a reason to care. Bigger, not
   promoted. */
.name-note{max-width:34ch;margin:30px auto 0;text-align:center;
  font-size:clamp(21px,2.3vw,29px);line-height:1.5;color:var(--muted);
  text-wrap:balance}
.name-note strong{color:var(--ink);font-weight:600}
/* ── THE RAKING LIGHT — the moving half of that ground ────────────────
   George, 2026-08-23, handing over an approved animation as `Property
   management background animation.zip`. See RAKE in site.js: three warm gold
   masses drifting, with randomised sweeps raking across them.

   WHAT WENT WITH IT — a second thing, and it had to. The grid RADIATE that
   stood here (the same 64px lattice drawn again in accent, with a ring of
   visibility travelling out from the mark) was itself the replacement for a
   canvas field of lit lots and crawling signals. Three answers to one question
   is two too many: two moving backgrounds on one hero is exactly the noise the
   field was removed for. The STATIC lattice below stays — the raking light
   passes over the plat map rather than instead of it, which is the whole
   picture: a warm light crossing a parcel map.

   ⚠ THE CANVAS IS TRANSPARENT AND THE GROUND IS THE PARENT'S, which is the
   handoff's integration note and it already holds here: `.hero` carries
   `--paper`, `position:relative` and `overflow:hidden`. Nothing to add.
   ⚠ AND `.pz-veil` DOES NOT GET ONE. The portal's emptied screen is on stage
   for about a second a trip, and the shortest sweep in the set lasts six and a
   half — so a second instance would cost a second rAF loop to show a still
   frame of a moving thing. It keeps the static lattice it already had. */
/* ══ the traces on the plat grid (About + Compare) ═════════════════════════
   ⚠ UNDER THE CONTENT, AND THAT IS STRUCTURAL RATHER THAN COSMETIC. `.hero-in`
   is `position:relative` with no z-index, so it paints above a z-index:0
   absolutely-positioned sibling that precedes it in the DOM. A moving line
   behind a headline is texture; the same line in front of it is a defect.
   ⚠ NO `opacity`, NO `filter`, NO `mix-blend-mode` HERE. Nothing on these two
   heroes uses `backdrop-filter` today, but the hub cards do on the home page and
   this class is one copy-paste away from being reused there — where any of those
   would make the hero a backdrop root and flatten every glass card under it.
   ⚠ `.hero{overflow:hidden}` ALREADY CLIPS IT. A trace runs a long way past the
   far edge by design (that is what "disappears off an edge" means); without the
   clip those overruns would extend the page. */
.gflow{
  position:absolute;inset:0;z-index:0;pointer-events:none;
  display:block;width:100%;height:100%;
  /* ⚠ THE SAME FADE AS THE GRID, FROM THE SAME `--grid-stop`. The canvas draws
     nothing below the stop anyway (see `makeTrace`), but the last few pixels of
     a tail can reach it, and light that outlives its own grid is the thing
     George reported: "I can see them off grid." One variable, two masks, so
     they cannot drift apart. */
  mask-image:var(--hero-mask,linear-gradient(180deg,#000 0,#000 70%,transparent 79%));
  -webkit-mask-image:var(--hero-mask,linear-gradient(180deg,#000 0,#000 70%,transparent 79%));
  mask-size:100% 100%;-webkit-mask-size:100% 100%;
  mask-repeat:no-repeat;-webkit-mask-repeat:no-repeat;
}
/* ⚠ THE `mask-composite:intersect` BLOCK THAT LIVED HERE IS GONE (2026-08-25).
   It existed to AND a vertical fade with a set of radial holes across two
   elements; the SVG mask above does both inside one document, so there is no
   second layer to composite and no @supports gate to fail. If a future change
   needs two mask layers again, read the note at `.hero:not(.hub-hero)::before`
   first — the reason that block sat AFTER `.gflow` still applies. */
.rake{
  /* ⚠ ANCHORED TO THE DOCUMENT, NOT TO THE HERO — see the note at `heroRake` in
     site.js. `body` is static, so an absolutely positioned child resolves
     against the initial containing block, which puts 0,0 at the document's top
     left: exactly where the nav starts. Its HEIGHT is written by JS (nav +
     hero); nothing here may set one, or the light stops at the wrong place. */
  position:absolute;left:0;top:0;width:100%;
  pointer-events:none;z-index:0;
  /* ⚠ IT FADES OUT BEFORE THE HERO ENDS, and this is the actual seam fix. The
     warm ground on `html` runs the whole page, so hero and blocks share a
     colour — but the CANVAS still stopped dead at the hero's last pixel, which
     just moved the step rather than removing it. Dying out over the last third
     hands the ground back gradually, so scrolling out of the hero is the light
     running out rather than a line being crossed. */
  mask-image:linear-gradient(to bottom,#000 0%,#000 58%,transparent 97%);
  -webkit-mask-image:linear-gradient(to bottom,#000 0%,#000 58%,transparent 97%);
}
.hero-in,.hub-hero>.wrap{position:relative;z-index:1}
/* ⚠ `translate:none` — `.hub-core` centres itself on the diagram with the
   standalone `translate` property, and in the layer it is placed by left/top
   instead. Leave it and every position is half a mark up and to the left.
   `transform` carries the travel and `scale` the aperture: three properties,
   one owner each, composing — the rule the whole hub is built on. */
.pz-fly{
  position:absolute;translate:none;margin:0;
  transform-origin:50% 50%;
  transition:border-radius .3s ease,box-shadow .3s ease;
}
/* ⚠ THE MARK BECOMES A RULE, NOT A PANEL (George, 2026-08-23: "the portal that
   opens up the feature card looks ugly, choose a different transition"). It used
   to morph into a `--panel` rectangle the size of the whole block, with a border
   and a lift shadow — and nothing else on this site is a slab like that. A
   `.feature` has NO background of its own: it sits on the paper, separated from
   its neighbours by a single `--hair` rule. So a solid box the size of the block
   appeared out of nowhere, sat over the content and vanished again, with nothing
   on the page for the eye to attach it to.
   It flattens into an accent rule along the block's TOP EDGE instead — exactly
   where that hairline already is — the content rises out from under it, and the
   rule fades and hands the line back to the real border. The last frame of the
   transition is the page as it always looks, which is the whole point.
   ⚠ The shadow goes with it: a lift shadow under a 3px rule is a smudge. */
.pz-fly.pz-seam{border-radius:2px;box-shadow:none}
.pz-fly img{transition:opacity .16s linear}

/* ⚠ NOTHING TRANSITIONS `scale` OR `opacity` ON THE DIAGRAM ANY MORE, and that
   is load-bearing rather than tidy. The tile curtain declared
   `transition: opacity .42s, scale .42s` here for its own collapse, and it
   damped every one of the portal's per-frame writes into a lagging trail — the
   cards rode their branches at nearly full size and then shrank once they had
   already arrived, which flatters the way out and wrecks the way home, because
   a lag always trails and so can never be mirrored. It cost two rounds. The
   engine owns these two properties outright now; adding a transition back to
   this selector re-opens both bugs at once.
   ⚠ `scale` AS ITS OWN PROPERTY, never inside `transform`. `.hcard` uses
   transform for its position (and `.hcard.c` for its centring) and the
   standalone `translate` for its entrance — three separate properties on one
   element, composing, which is the rule this whole hub is built on. Fold any
   of them into `transform` and the last one written silently wins. */

/* ══ the way back, at the foot of every feature ════════════════════════════
   ⚠⚠ THIS WAS A FIXED PILL FLOATING OVER THE VIEWPORT UNTIL 2026-08-24, and
   the history matters because three rounds of work went into making a floating
   control behave, all of which is now moot. It was `position:fixed`,
   bottom-anchored with a `safe-area-inset-bottom` allowance because it had been
   cut off on a phone; it was hidden at boot and revealed only when a teleport
   landed; it was withdrawn by an IntersectionObserver on the hub; and it was
   timed to the block rather than to the click so a sheet never slid over it.
   Every one of those problems existed BECAUSE it floated.

   George, 2026-08-24: "back to features button should live under the last
   bullet point of each feature that we click on." An anchor in the flow at the
   end of `.feature-copy` has none of those problems: nothing can slide over it,
   it cannot be cut off, it needs no reveal, and it is in the tab order where a
   reader arrives at it. `.feature-copy` is the left column on a plain
   `.feature` and the right column on a `.flip`, so the side George specified
   for each block falls out of the markup.

   ⚠ COLOUR AND SIZE ARE UNCHANGED and that is deliberate — the fill is the
   whole reason this control reads, and TWO ATTEMPTS AT A HALO ARE IN THE DEPLOY
   LOG so a third does not get invented. It was a green ring plus an amber bloom
   (2026-08-24, morning; George read the ring as a focus outline), then one soft
   ellipse breathing on `transform` and `opacity` (same day, afternoon; "the
   back to features aura doesnt look good take it out"). What separates it from
   this page is that it is an opaque saturated GREEN mass, the only element in
   the section that is neither ivory nor gold. Prominence came from the fill. */
/* ⚠⚠ HIDDEN UNTIL A CARD IS CLICKED (George, 2026-08-24: "back to features
   should only be activated when you click on that specific feature, it
   shouldn't be a constantly present icon"). `site.js` puts `.lit` on the ONE
   block a teleport lands on and takes it off every other; scrolling past a
   feature shows nothing, which is the point — a visitor who scrolled here can
   scroll back, and only a visitor who was thrown here needs a way out.
   ⚠ `display:none`, NOT `opacity:0` — a hidden-but-laid-out button leaves a
   66px hole under the last bullet of all eleven blocks, which is worse than the
   button.
   ⚠ THE COST, STATED: with no JavaScript no block is ever `.lit`, so the anchor
   never appears. That is a real regression from the version that always showed
   it, and it is accepted — without the script there is no teleport either, so
   nobody is ever stranded; the page is an ordinary scrolling document. */
/* ⚠⚠ AND IT IS OUT OF FLOW, WHICH IS THE FIX FOR A SHIFT GEORGE COULD SEE
   (2026-08-25: *"I don't like what happens when I click back to features — the
   description kind of shifts abruptly and doesn't look sleek or professional"*).

   MEASURED on `#f-lease` at 1440, before and after the click:

       resting   h3 at 1002 · copy column 162px
       lit       h3 at  947 · copy column 240px      (the button is 78px of box)
       +16ms     h3 at  986 · copy column 162px      ← one frame, 39px of jump

   `.feature` is `align-items:center`, so the copy column is centred against a
   397px art column. Toggling `display` on a 78px button therefore does not just
   add a button — it re-centres the entire copy column, moving the heading and
   every bullet by HALF the button's box, instantly, twice per visit. Nothing was
   animating it because nothing knew it had moved: it is a layout change, not a
   transition.

   ⚠ SO THE BUTTON HANGS BELOW THE COLUMN INSTEAD OF SITTING IN IT. `position:
   absolute; top:100%` takes it out of flow, so the column measures 162px whether
   it is shown or not and the copy never moves at all — 0px, not "eased to 39px".
   Animating the shift was the other option and it is the wrong one: the sleekest
   version of a movement that should not happen is for it not to happen.
   ⚠ IT FITS INSIDE THE BLOCK. The copy column is the SHORTER of the two (162 vs
   397), so centring leaves ~117px of slack beneath it and the button needs 68.
   If a block ever appears whose copy column is the taller one, this will hang
   into the next block — check that before adding a twelfth.
   ⚠ AND IT GOES BACK IN FLOW WHEN THE BLOCK STACKS (see the one-column media
   query): with a single column there is no shorter sibling to centre against, so
   there is no slack to hang in and no re-centring to avoid. */
.feature .tele-back{display:none}
.feature.lit .tele-back{display:flex}
.tele-back{
  position:absolute;top:100%;left:0;right:0;
  display:flex;align-items:center;gap:9px;
  margin:26px auto 0;width:fit-content;
  font-family:var(--ui);font-size:12.5px;font-weight:700;
  letter-spacing:.1em;text-transform:uppercase;text-decoration:none;
  color:#FBF8F1;background:var(--accent);
  border:1px solid var(--accent);border-radius:var(--radius);
  padding:15px 26px;
  box-shadow:var(--shadow-lift);
  transition:background .14s,border-color .14s,box-shadow .14s;
}
/* ⚠ `display:flex` AND NOT `inline-flex`, WHICH IS THE WHOLE CENTRING. A flex
   container is block-level, so `margin:0 auto` centres it in `.feature-copy`;
   an INLINE-flex box is placed by its parent's text alignment instead and
   `auto` margins do nothing to it. `width:fit-content` keeps the pill the
   width of its own words either way. Nothing here touches `.feature-copy`, so
   the heading and the tick list keep centring through the rules they already
   have and `/consultation`'s tick list stays left-aligned. */
/* ⚠ NOT `--ink`. The original hover flipped an outline button to a solid dark
   one, which on a green pill goes black. A darker green is the same statement
   in the colour the button already is. */
.tele-back:hover{background:#4C5931;border-color:#4C5931;color:#FBF8F1}
.tele-back span{font-size:15px;line-height:1;letter-spacing:0}

/* ══ the workbook, drawn as a workbook ═════════════════════════════════════
   The export block used to be four rows saying ".xlsx". This is the file
   `reconciliationExcel.js` actually writes — the olive title band, the header
   block, a section head, the summary rows, a red variance cell and a tab per
   tenant. Column letters and row numbers are what make a picture read as a
   spreadsheet rather than as another list; they cost two flex rows. */
.mock.sheet{font-size:11px;line-height:1.35}
.sheet-bar{
  display:flex;align-items:center;gap:6px;
  padding:8px 11px;background:var(--panel-2);border-bottom:1px solid var(--hair)}
.sheet-bar i{width:8px;height:8px;border-radius:50%;background:var(--border);flex:none}
.sheet-bar span{
  margin-left:6px;font-size:12px;color:var(--faint);font-weight:600;
  white-space:nowrap;overflow:hidden;text-overflow:ellipsis}
.sheet-cols,.sheet-grid .sr{
  display:grid;grid-template-columns:26px minmax(0,1.55fr) 52px 84px 46px}
.sheet-cols b{
  font-size:12px;font-weight:700;color:var(--faint);text-align:center;
  padding:3px 0;background:var(--panel-2);border-right:1px solid var(--hair);
  border-bottom:1px solid var(--hair)}
.sheet-grid .sr{border-bottom:1px solid var(--hair);min-height:21px;align-items:stretch}
.sheet-grid .sr:last-child{border-bottom:0}
/* the row number gutter — the same treatment as the column letters */
.sheet-grid .sr > b{
  font-size:12px;font-weight:700;color:var(--faint);text-align:center;
  padding:4px 0;background:var(--panel-2);border-right:1px solid var(--hair)}
.sheet-grid .sr > span{padding:4px 7px;border-right:1px solid var(--hair);min-width:0;
  overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
.sheet-grid .sr > span:last-child{border-right:0}
/* ⚠ THE BAND SPANS THE SHEET because `addTenantSheet` merges A..L for it. A
   title sitting in one cell would be a different file. */
.sheet-grid .band{
  grid-column:2 / -1;background:var(--accent);color:#FBF8F1;
  font-weight:700;font-size:11.5px;padding:6px 9px !important}
.sheet-grid .sec{
  grid-column:2 / -1;background:var(--accent-soft);color:var(--accent);
  font-weight:700;font-size:11px;letter-spacing:.08em;text-transform:uppercase}
.sheet-grid .k{color:var(--muted);font-weight:700;font-size:12px}
.sheet-grid .v{grid-column:3 / -1;color:var(--ink)}
.sheet-grid .c{color:var(--ink)}
.sheet-grid .cap{font-weight:700;background:var(--panel-2)}
.sheet-grid .n{text-align:right;font-variant-numeric:tabular-nums}
/* green under estimate, red over — `varianceFill`, same rule as the file */
.sheet-grid .n.over{background:var(--danger-soft);color:var(--danger);font-weight:700}
.sheet-grid .n.under{background:var(--forest-soft);color:var(--forest);font-weight:700}
.sheet-tabs{
  display:flex;gap:2px;padding:6px 8px 7px;background:var(--panel-2);
  border-top:1px solid var(--hair);overflow:hidden}
.sheet-tabs span{
  font-size:11.5px;font-weight:600;color:var(--faint);
  padding:4px 9px;border-radius:2px 2px 0 0;white-space:nowrap}
.sheet-tabs span.on{background:var(--panel);color:var(--ink);
  box-shadow:0 -1px 0 var(--border) inset}

/* ══ the lease review, with the lease in it ════════════════════════════════
   The page on the left is CSS bars, not an image: nothing to download and
   nothing to re-shoot. Its two struck lines carry the severity colour of the
   flag beside them, so the picture says "these words, this problem" without a
   caption doing it. */
.rev-grid{display:grid;grid-template-columns:104px minmax(0,1fr);gap:16px;align-items:start}
.rev-page{
  background:var(--panel-2);border:1px solid var(--hair);border-radius:2px;
  padding:11px 10px;display:grid;gap:5px;box-shadow:var(--shadow)}
.rev-page i{display:block;height:4px;border-radius:2px;background:var(--border);opacity:.55}
.rev-page i.s{width:58%}
/* ⚠ THE STRIKE IS THE POINT, not the fill. A flagged clause is present and
   readable — it is a term to weigh, not an error — so it is marked, not
   blanked. Colour matches the flag's severity. */
.rev-page i.hit{position:relative;opacity:1}
.rev-page i.hit.bad{background:var(--danger-soft)}
.rev-page i.hit.warn{background:var(--gold-soft)}
.rev-page i.hit::after{
  content:'';position:absolute;left:0;right:0;top:50%;height:1px}
.rev-page i.hit.bad::after{background:var(--danger)}
.rev-page i.hit.warn::after{background:var(--gold)}
.rev-list{display:grid;gap:10px}
/* ⚠ A FIXED CHIP COLUMN, not `auto`. Each flag is its own grid, so with an
   auto column "MEDIUM" (wider than "HIGH") pushed its own title and note a few
   pixels right of the rows above — three flags, three different left edges,
   which reads as a rendering fault rather than as a list. */
.rev-flag{
  display:grid;grid-template-columns:58px minmax(0,1fr);gap:3px 9px;
  align-items:baseline;padding-bottom:10px;border-bottom:1px solid var(--hair)}
.rev-flag:last-child{padding-bottom:0;border-bottom:0}
.rev-flag .sev{
  grid-row:1 / span 2;align-self:start;justify-self:start;margin-top:1px;
  font-size:11px;font-weight:700;letter-spacing:.09em;text-transform:uppercase;
  padding:3px 6px;border-radius:2px;white-space:nowrap}
.rev-flag .sev.bad{background:var(--danger-soft);color:var(--danger)}
.rev-flag .sev.warn{background:var(--gold-soft);color:var(--gold)}
.rev-flag b{font-size:12.5px;font-weight:700;color:var(--ink)}
.rev-flag > span:last-child{font-size:11.5px;color:var(--muted);line-height:1.45}
.rev-foot{
  margin:14px 0 0;padding-top:11px;border-top:1px solid var(--hair);
  font-size:12px;color:var(--faint)}
.rev-foot b{color:var(--muted);font-weight:700}
@media (max-width:520px){
  /* The page thumbnail is the first thing to go: at this width it is 60px of
     grey bars beside text that needs every pixel. The flags are the content. */
  .rev-grid{grid-template-columns:minmax(0,1fr)}
  .rev-page{display:none}
}

/* ══ scrolling through the features ════════════════════════════════════════
   George, 2026-08-23: "I want a cool mechanic for scrolling throughout the
   features." Each block's mock tips as it passes: nose-up on the way in,
   level at the middle of the screen, nose-down on the way out, with a little
   counter-drift so it does not travel at exactly the speed of the page. It is
   the same gesture as picking a card up off a table and putting it down.

   `--p` is 0 as the block enters the viewport and 1 as it leaves — site.js
   writes it, this file spends it. One `transform` per visible block, three
   blocks visible at most, so the whole mechanic is one composited property
   and nothing here reads back layout.

   ⚠ `--p` DEFAULTS TO .5 — the LEVEL, settled state. A block whose script
   never ran, or a browser asking for less motion, gets an untilted mock
   sitting exactly where it belongs. Never default it to 0.
   ⚠ AND THE TILT IS ON `.mock`, NOT ON `.feature-art`. The art element is
   what the scroll-reveal fades and lifts (`.rv`); putting a second transform
   on it means two systems writing one property, and the later one wins
   silently. Different element, different property, no collision. */
.feature-art .mock{
  --p:.5;
  transform:perspective(1400px)
            rotateX(calc((.5 - var(--p)) * 5deg))
            translateY(calc((.5 - var(--p)) * -20px));
  transform-origin:50% 50%;
  will-change:transform;
}
/* ⚠ NO `transition` ON THAT TRANSFORM. It is driven from the scroll position
   every frame; a transition on top of it lags the tilt behind the page and
   turns a solid connection into a wobble. */
@media (max-width:900px){
  /* Off on a phone: the block is full width, the tilt is read as a rendering
     fault rather than as depth, and it costs a composited layer per block on
     the device least able to spare one. */
  .feature-art .mock{transform:none;will-change:auto}
}

/* ── responsive ─────────────────────────────────────────────────── */
/* Between full desktop and the fold-up below: the hub's aspect box shrinks
   with the viewport while the cards are sized in px, so the outer branches
   would poke past the paper. Smaller cards keep the whole scatter inside —
   at 901px the outer card's left edge lands ~15px into the wrap padding,
   measured, not assumed. */
/* ⚠ 1180, NOT 900, AND ONLY THE NAV'S COPY. At 12.5px UI the tagline cost about
   150px and cleared 900px comfortably. At the wordmark's 25px serif it costs
   roughly 350px, which pushes the nav's wrap point up past 1024 and 1152 — two
   widths a lot of laptops actually run at. The footer's copy is still small and
   is hidden by the 900px rule below, unchanged. */
@media (max-width:1180px){
  .nav-brand .nav-tag{display:none}
}
@media (max-width:900px){
  .nav-tag{display:none}
  .section{padding:64px 0}
  .hero{padding-top:56px}
  .hero-in,.feature,.form-grid{grid-template-columns:minmax(0,1fr);gap:40px}
  /* ⚠ AND THE BACK BUTTON RETURNS TO THE FLOW HERE. Out of flow it hangs in
     the slack that `align-items:center` leaves under the shorter column —
     and at one column there is no other column, so there is no slack and
     nothing to re-centre. In flow it simply sits under the last bullet;
     absolute, it would overlap the mock stacked beneath it. */
  .feature .tele-back{position:static}
  /* ⚠ NAMED EXPLICITLY, because `.hero-body` is its own grid and the rule above
     only reaches `.hero-in`. The earlier version of this hero used
     `.hero-in.wide-art` at (0,2,0), which beat the (0,1,0) rule above even
     inside this media query and left About two-column at 390px with the
     headline wrapping to one word per line. Specificity does not care which
     block a rule is written in — and a nested grid does not inherit its
     parent's template at all. */
  .hero-body{grid-template-columns:minmax(0,1fr);gap:40px}
  .hero-head{margin-bottom:8px}
  .hero-in{padding-bottom:64px}
  /* the mock goes BELOW the prose, never squashed beside it */
  .feature.flip .feature-copy{order:1}
  .feature.flip .feature-art{order:2}
  /* ⚠ THE HUB STOPS BEING A DIAGRAM HERE. An eleven-card ring cannot be read
     at this width, so the wires go, the core sits on top, and the same eleven
     cards lay themselves out as a plain two-column grid. No second copy of
     anything; only the positioning changes. The grid is the breadth carrier
     on a phone. */
  .hero-hub{aspect-ratio:auto;width:100%}
  /* ⚠ ONE SVG AGAIN, so one selector — the pulses are back inside `.hub-wires`
     and go with it. This briefly needed to name a second `.hub-trace` overlay,
     and hiding only the wires left that overlay alive, drawing eleven gold laps
     around rectangles that had moved into a stacked grid. Anything ever added
     to the hub as a separate layer has to be named here too. */
  .hub-wires{display:none}
  /* ⚠ position:RELATIVE, not static — the ring inside is absolutely
     positioned, and a static core would hand it to .hero-hub, which draws a
     ring around the entire grid. left/top must be zeroed too: on a relative
     box the desktop's 50%/50% become an offset, not an anchor. */
  .hub-core{position:relative;left:0;top:0;translate:none;margin:0 auto 22px}
  .hub-cards{display:grid;grid-template-columns:1fr 1fr;gap:8px}
  .hcard{position:static;transform:none;width:auto}
  /* ⚠ `.hcard.c` MUST BE NAMED HERE. It is two classes deep, so it outranks
     the single-class reset above and the axis card would keep its
     translate(-50%,-50%) in the static stack — shifted half its width left
     and half its height up, on top of the card before it. */
  .hcard.l,.hcard.r,.hcard.c{left:auto;right:auto;transform:none}
  .hcard a{align-items:flex-start;height:100%;min-height:0}
  .hvis{margin-top:1px}
  .hero-top{margin-bottom:34px}
  .hero-bottom{padding-bottom:64px}
  .foot-in{grid-template-columns:repeat(2,minmax(0,1fr));gap:32px}
  .cta{padding:48px 26px}
  /* ⚠ IT STAYS TWO COLUMNS ON A PHONE, JUST NARROW ONES. This used to collapse
     to a single column with the number stacked above its heading — which was
     right when the number was loose type, and is wrong now that a spine runs
     through it: at one column the ring and the copy share a left edge, so the
     line would run straight down the middle of the paragraphs. 44px is the
     ring's own width, so the only thing that changes is the gutter. */
  .tl-item{grid-template-columns:44px minmax(0,1fr);gap:16px}
}
@media (max-width:680px){
  .wrap{padding:0 20px}
  /* The links don't fit beside the brand and both buttons at 390px. They wrap to
     a second row rather than disappearing — hiding them behind nothing left Compare
     and About reachable only from the footer, which is not navigation. No
     hamburger, so no JS on any page and nothing to fail. */
  :root{--navh:112px}   /* two rows once the links wrap */
  .nav-tag{display:none}
  .nav-in{flex-wrap:wrap;height:auto;gap:10px;padding-top:12px}
  .nav-links{order:3;width:100%;justify-content:center;gap:2px;
    border-top:1px solid var(--hair);padding:8px 0 4px;margin-top:2px}
  .nav-links a{padding:8px 11px;font-size:11px}
  .nav-cta{padding-left:0}
  .field-row{grid-template-columns:minmax(0,1fr)}
  .foot-in{grid-template-columns:minmax(0,1fr)}
  /* ⚠ ONE COLUMN ON A PHONE, not two. Two columns at 390px leaves ~170px per
     card, which forces both lines to wrap three deep and shrinks the sub-line
     back below the readable floor — the exact fault this round fixed. One
     column gives each card the full width, so title and proof each hold a
     line and the type stays at its desktop size. */
  .hub-cards{grid-template-columns:1fr;gap:7px}
  /* ⚠ `padding-right` RESTATED, not inherited: this shorthand would otherwise
     reset it to 12 and the title would run under the `.harr` arrow. */
  .hcard a{padding:10px 12px;padding-right:22px;box-shadow:none;align-items:center}
  .hvis{margin-top:0}
  .btn{flex:1 1 auto}
}
/* ⚠ THIS BLOCK IS THE SECOND HALF OF A PAIR — `site.js` checks the same query
   and wires up nothing when it matches. Both halves are needed: CSS alone
   cannot stop a scroll listener, and JS alone cannot stop a keyframe that the
   stylesheet started. Anything animated below must land in its FINISHED state
   here, never its starting one, or asking for less motion hides content. */

@media (prefers-reduced-motion:reduce){
  html{scroll-behavior:auto}
  *{transition:none!important;animation:none!important}
  .rv{opacity:1;transform:none}
  /* The hub, stated in its finished state — same discipline as .rv above.
     The wires' base dashoffset is already 0 and the cards' base opacity is
     already 1, so these lines are the backstop, not the mechanism: every
     animated thing in the hub must LAND here, never start here. site.js
     wires no cycle under reduced motion, so no pulse and no tick — the
     diagram simply stands, complete. */
  .hcard{opacity:1;translate:none}
  .hwire{stroke-dashoffset:0}
  .hdot{opacity:1}
  /* The outward pulse is ornament and rests invisible, so killing its
     animation is the whole story — but state it, because a dash pattern
     left mid-travel would be a stray accent stub on every branch. */
  .hpulse{opacity:0;stroke-dashoffset:0}
  /* ⚠ THE CARD'S GLOSS NEEDS ITS OWN LINE, because `*` above CANNOT REACH IT.
     `*` is the universal ELEMENT selector and a pseudo-element is not an
     element — the same trap that let the hero's radiate keep animating through
     a reduced-motion block one round ago. The highlight itself stays (it is a
     static reflection, not motion); only its hover transition is stopped. */
  .hcard a::before{transition:none!important}
  /* ⚠ `.tele-back` NEEDS NOTHING HERE ANY MORE, and it is worth one line
     saying why rather than leaving the absence to look like an oversight. It
     briefly had a breathing aura on a `::after`, which DID need stating —
     `*{animation:none!important}` cannot reach a pseudo-element, the same trap
     as `.hcard a::before` above. The aura was removed on George's word the same
     day, so the button is a plain filled pill and the `*` rules cover it. */
  /* The raking light is not built at all under reduced motion — `RAKE.build`
     returns null before it makes a canvas — so there is nothing here to state.
     Its ground, the static lattice on `.hero::before`, never moved. */
}


/* ── legal pages ────────────────────────────────────────────────────────
   The first long-form prose on this site. Measure is capped well below the
   page width because a privacy policy nobody can read is the same as not
   having one. */
/* One centred reading column for the whole page, heading included — a 68ch
   measure pinned to the left of a 1280px window reads as an unfinished layout. */
.legal-wrap{max-width:68ch;margin:0 auto}
.legal{max-width:none}
.legal-meta{font-size:12.5px;color:var(--faint);margin:0 0 40px;
  padding-bottom:18px;border-bottom:1px solid var(--hair)}
.legal h2{font-size:clamp(26px,3vw,32px);margin:44px 0 14px;scroll-margin-top:calc(var(--navh) + 16px)}
.legal h2:first-of-type{margin-top:0}
.legal h3{font-size:19px;margin:28px 0 8px}
.legal p{color:var(--muted);font-size:15.5px;line-height:1.68;margin:0 0 14px}
.legal p strong,.legal li strong{color:var(--ink);font-weight:600}
.legal ul{margin:0 0 16px;padding-left:20px;color:var(--muted);font-size:15.5px;
  line-height:1.62;display:grid;gap:9px}
.legal a{color:var(--accent);text-decoration:none;border-bottom:1px solid transparent}
.legal a:hover{border-bottom-color:var(--accent)}
.legal .callout{
  background:var(--panel);border:1px solid var(--border);border-left:3px solid var(--gold);
  border-radius:var(--radius);padding:18px 22px;margin:0 0 20px;
}
.legal .callout p:last-child{margin-bottom:0}
.legal .callout.plain{border-left-color:var(--accent)}
/* The sub-processor / who-else-sees-it list. Named rows, because "trusted
   partners" is the phrase a privacy policy uses when it doesn't want to say. */
.who{display:grid;gap:1px;background:var(--border);border:1px solid var(--border);
  border-radius:var(--radius);overflow:hidden;margin:0 0 20px}
.who-row{background:var(--panel);padding:14px 18px;display:grid;
  grid-template-columns:150px minmax(0,1fr);gap:16px;align-items:start}
.who-row b{font-size:14.5px;font-weight:600;color:var(--ink)}
.who-row span{font-size:14px;color:var(--muted);line-height:1.55}
.legal-toc{display:flex;flex-wrap:wrap;gap:8px;margin:0 0 40px}
.legal-toc a{font-size:11px;font-weight:700;letter-spacing:.09em;text-transform:uppercase;
  color:var(--muted);background:var(--panel-2);border:1px solid var(--hair);
  border-radius:100px;padding:6px 13px;text-decoration:none}
.legal-toc a:hover{color:var(--accent);border-color:var(--accent)}
@media (max-width:680px){
  .who-row{grid-template-columns:minmax(0,1fr);gap:4px}
}
