/* Structure and depth only. Every colour, gradient and shadow comes from the token
   custom properties, so the four concepts are four palettes of one interface rather
   than four stylesheets.

   Three things arrive from the token package and are used throughout:
     --gradient-*  composed gradients, defined once and re-derived in dark mode
     --shadow-1..3 elevation, tinted with the concept's own ink rather than black
     --ring        the focus signal
   None of them are declared here. If a surface needs a new one it belongs upstream. */
*{box-sizing:border-box;margin:0;padding:0}

/* The page ground: the flat surface colour with one very soft brand glow whose centre
   sits above the top of the document, so only its shoulder is ever on screen. It is
   full-bleed because it lives on the body — the same wash painted on `main` would be a
   1140px rectangle with two visible vertical edges.

   It tiles rather than sitting once at the top. Painted no-repeat across the first
   560px, the wash was gone by the second screenful and every page below the fold was
   plain white — which is what made the colour look like it had been spent on the
   header and the cards and nothing else. The tile is 150vh, so the two blooms inside
   --gradient-page arrive about one and a half screens apart however long the document
   is: a short page gets one, a long one keeps receiving them. Sizing it in viewport
   units rather than pixels is what keeps that true on a phone as well as a desktop.

   It is still deliberately weak. An earlier attempt ran the full-strength glow across
   the whole page and turned every interior view mint; a wash you can name is a wash
   that is too strong. This one should only be missed when it is removed. */
body{font-family:var(--font-body);color:var(--on-surface);line-height:1.55;
     background-color:var(--surface);
     background-image:var(--gradient-page);
     background-repeat:repeat-y;
     background-size:100% 150vh;
     background-position:center top;
     -webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale}
/* Nothing may be wider than the screen. A full-bleed hero beside padded content is
   how a page ends up scrolling sideways on a phone. */
img,video,svg,table{max-width:100%}
a{color:var(--brand);text-decoration:none}
a:not(.btn):not(.tile):not(.feature):not(.brand):not(.chip):not(.theme-chip):not(.gallery-open):not(.plan):not(.file):hover{
     text-decoration:underline;text-underline-offset:3px;
     text-decoration-color:color-mix(in srgb,currentColor 45%,transparent)}
/* The page width, and it is ONE number for the whole application: 78 views are laid
   out inside `.wrap`, so this token is where the site's margins are decided.

   ~~1140px~~ was chosen against a laptop and looked like a column down the middle of a
   large monitor — on a 2,500px display it left more than half the screen empty and made
   a property card 350px wide, which is the size at which photography stops selling
   anything. 1760px, and it is a CAP rather than a target: the page still centres, and a
   line of prose is still capped in `ch` by `.lede`, `.prose` and `.section-head p`, so
   widening the frame never widens a paragraph past the point it is readable.

   The gutter grows with the viewport instead of sitting at a flat 24px, because a fixed
   gutter that is right on a phone is a hairline on a monitor. */
:root{--page-max:1760px}
.wrap{width:100%;max-width:var(--page-max);margin:0 auto;
      padding:0 clamp(20px,2.6vw,48px)}
/* Vertical rhythm for a page whose content starts straight after the header. It used
   to be an inline `padding:44px 0` on the same element as .wrap, and the shorthand
   also set the horizontal padding to zero — so on a 390px phone every headline,
   filter bar and price on the site sat hard against the glass. Longhands, and in a
   class, so .wrap keeps its own gutter. */
.pad{padding-top:44px;padding-bottom:44px}
.pad-lead{padding-top:52px}
.pad-page{padding-top:64px;padding-bottom:64px}

/* ---- header -------------------------------------------------------------- */

/* The top padding is LARGER than the bottom, and deliberately: this is the first
   element on the page, so its top edge is the window's and the logo sat 18px from the
   glass — too close to read as a margin at all (reported on the home page, 17 September
   2026). The bottom stays 18px because the gradient rule below the nav is spaced against
   the links, not against the top of the page. */
.nav{display:flex;align-items:center;justify-content:space-between;gap:22px;
     padding:clamp(26px,2.4vw,34px) 0 18px;position:relative;flex-wrap:wrap}
/* A rule that starts as a border and ends as nothing. A flat 1px line across the full
   width is the single most dated thing on a page like this. */
.nav::after{content:"";position:absolute;left:0;right:0;bottom:0;height:1px;
     background:var(--gradient-rule)}

/* The company lockup, used whole rather than rebuilt from a mark and type. Two files:
   the supplied one, and a version with the ink reversed out to bone. Roughly half the
   artwork is near-black, so on a dark header the original reads as a lone green
   triangle — which is what made it look timid.

   Visibility is decided ONLY here. Sizing lives on .brand-logo and deliberately sets
   no display property: a (0,2,0) sizing rule outranking a (0,2,0) swap rule is how
   both versions ended up on screen at once. */
/* order:0, not order:1. This rule spent a while behind a nonsense ancestor chain and
   so never applied; the moment the chain was deleted the `order:1` inside it sent the
   lockup to the right-hand end of the header, past the navigation. The lockup is the
   first thing in the DOM and must be the first thing in the row. */
.nav .brand{display:flex;align-items:center;flex:0 0 auto;order:0}
.brand-logo{height:52px;width:auto;max-width:min(60vw,300px);object-fit:contain}
.logo-on-light{display:block}
.logo-on-dark{display:none}
[data-theme="dark"] .logo-on-light{display:none}
[data-theme="dark"] .logo-on-dark{display:block}
[data-theme="light"] .logo-on-light{display:block}
[data-theme="light"] .logo-on-dark{display:none}

/* :not(.btn) matters. `.nav a` is (0,1,1) and `.btn` only (0,1,0), so without this
   the nav's muted link colour wins over a button's own text colour — which in light
   mode painted "Sign in" dark green on dark green in all three concepts. Scoping it
   here fixes the cause rather than patching each button. */
.nav a:not(.btn){font-size:13px;color:var(--on-surface-muted);transition:color .2s ease}
.nav-links{display:flex;align-items:center;gap:22px;flex:1 1 auto}
.nav-links a:not(.btn):hover{color:var(--brand)}
/* Where you are. Colour alone would carry this for most people and for nobody reading
   in a high-contrast mode, so weight and an underline carry it too — and
   `aria-current="page"` in the markup carries it for a screen reader. Weight matches
   `.surface-nav a.is-here` in portal.css: the signed-in navigation has always marked the
   current tab this way and two different signals for one idea is one to unlearn.

   The underline is an inset shadow rather than a border ON PURPOSE. The mobile
   breakpoint gives every nav link a 1px bottom border as a divider, and
   `.nav-links a:not(.btn)` outranks the `.nav-links a` that sets it — so a border here
   would quietly erase the dividers on every phone. A shadow does not enter that
   cascade, and it does not occupy layout, so nothing shifts. */
.nav-links a:not(.btn).is-here{color:var(--brand);font-weight:600;
  box-shadow:inset 0 -2px 0 var(--brand)}
.nav-gap{margin-left:auto}
.nav-phone{display:inline-flex;align-items:center;gap:9px;font-weight:600;
           white-space:nowrap}
.nav-phone .dot{display:inline-flex;align-items:center;justify-content:center;
                width:31px;height:31px;border-radius:50%;flex:0 0 auto;
                background:var(--gradient-brand-soft);color:var(--on-brand);font-size:14px;
                box-shadow:var(--shadow-brand)}
.nav a.nav-phone:not(.btn){color:var(--on-surface);font-size:14px}

/* The hamburger exists only below the breakpoint. Hiding it here rather than showing
   it there means a wide viewport never renders it, even for one frame. */
/* ~~`display:flex` on the BUTTON, with the three bars stretched by `align-items`.~~
   Reported from an iPhone, 12 September 2026: the button rendered as an empty square —
   the box styles applied, the bars did not. **Safari does not lay a `<button>` out as a
   flex container reliably**, so the bars never received a width from `stretch` and were
   0 × 2.5px. It opened the menu perfectly and looked broken, which is why nobody caught
   it: nothing failed.

   So the bars are positioned, not flexed, and each one states its own width. No
   `gap`, no `stretch`, no flex on a button — nothing here depends on how a `<button>`
   chooses to lay its children out. */
.nav-toggle{display:block;position:relative;order:2;flex:0 0 auto;
            width:46px;height:42px;padding:0;cursor:pointer;
            background:var(--surface);border:1px solid var(--border);
            border-radius:var(--radius-small);box-shadow:var(--shadow-1);
            -webkit-appearance:none;appearance:none}
.nav-toggle span{position:absolute;left:50%;width:20px;height:2px;margin-left:-10px;
                 background:var(--on-surface);border-radius:2px;
                 transition:transform .18s ease, opacity .12s ease}
.nav-toggle span:nth-child(1){top:14px}
.nav-toggle span:nth-child(2){top:20px}
.nav-toggle span:nth-child(3){top:26px}
/* Open, it is a close control and says so — the same button, so the eye does not have to
   find a second one. Driven by `aria-expanded`, which nav.js already maintains for the
   screen reader: one source of truth for "is this open", not a second class to forget. */
.nav-toggle[aria-expanded="true"] span:nth-child(1){transform:translateY(6px) rotate(45deg)}
.nav-toggle[aria-expanded="true"] span:nth-child(2){opacity:0}
.nav-toggle[aria-expanded="true"] span:nth-child(3){transform:translateY(-6px) rotate(-45deg)}
.nav-toggle:focus-visible{outline:2px solid var(--brand);outline-offset:2px}


/* ---- buttons ------------------------------------------------------------- */
/* Element-qualified so a button always outranks a container's link colour, wherever
   it is placed. A button whose label is unreadable is not a button.

   The fill is --gradient-brand-soft: a short trip from --brand towards --brand-muted,
   which catches the light without ever getting light enough to lose --on-brand. The
   cast underneath is --shadow-brand, the concept's own green rather than grey, which
   is the difference between a button sitting on the page and floating over it. */
.btn.wide{width:100%;padding:13px;font-size:14px}
a.btn, button.btn, .btn{display:inline-flex;align-items:center;justify-content:center;gap:8px;
     background:var(--gradient-brand-soft);color:var(--on-brand);
     font-size:13px;font-weight:600;padding:10px 20px;border-radius:var(--radius-small);
     border:0;cursor:pointer;text-align:center;box-shadow:var(--shadow-brand);
     transition:transform .18s ease,box-shadow .28s ease,filter .28s ease}
.btn:hover{transform:translateY(-1px);filter:saturate(1.06) brightness(1.07);
     box-shadow:var(--shadow-2),var(--shadow-brand)}
.btn:active{transform:translateY(0)}
.btn:focus-visible{outline:2px solid var(--brand);outline-offset:3px}

/* The quiet second action on a light ground. No coloured cast — two glowing buttons
   side by side is two primary actions. */
a.btn.outline, button.btn.outline, .btn.outline{background:var(--surface);
     color:var(--brand);border:1px solid var(--border-strong);box-shadow:var(--shadow-1)}
.btn.outline:hover{border-color:var(--brand);background:var(--brand-soft);
     color:var(--on-brand-soft);filter:none}

/* Neutral, for an action nobody should be encouraged towards — signing out. */
a.btn.quiet, button.btn.quiet, .btn.quiet{background:var(--surface);
     color:var(--on-surface-muted);border:1px solid var(--border);box-shadow:none}
.btn.quiet:hover{border-color:var(--border-strong);color:var(--on-surface);
     box-shadow:var(--shadow-1);filter:none}

/* ---- type ---------------------------------------------------------------- */

/* An eyebrow used to be four words of small green text that nobody saw. As a soft
   brand chip it is the one place the colour appears in ordinary body copy. */
.eyebrow{display:inline-flex;align-items:center;gap:8px;font-size:10px;
     letter-spacing:.16em;text-transform:uppercase;font-weight:700;
     color:var(--on-brand-soft);background:var(--brand-soft);
     border-radius:var(--radius-pill);padding:5px 12px 5px 10px}
.eyebrow::before{content:"";width:5px;height:5px;border-radius:50%;flex:0 0 auto;
     background:var(--brand)}

/* Fluid, so a heading never needs an inline size — an inline size is the one thing a
   breakpoint cannot override, and every h1 on the site had one. */
h1{font-family:var(--font-display);font-size:clamp(28px,5.4vw,46px);
   line-height:1.06;letter-spacing:-.025em}
h2{font-family:var(--font-display);font-size:clamp(19px,2.5vw,22px);letter-spacing:-.012em}
.lede{font-size:14.5px;color:var(--on-surface-muted);max-width:520px}

/* ---- cards --------------------------------------------------------------- */

.fork{display:grid;grid-template-columns:1fr 1fr;gap:20px;margin-top:36px}
.card{position:relative;overflow:hidden;border:1px solid var(--border);
      border-radius:var(--radius);padding:26px;background:var(--gradient-panel);
      box-shadow:var(--shadow-1);
      transition:box-shadow .3s ease,border-color .3s ease,transform .3s ease}
.card:hover{box-shadow:var(--shadow-2);border-color:var(--border-strong)}
/* Was a full accent-coloured border, which read as a warning. A three-pixel gradient
   along the top edge marks the same card and costs nothing in noise. */
.card.accent{border-color:var(--border-strong)}
.card.accent::before{content:"";position:absolute;top:0;left:0;right:0;height:3px;
      background:var(--gradient-brand)}
.card p{font-size:13.5px;color:var(--on-surface-muted);margin:8px 0 16px}

/* Longhands, for the reason given above .page: the footer is `.wrap.foot`, and
   `padding:34px 0` zeroed the gutter .wrap gives it — so on every page of the site the
   copyright line sat hard against the left edge of the glass and the appearance switch
   against the right, under content that was inset. The rule is inset to match. Seen
   18 Sep 2026 in the first full-page screenshots anyone had taken. */
.foot{margin-top:72px;padding-top:34px;padding-bottom:34px;position:relative;
      font-size:12px;color:var(--on-surface-subtle)}
.foot::before{content:"";position:absolute;top:0;left:clamp(20px,2.6vw,48px);
      right:clamp(20px,2.6vw,48px);height:1px;
      background:var(--gradient-rule)}

.shell{min-height:60vh;display:flex;align-items:center;justify-content:center;
       padding:80px 0;color:var(--on-surface-subtle);font-size:13px}

/* ---- listings ------------------------------------------------------------ */

/* Three across is right up to about 1,500px. Beyond it the wider frame would stretch
   three cards to 590px each — a card the size of a hero, and four rows of them to
   scroll — so a fourth column is added instead. That still leaves each card LARGER
   than the 350px it was at 1140px wide, which was the complaint. */
.grid{display:grid;grid-template-columns:repeat(3,1fr);gap:22px;margin-top:26px}
/* Cards in a row must line up, whatever the photograph or the length of the title.
   Grid stretches every tile to the tallest in its row, so each part of the card is
   given a fixed job:
     .shot  never grows or shrinks   — flex:0 0 auto, or a portrait source (960×1280)
                                       stretches its cell and the row goes ragged
     h3     always two lines         — clamped, so a wrapping title cannot shove the
                                       price down on one card and not its neighbour
     .tags  pinned to the bottom     — margin-top:auto */
.tile{border:1px solid var(--border);border-radius:var(--radius);overflow:hidden;
      display:flex;flex-direction:column;background:var(--gradient-panel);
      box-shadow:var(--shadow-1);
      transition:transform .3s cubic-bezier(.2,.7,.3,1),box-shadow .3s ease,
                 border-color .3s ease}
.tile:hover{transform:translateY(-3px);box-shadow:var(--shadow-3);
      border-color:var(--border-strong)}
.tile .shot{flex:0 0 auto;aspect-ratio:4/3;position:relative;padding:0;overflow:hidden;
            background:var(--surface-muted);
            display:flex;align-items:center;justify-content:center;
            border-bottom:1px solid var(--border);
            font-size:10px;letter-spacing:.16em;text-transform:uppercase;
            color:var(--on-surface-subtle)}
.tile .shot img{width:100%;height:100%;object-fit:cover;display:block;
            transition:transform .7s cubic-bezier(.2,.7,.3,1)}
.tile:hover .shot img{transform:scale(1.04)}
.tile .body{padding:17px;display:flex;flex-direction:column;gap:6px;flex:1 1 auto}
.tile .ref{font-size:10px;letter-spacing:.14em;text-transform:uppercase;color:var(--on-surface-subtle)}
.tile h3{font-family:var(--font-display);font-size:16px;line-height:1.3;font-weight:600;
         display:-webkit-box;-webkit-line-clamp:2;line-clamp:2;-webkit-box-orient:vertical;
         overflow:hidden;min-height:calc(2 * 1.3em)}
.tile .where{font-size:12px;color:var(--on-surface-muted);
             white-space:nowrap;overflow:hidden;text-overflow:ellipsis}
/* Money is NOT set in the display face. Its naira sign reads as a struck-through N at
   card sizes — the two crossbars merge on a serif N — so ₦28,000,000 looks like a
   cancelled price on every card on /properties and /land. `portal.css`'s header states
   this rule and moved all portal money off the display face for exactly this reason; the
   public browse never got the same treatment. Found by a design review, 17 August 2026.
   `tabular-nums` so a column of prices lines up digit for digit. */
.tile .price{font-family:var(--font-body);font-size:20px;color:var(--brand);
             margin-top:2px;font-weight:600;letter-spacing:-.018em;
             font-variant-numeric:tabular-nums}
/* Reserved whether or not this property has bedrooms — a retail unit has none, and
   its card should still align with the duplex beside it. */
/* What a build's figure covers, directly under it. Smaller than the price and larger
   than the fine print: "from ₦240,000,000" is meaningless without "the plot and the
   build, standard finish", and a client who reads one without the other arrives at a
   meeting holding a number Ecoterra never quoted. */
.tile .price-basis{font-size:11px;line-height:1.45;color:var(--on-surface-subtle);
  margin-top:-2px}
.tile .spec{font-size:12px;color:var(--on-surface-muted);display:flex;gap:12px;
            flex-wrap:wrap;min-height:1.5em}
.tile .tags{display:flex;gap:6px;flex-wrap:wrap;margin-top:auto;padding-top:13px;
            border-top:1px solid var(--border)}

/* A status may never be carried by colour alone — a buyer checking whether a ₦240M
   unit is still available on a phone in daylight is exactly the person who cannot
   rely on a hue. Each state carries its word, and now a mark as well. */
/* The kind pill sits on the card footer's far right, level with the status —
   the two ends of one line: what it is, and whether you can still have it. */
.tile .tags .kind{margin-left:auto}
/* On a taken unit the kind is an icon, not the word — "Sold · Buy" read as an offer
   (walkthrough U-8; the user, 15 Oct 2026). See partials/kindtag.ejs. */
.tag.kind.icon{padding:3px 7px}
.tag.kind.icon svg{display:block}
.tag{display:inline-flex;align-items:center;gap:5px;
     font-size:10px;letter-spacing:.08em;text-transform:uppercase;font-weight:700;
     padding:4px 9px;border-radius:999px;border:1px solid var(--border);
     color:var(--on-surface-muted);background:var(--surface)}
.tag.ok{border-color:color-mix(in srgb,var(--status-complete) 42%,transparent);
        color:var(--status-complete);
        background:color-mix(in srgb,var(--status-complete) 9%,transparent)}
.tag.ok::before{content:"\2713"}
.tag.warn{border-color:color-mix(in srgb,var(--status-attention) 42%,transparent);
        color:var(--status-attention);
        background:color-mix(in srgb,var(--status-attention) 9%,transparent)}
.tag.warn::before{content:"\26A0"}
.tag.gone{opacity:.65;text-decoration:line-through}
.tag.gone::before{content:"\2715";text-decoration:none;display:inline-block}
.tag.title{border-color:color-mix(in srgb,var(--accent) 45%,transparent);
        color:var(--accent);background:color-mix(in srgb,var(--accent) 8%,transparent)}

/* Available to a screen reader, absent from the page. */
.visually-hidden{position:absolute;width:1px;height:1px;padding:0;margin:-1px;
                 overflow:hidden;clip:rect(0 0 0 0);white-space:nowrap;border:0}

/* ---- filters ------------------------------------------------------------- */
/* One row of controls that reads as a single instrument rather than five loose
   fields. Native <select> is kept — it is the best control on a phone and needs no
   JavaScript — but its chrome is replaced so all four concepts look deliberate. */

/* A bar of controls, not a bar the width of the page. `/properties` has five fields and
   fills a wide frame honestly; the plots have two, and stretched across 1,760px they
   became a pair of 830px dropdowns holding the words "Any size" — which looks like a
   mistake, because it is one. `--filter-max` bounds the bar to what its own contents
   deserve, and each page says how many fields it has rather than the sheet guessing. */
.filters{display:flex;gap:0;flex-wrap:wrap;align-items:stretch;margin-top:26px;
         max-width:var(--filter-max,100%);
         border:1px solid var(--border);border-radius:var(--radius);
         background:var(--gradient-panel);overflow:hidden;box-shadow:var(--shadow-1)}
/* Roughly 300px a field, which is where a select stops reading as an input and starts
   reading as an empty strip. One modifier, because one page needs one — a `--3` for a
   form nobody has written is a rule the next reader has to check is unused. */
.filters--2{--filter-max:620px}
/* Plot size, title and the Filters button — /build since it gained the drawer. */
.filters--3{--filter-max:780px}
.filters .f{display:flex;flex-direction:column;gap:3px;justify-content:center;
            padding:12px 17px;flex:1 1 150px;min-width:0;
            border-right:1px solid var(--border);transition:background .2s ease}
.filters .f:focus-within{background:var(--brand-soft)}
.filters .f > span{font-size:9px;letter-spacing:.15em;text-transform:uppercase;
                   color:var(--on-surface-subtle);font-weight:700}

.filters select{appearance:none;-webkit-appearance:none;font-family:inherit;
      font-size:14px;font-weight:500;padding:2px 22px 2px 0;width:100%;
      border:0;background:transparent;color:var(--on-surface);cursor:pointer;
      text-overflow:ellipsis;
      /* The arrow is drawn, not inherited, so every browser and concept matches. */
      background-image:linear-gradient(45deg,transparent 50%,currentColor 50%),
                       linear-gradient(135deg,currentColor 50%,transparent 50%);
      background-position:calc(100% - 9px) 60%, calc(100% - 4px) 60%;
      background-size:5px 5px,5px 5px;background-repeat:no-repeat}
.filters select:focus{outline:0}
.filters select option{background:var(--surface);color:var(--on-surface)}

.filters .go{display:flex;align-items:center;padding:8px;flex:0 0 auto;border-right:0}
/* The submit is the fallback for a visitor without JavaScript; with it, changing a
   control applies immediately and this is hidden. */
.filters.is-live .go{display:none}

.chips{display:flex;gap:8px;flex-wrap:wrap;align-items:center;margin-top:16px}
.chip{display:inline-flex;align-items:center;gap:7px;font-size:12px;font-weight:500;
      padding:5px 8px 5px 13px;border-radius:var(--radius-pill);
      border:1px solid color-mix(in srgb,var(--brand) 30%,transparent);
      color:var(--on-brand-soft);background:var(--brand-soft)}
.chip .x{display:inline-flex;align-items:center;justify-content:center;
         width:17px;height:17px;border-radius:50%;font-size:13px;line-height:1;
         background:var(--gradient-brand-soft);color:var(--on-brand)}
.chip:hover .x{opacity:.82}
.chip-clear{font-size:12px;color:var(--on-surface-muted);border-bottom:1px solid var(--border);
            padding-bottom:1px}
.chip-clear:hover{color:var(--brand);border-bottom-color:var(--brand)}

.count{font-size:12px;color:var(--on-surface-subtle);margin-top:20px}
.notice{margin-top:22px;padding:13px 15px;border-radius:var(--radius-small);
        border:1px solid color-mix(in srgb,var(--status-attention) 40%,transparent);
        background:color-mix(in srgb,var(--status-attention) 7%,transparent);
        font-size:12px;color:var(--on-surface-muted)}

/* `minmax(0,…)`: a plain `fr` track will not shrink below its widest child, and the
   development page ran 42px past a phone (sweep, 14 Oct 2026). */
.detail{display:grid;grid-template-columns:minmax(0,1.6fr) minmax(0,1fr);gap:30px;margin-top:26px;align-items:start}
.facts{border:1px solid var(--border);border-radius:var(--radius);padding:22px;
       background:var(--gradient-panel);box-shadow:var(--shadow-1)}
.facts dl{display:grid;grid-template-columns:auto 1fr;gap:10px 16px;font-size:13px;margin-top:12px}
.facts dt{color:var(--on-surface-subtle)}
.facts dd{text-align:right;color:var(--on-surface)}
/* The listing page's headline price. Same reason as `.tile .price` above, and it matters
   more here: this is the largest number on the page and the one a buyer reads first. */
.facts .big{font-family:var(--font-body);font-size:28px;color:var(--brand);
            letter-spacing:-.018em;font-weight:600;font-variant-numeric:tabular-nums}
.crumb{font-size:12px;color:var(--on-surface-subtle);margin-bottom:8px}
.prose{font-size:14px;color:var(--on-surface-muted);margin-top:14px;max-width:62ch}

/* ---- hero carousel ------------------------------------------------------- */
/* Slides are stacked and cross-faded. The first is visible with CSS alone, so the
   hero is complete before carousel.js arrives — and complete without it entirely. */

.hero-full{position:relative;min-height:min(78vh,660px);overflow:clip;
           background:var(--surface-muted)}
.slide{position:absolute;inset:0;opacity:0;transition:opacity .9s ease;
       display:flex;align-items:flex-end;pointer-events:none}
.slide.is-active{opacity:1;pointer-events:auto}
/* Without JS only the first slide is ever painted, and it must be visible. */
.hero-full:not(.is-enhanced) .slide:first-child{opacity:1;pointer-events:auto}
.slide picture,.slide>img{position:absolute;inset:0;width:100%;height:100%}
.slide img{width:100%;height:100%;object-fit:cover;display:block}

/* A shorter hero for the two browse pages. Somebody who reached /properties or /build
   came to search, so the filters have to be on screen without a scroll on a laptop —
   a full-height hero above a search form is a hero standing in the way. */
.hero-inner{min-height:min(56vh,470px)}

/* ---- a stage with no photograph ------------------------------------------ */
/* The build carousel names the four phases of a build and the company has no
   construction photography yet. This is what a slide shows until it does: the brand
   gradient with a drafting-paper rule over it, the stage named, and a line saying the
   photograph is missing. It is NOT a broken-image placeholder — it is the same answer
   the plot cards give ("Photography to follow"), for the same reason: a picture of a
   finished house behind the word "Foundation" is a picture of the wrong thing. */
/* Held to the RIGHT half, because the left half is the headline's. Centred, the stage
   word ran under the lede at 1440px and would collide outright on a 13" laptop — the
   plate stands in for a photograph, and a photograph does not get to sit on the type. */
.slide-plate{position:absolute;inset:0;display:flex;flex-direction:column;
  align-items:flex-end;justify-content:center;gap:4px;text-align:right;
  padding:0 clamp(24px,6vw,90px) 40px;
  background:
    repeating-linear-gradient(90deg,
      color-mix(in srgb,var(--on-hero) 5%,transparent) 0 1px,
      transparent 1px 64px),
    repeating-linear-gradient(0deg,
      color-mix(in srgb,var(--on-hero) 5%,transparent) 0 1px,
      transparent 1px 64px),
    var(--gradient-brand)}
.slide-plate .plate-mark{font-family:var(--font-display);
  font-size:clamp(38px,6vw,80px);line-height:1;letter-spacing:-.02em;
  color:color-mix(in srgb,var(--on-hero) 26%,transparent)}
.slide-plate .plate-note{font-size:10px;letter-spacing:.2em;text-transform:uppercase;
  font-weight:700;color:color-mix(in srgb,var(--on-hero) 44%,transparent)}
/* The scrim exists to guarantee contrast over an unknown photograph. Over the plate
   there is no photograph and its side vignette only muddies the plate, so the vertical
   wash alone carries the text — the headline still needs a floor to sit on. */
.slide.no-photo .scrim{background:var(--gradient-hero)}

/* A scrim guarantees contrast whatever photograph sits behind it, so swapping an
   image can never silently make the headline unreadable. Two layers now: a side
   vignette under the text column, over a vertical wash. Both are tinted with the
   concept's deepest gradient stop rather than neutral black, so the photograph
   belongs to the brand instead of sitting next to it. */
.slide .scrim{position:absolute;inset:0;
  background:var(--gradient-hero-side),var(--gradient-hero)}
/* A photograph that is mostly white — an interior of a finished house — gives the text
   column nothing to sit on, and the eyebrow is the first thing to disappear. Slides
   carrying one ask for the heavier vignette by declaring `scrim: 'strong'`; it is the
   same gradient at a second weight, so the hero still belongs to the concept. */
.slide .scrim.scrim-strong{
  background:var(--gradient-hero-side-strong),var(--gradient-hero)}
.slide .inner{position:relative;padding-bottom:66px;width:100%}
/* `.slide-title` is the same headline as an h2 on an inner page, where the page carries
   its own h1 (14 Oct 2026) — the h1's type, set here because the element is not one. */
.slide .slide-title{font-family:var(--font-display);font-size:clamp(28px,5.4vw,46px);
  line-height:1.06;letter-spacing:-.025em}
.slide h1,.slide .slide-title{color:var(--on-hero);max-width:15ch;
  text-shadow:0 2px 30px color-mix(in srgb,var(--gradient-from) 55%,transparent)}
.slide .lede{color:color-mix(in srgb,var(--on-hero) 90%,transparent);
  max-width:46ch;margin-top:13px;font-size:15px}
.slide .eyebrow{color:var(--on-hero);
  background:color-mix(in srgb,var(--on-hero) 14%,transparent);
  border:1px solid color-mix(in srgb,var(--on-hero) 26%,transparent);
  backdrop-filter:blur(8px)}
.slide .eyebrow::before{background:var(--on-hero)}
.slide .cta{display:flex;gap:12px;margin-top:26px;flex-wrap:wrap}
/* GHOST — and the default is the SURFACE one, not the hero one.

   It was the other way round until 29 August 2026, and the cost was the three buttons
   that matter most on a property page. In LIGHT mode `--on-hero` and the raised card
   are both off-whites a shade apart — a contrast ratio of **1.01** — so "call",
   "WhatsApp" and "share" were rendering, taking up space, and completely invisible to
   every visitor. (The figures are not written here as literals: `portal.test.js`
   refuses a hex in this file, comments included, and it is right to.)
   Reported from Firefox, which is not the cause — the earlier screenshots were all in
   dark mode, where the same rule gives 13.27 and looks perfect.

   The workspace had already fixed it for itself (`workspace.css`), which is exactly how
   a bug like this survives: every screen its author looked at was fine.

   So the plain variant is now readable on a panel, and the translucent-over-photograph
   treatment is scoped to `.slide`, which is the hero and nothing else. A variant whose
   only legible home is one component should have to name that component. */
/* ~~`border-color` alone~~ until 6 September 2026, and `.btn` sets `border:0`, so the
   rule painted nothing: Deactivate and Suspend looked exactly like Save. A destructive
   act now reads as one — the surface's own ground, the attention ink, and a real border.
   Both themes clear 4.5:1 on the text (light 6.4:1, dark 6.0:1). */
button.btn.danger,a.btn.danger{background:var(--surface);color:var(--status-attention);
  border:1px solid var(--status-attention);box-shadow:none}
button.btn.danger:hover,a.btn.danger:hover{filter:none;
  background:color-mix(in srgb, var(--status-attention) 8%, var(--surface))}

a.btn.ghost, button.btn.ghost, .btn.ghost{
  background:var(--surface);color:var(--on-surface);
  border:1px solid var(--border-strong);box-shadow:none}
.btn.ghost:hover{border-color:var(--brand);color:var(--brand);
  background:var(--brand-soft);box-shadow:none;filter:none}

.slide a.btn.ghost, .slide button.btn.ghost, .slide .btn.ghost{
  background:color-mix(in srgb,var(--on-hero) 12%,transparent);color:var(--on-hero);
  border:1px solid color-mix(in srgb,var(--on-hero) 42%,transparent);
  backdrop-filter:blur(8px);box-shadow:none}
.slide .btn.ghost:hover{background:color-mix(in srgb,var(--on-hero) 22%,transparent);
  color:var(--on-hero);box-shadow:none;filter:none}

.carousel-ui{position:absolute;left:0;right:0;bottom:22px;z-index:3}
.carousel-ui .bar{display:flex;align-items:center;gap:14px}
.dots{display:flex;gap:8px;margin-right:auto}
.dot{width:30px;height:3px;border:0;padding:0;cursor:pointer;border-radius:2px;
     background:color-mix(in srgb,var(--on-hero) 34%,transparent);transition:background .3s}
.dot.is-active{background:var(--on-hero)}
.dot:focus-visible{outline:2px solid var(--on-hero);outline-offset:3px}
.arrow{width:38px;height:38px;border-radius:50%;
       border:1px solid color-mix(in srgb,var(--on-hero) 42%,transparent);
       background:color-mix(in srgb,var(--gradient-from) 42%,transparent);
       color:var(--on-hero);font-size:15px;cursor:pointer;
       display:flex;align-items:center;justify-content:center;backdrop-filter:blur(8px);
       transition:background .25s ease}
.arrow:hover{background:color-mix(in srgb,var(--gradient-from) 78%,transparent)}
.arrow:focus-visible{outline:2px solid var(--on-hero);outline-offset:2px}
/* No JS means no working controls, so they are not shown. */
.hero-full:not(.is-enhanced) .carousel-ui{display:none}

/* ---- the standing statement --------------------------------------------- */
/* Who Ecoterra is, held at the top right of the hero and unchanged as the slides
   move. It sits outside .slide deliberately: anything inside a slide inherits that
   slide's opacity transition and would fade in and out with it. */

.hero-note{position:absolute;top:26px;right:26px;z-index:4;max-width:320px;
           padding:22px 24px;border-radius:var(--radius);
           background:linear-gradient(160deg,
                      color-mix(in srgb,var(--gradient-from) 58%,transparent) 0%,
                      color-mix(in srgb,var(--gradient-from) 86%,transparent) 100%);
           backdrop-filter:blur(14px) saturate(1.25);
           border:1px solid color-mix(in srgb,var(--on-hero) 18%,transparent);
           box-shadow:var(--shadow-3)}
.hero-note .who{font-size:9px;letter-spacing:.2em;text-transform:uppercase;
                color:color-mix(in srgb,var(--on-hero) 72%,transparent);font-weight:700}
.hero-note p{color:var(--on-hero);font-size:13.5px;line-height:1.6;margin-top:9px}
.hero-note .est{display:flex;gap:14px;margin-top:15px;padding-top:13px;
                border-top:1px solid color-mix(in srgb,var(--on-hero) 20%,transparent);
                font-size:10px;letter-spacing:.12em;text-transform:uppercase;
                color:color-mix(in srgb,var(--on-hero) 80%,transparent);font-weight:700}

/* ---- imagery ------------------------------------------------------------- */

.shot.hero{aspect-ratio:16/10;border:1px solid var(--border);border-radius:var(--radius);
           overflow:hidden;display:flex;align-items:center;justify-content:center;
           background:var(--surface-muted);box-shadow:var(--shadow-2)}
.shot.hero img{width:100%;height:100%;object-fit:cover;display:block}

/* ---- proof band ---------------------------------------------------------- */
/* Numbers a buyer can check beat adjectives, and this is where the brand finally
   occupies the page rather than decorating it: a full-bleed gradient immediately
   under the hero, with a one-pixel sheen along the top edge. */

.stats{display:grid;grid-template-columns:repeat(4,1fr);gap:0;position:relative;
       overflow:hidden;background:var(--gradient-brand);color:var(--on-gradient)}
.stats::before{content:"";position:absolute;inset:0;pointer-events:none;
       background:var(--gradient-sheen)}
.stats div{background:transparent;padding:30px 20px;text-align:center;position:relative;
       border-left:1px solid color-mix(in srgb,var(--on-gradient) 15%,transparent)}
.stats div:first-child{border-left:0}
.stats .n{font-family:var(--font-display);font-size:30px;color:var(--on-gradient);
          display:block;letter-spacing:-.02em}
.stats .k{font-size:10px;letter-spacing:.14em;text-transform:uppercase;
          color:color-mix(in srgb,var(--on-gradient) 76%,transparent);
          margin-top:7px;display:block}

.section-head{display:flex;align-items:baseline;justify-content:space-between;
              gap:16px;margin-top:66px;padding-bottom:15px;position:relative;
              flex-wrap:wrap}
.section-head::after{content:"";position:absolute;left:0;right:0;bottom:0;height:1px;
              background:var(--gradient-rule)}
.section-head p{font-size:13px;color:var(--on-surface-muted);max-width:52ch;margin-top:5px}
.section-head > a{font-size:13px;font-weight:600;white-space:nowrap}

/* ---- theme switcher ------------------------------------------------------ */
/* A review affordance, not a product feature — absent entirely in production, and
   floating rather than in the footer so comparing concepts never means scrolling
   past the whole page first. */

.theme-switch{position:fixed;right:18px;bottom:18px;z-index:60;
              display:flex;align-items:center;gap:7px;flex-wrap:wrap;max-width:min(92vw,560px);
              padding:11px 14px;border-radius:var(--radius-pill);
              border:1px solid var(--border);background:var(--surface-panel);
              box-shadow:var(--shadow-3)}
.theme-switch-label{font-size:9px;letter-spacing:.16em;text-transform:uppercase;
                    color:var(--on-surface-subtle);font-weight:700}
.theme-switch-divider{width:1px;height:18px;background:var(--border);margin:0 3px}
.theme-switch-note{font-size:9px;letter-spacing:.08em;text-transform:uppercase;
                   color:var(--on-surface-subtle);opacity:.72;width:100%;text-align:center}
.theme-chip{font-size:11px;padding:5px 11px;border-radius:var(--radius-pill);
            border:1px solid var(--border);color:var(--on-surface-muted);
            white-space:nowrap;transition:border-color .2s,color .2s,background .2s}
.theme-chip:hover{border-color:var(--brand);color:var(--brand)}
.theme-chip.is-current{border-color:transparent;color:var(--on-brand);
            background:var(--gradient-brand-soft);box-shadow:var(--shadow-brand)}
.theme-chip:focus-visible{outline:2px solid var(--brand);outline-offset:2px}

/* ---- feature cards ------------------------------------------------------- */
/* Big image, centred title, underlined caption. The pattern every established
   builder site uses for its top-level destinations, and it works because the
   photograph carries the message and the words only label it. */

.features{display:grid;grid-template-columns:repeat(2,1fr);gap:38px 28px;margin-top:38px}
/* Entry points: four across on a desktop, two on a tablet, one on a phone. Was an
   inline repeat(4,1fr), which stayed four columns at 390px wide. */
.entry-grid{display:grid;grid-template-columns:repeat(4,1fr);gap:16px;margin-top:24px}
.feature{display:block;text-align:center;color:inherit}
.feature .shot{position:relative;aspect-ratio:3/2;overflow:hidden;border-radius:var(--radius);
               background:var(--surface-muted);box-shadow:var(--shadow-1);
               display:flex;align-items:center;justify-content:center;
               font-size:10px;letter-spacing:.16em;text-transform:uppercase;
               color:var(--on-surface-subtle);transition:box-shadow .35s ease}
/* The brand wash only appears on approach, so the photograph is never permanently
   tinted — a green cast over a house is a filter, not a brand. */
.feature .shot::after{content:"";position:absolute;inset:0;pointer-events:none;
               background:var(--gradient-hero);opacity:0;transition:opacity .4s ease}
.feature:hover .shot{box-shadow:var(--shadow-3)}
.feature:hover .shot::after{opacity:.5}
.feature .shot img{width:100%;height:100%;object-fit:cover;display:block;
                   transition:transform .6s ease}
.feature:hover .shot img{transform:scale(1.04)}
.feature h3{font-family:var(--font-display);font-size:19px;font-weight:600;
            letter-spacing:.10em;text-transform:uppercase;margin-top:19px;
            color:var(--on-surface)}
.feature .under{display:inline-block;margin-top:10px;font-size:11px;
                letter-spacing:.09em;text-transform:uppercase;
                color:var(--on-surface-muted);
                border-bottom:1px solid var(--border);padding-bottom:3px;
                transition:color .25s ease,border-color .25s ease}
.feature:hover .under{color:var(--brand);border-bottom-color:var(--brand)}

/* ---- the approach page --------------------------------------------------- */

/* The lead of a page that opens on an argument rather than a photograph. It no longer
   sets its own vertical padding: it is the first thing in a .band now, and the band
   decides how much air the top of a page gets. */
.approach-hero{max-width:62ch}
.steps{display:grid;grid-template-columns:repeat(2,1fr);gap:20px;margin-top:34px}
.step{border:1px solid var(--border);border-radius:var(--radius);padding:26px;
      background:var(--gradient-panel);box-shadow:var(--shadow-1);
      display:flex;flex-direction:column;gap:10px;
      transition:box-shadow .3s ease,border-color .3s ease}
.step:hover{box-shadow:var(--shadow-2);border-color:var(--border-strong)}
.step .num{display:inline-flex;align-items:center;justify-content:center;
           align-self:flex-start;min-width:34px;height:26px;padding:0 10px;
           border-radius:var(--radius-pill);background:var(--brand-soft);
           color:var(--on-brand-soft);font-family:var(--font-display);
           font-size:12px;font-weight:600;letter-spacing:.1em}
.step h3{font-family:var(--font-display);font-size:17px;font-weight:600}
.step p{font-size:13px;color:var(--on-surface-muted);line-height:1.62}
.step .who{margin-top:auto;padding-top:14px;font-size:10px;letter-spacing:.13em;
           text-transform:uppercase;color:var(--on-surface-subtle)}

/* The single sentence a nervous buyer is looking for. It used to be a hairline and a
   serif; it is now a panel, because on this page it is the argument. */
.pull{position:relative;margin:36px 0;padding:26px 30px 26px 34px;max-width:62ch;
      overflow:hidden;border:1px solid var(--border);border-radius:var(--radius);
      background:var(--gradient-tint-strong);box-shadow:var(--shadow-1);
      font-family:var(--font-display);font-size:20px;line-height:1.5;
      color:var(--on-surface)}
.pull::before{content:"";position:absolute;left:0;top:0;bottom:0;width:4px;
      background:linear-gradient(180deg,var(--brand) 0%,var(--brand-muted) 100%)}
/* Question left, answer right. Stacked, the answers ran to 62ch and left half the
   page empty beside six of them — which on the page that has to do the persuading
   read as a site with nothing more to say. */
.qa{margin-top:34px;display:grid;grid-template-columns:minmax(0,.85fr) minmax(0,1.15fr);
    gap:0 44px;align-items:baseline}
.qa dt{font-family:var(--font-display);font-size:15.5px;font-weight:600;
       padding:20px 0 20px;border-top:1px solid var(--border)}
.qa dd{font-size:13.5px;color:var(--on-surface-muted);
       line-height:1.65;padding:20px 0;border-top:1px solid var(--border)}

/* ---- the service ladder -------------------------------------------------- */
/* An ordered list, kept as <ol> because the order is the meaning: land comes before
   title, title before design. A grid of cards would lose that. */

.ladder{list-style:none;margin-top:26px;border-top:1px solid var(--border)}
.ladder li{display:flex;gap:20px;align-items:flex-start;padding:20px 14px;
           border-bottom:1px solid var(--border);border-radius:var(--radius-small);
           transition:background .25s ease}
.ladder li:hover{background:var(--tint)}
.ladder .rung{display:inline-flex;align-items:center;justify-content:center;
              flex:0 0 auto;width:36px;height:36px;border-radius:50%;
              background:var(--brand-soft);color:var(--on-brand-soft);
              font-family:var(--font-display);font-size:12px;font-weight:600;
              letter-spacing:.06em;transition:background .25s ease,color .25s ease}
.ladder li:hover .rung{background:var(--gradient-brand-soft);color:var(--on-brand)}
.ladder h3{font-family:var(--font-display);font-size:17px;font-weight:600}
.ladder p{font-size:13px;color:var(--on-surface-muted);margin-top:4px;
          line-height:1.62;max-width:64ch}

/* ---- newsletter ---------------------------------------------------------- */
/* The second place the brand fills the page. Same gradient as the proof band, so the
   page opens and closes on the same colour. */

.subscribe{position:relative;overflow:hidden;background:var(--gradient-brand);
           color:var(--on-gradient);border-radius:var(--radius);
           padding:40px;margin-top:70px;display:grid;
           grid-template-columns:1.1fr 1fr;gap:30px;align-items:center;
           box-shadow:var(--shadow-3)}
.subscribe::before{content:"";position:absolute;inset:0;pointer-events:none;
           background:var(--gradient-glow-soft)}
.subscribe > div{position:relative}
.subscribe h2{color:var(--on-gradient);font-size:clamp(22px,2.7vw,28px);
              letter-spacing:-.018em}
.subscribe p{font-size:13.5px;color:color-mix(in srgb,var(--on-gradient) 86%,transparent);
             margin-top:8px;max-width:44ch}
.subscribe form{display:flex;gap:0;background:var(--surface);
                border-radius:var(--radius-small);overflow:hidden;
                box-shadow:var(--shadow-2)}
.subscribe input{flex:1 1 auto;min-width:0;border:0;background:transparent;
                 font-family:inherit;font-size:14px;padding:14px 16px;color:var(--on-surface)}
.subscribe input:focus{outline:2px solid var(--accent);outline-offset:-2px}
.subscribe button{flex:0 0 auto;border:0;cursor:pointer;font-family:inherit;
                  font-size:12px;font-weight:700;letter-spacing:.1em;text-transform:uppercase;
                  padding:14px 22px;background:var(--accent);color:var(--on-accent);
                  transition:filter .25s ease}
.subscribe button:hover{filter:brightness(1.08)}
.subscribe .small{font-size:11px;
                  color:color-mix(in srgb,var(--on-gradient) 76%,transparent);margin-top:10px}
.subscribe .said{font-size:14px;font-weight:600}

/* ---- auth and account ---------------------------------------------------- */

.auth{max-width:430px;margin:0 auto;padding:56px 0 90px}
.auth h1{font-size:29px;margin-bottom:6px}
.auth .lede{margin-bottom:22px}
.field{display:flex;flex-direction:column;gap:6px;margin-bottom:14px}
.field label{font-size:10px;letter-spacing:.14em;text-transform:uppercase;
             color:var(--on-surface-subtle);font-weight:700}
.field input{font-family:inherit;font-size:15px;padding:12px 13px;
      border:1px solid var(--border-strong);border-radius:var(--radius-small);
      background:var(--surface);color:var(--on-surface);
      transition:border-color .2s ease,box-shadow .2s ease}
.field input:focus{outline:2px solid var(--brand);outline-offset:1px;
      border-color:var(--brand);box-shadow:var(--ring)}
.field .hint{font-size:11px;color:var(--on-surface-subtle)}
.row2{display:grid;grid-template-columns:1fr 1fr;gap:12px}
.alt{margin-top:18px;font-size:13px;color:var(--on-surface-muted);text-align:center}
.err{border:1px solid color-mix(in srgb,var(--status-attention) 55%,transparent);
     background:color-mix(in srgb,var(--status-attention) 8%,transparent);
     border-radius:var(--radius-small);
     padding:12px 14px;font-size:13px;margin-bottom:16px;color:var(--on-surface)}
.check{display:flex;gap:9px;align-items:flex-start;font-size:12px;
       color:var(--on-surface-muted);margin-bottom:16px}
.check input{margin-top:2px}
.trust{margin-top:26px;padding-top:18px;border-top:1px solid var(--border);
       font-size:11px;color:var(--on-surface-subtle);line-height:1.65}

.acct{display:grid;grid-template-columns:1fr 2fr;gap:26px;margin-top:26px;align-items:start}
.panel{border:1px solid var(--border);border-radius:var(--radius);padding:22px;
       background:var(--gradient-panel);box-shadow:var(--shadow-1)}
.panel h2{font-size:15px;margin-bottom:12px}
.panel dl{display:grid;grid-template-columns:auto 1fr;gap:8px 14px;font-size:13px}
.panel dt{color:var(--on-surface-subtle)}
.panel dd{text-align:right}
.empty{border:1px dashed var(--border-strong);border-radius:var(--radius);padding:30px 22px;
       text-align:center;font-size:13px;color:var(--on-surface-muted);
       background:var(--gradient-tint)}

/* Show/hide on a password field. The button is added by assets/password.js, so these
   rules only ever apply where JavaScript has actually built one — there is no state in
   which an unstyled control appears. The wrapper is display:block because it replaces
   the input in the layout and the input was a block.

   Above the breakpoints, like everything else here: a base rule below the responsive
   section loses the cascade to nothing, and there is a test that catches it. */
.pw{position:relative;display:block}
.pw>input{width:100%;padding-right:44px}
.pw-toggle{position:absolute;top:0;right:0;height:100%;width:40px;
  display:flex;align-items:center;justify-content:center;
  background:none;border:0;padding:0;cursor:pointer;color:var(--on-surface-subtle)}
.pw-toggle:hover{color:var(--on-surface)}
/* Focus is visible even though the button is out of the tab order: it is still
   reachable, and a control you can reach and cannot see focused is worse than one you
   cannot reach at all. */
.pw-toggle:focus-visible{outline:2px solid var(--brand);outline-offset:-2px;border-radius:var(--radius-small)}

/* The password requirements list. Rendered server-side by partials/passwordrules.ejs;
   `.live` is added by assets/passwordrules.js, so the ticks only appear where something
   is actually watching the field. Without the script the list is a plain set of rules,
   which is what somebody with no JavaScript needs to read. */
.pwrules{list-style:none;margin:10px 0 0;padding:0;display:grid;gap:4px;
  font-size:var(--size-body-small);color:var(--on-surface-muted)}
.pwrules li{display:flex;align-items:center;gap:8px;line-height:1.45}
.pwrules .mark{flex:0 0 auto;width:14px;height:14px;border-radius:50%;
  border:1.5px solid var(--border-strong);position:relative}
/* No dot at all until the script is running: an empty circle beside every rule reads as
   "you have failed all of these" to somebody who has typed nothing yet. */
.pwrules:not(.live) .mark{display:none}
.pwrules li.met{color:var(--status-complete)}
.pwrules li.met .mark{border-color:var(--status-complete);background:var(--status-complete)}
.pwrules li.met .mark::after{content:"";position:absolute;left:4px;top:1px;
  width:3px;height:7px;border:solid var(--surface);border-width:0 1.5px 1.5px 0;
  transform:rotate(42deg)}

/* The way in to the workspace, on a customer's account page. Staff sign in through the
   same front door as everybody else and land here; without this there is no visible
   route to /manage at all. Set apart from the profile above it because it is about a
   different relationship with the platform, not another detail of this one. */
.staff-door{margin-top:22px;padding:16px;border:1px solid var(--border-strong);
  border-radius:var(--radius-small);background:var(--surface-raised)}
.staff-door h2{margin:0 0 6px}
.staff-door p{margin:0 0 12px;font-size:var(--size-body-small);color:var(--on-surface-muted)}

/* ---- public capture forms ------------------------------------------------ */
/* Contact and estimate. Two columns: the form, and the things a visitor wants while
   deciding whether to fill it in — the phone number, what happens next, and where the
   money is held. Inserted here rather than appended after the breakpoints, because a
   base rule below the responsive section loses the cascade to nothing. */

.form-page{display:grid;grid-template-columns:minmax(0,1.4fr) minmax(0,.7fr);
           gap:44px;padding-top:56px;padding-bottom:90px;align-items:start}
.form-page h1{margin:12px 0 0}
.form-page .lede{margin-top:12px;max-width:56ch}
.capture{margin-top:30px;max-width:56ch}
.capture .field{margin-bottom:16px}

/* Textarea and select match the inputs already defined for .field, rather than
   inheriting the browser's, which would put three different controls in one form. */
.field textarea,.field select{font-family:inherit;font-size:15px;padding:12px 13px;
      border:1px solid var(--border-strong);border-radius:var(--radius-small);
      background:var(--surface);color:var(--on-surface)}
.field textarea{resize:vertical;line-height:1.6}
.field textarea:focus,.field select:focus{outline:2px solid var(--brand);
      outline-offset:1px;border-color:var(--brand);box-shadow:var(--ring)}
.opt{text-transform:none;letter-spacing:0;font-weight:400;
     color:var(--on-surface-subtle);font-size:10px}
.field .hint.bad{color:var(--status-attention)}
.field.invalid input,.field.invalid select,.field.invalid textarea{border-color:var(--status-attention)}
.field .hint a:not(.btn){color:var(--brand)}

/* The error summary. It is focused on arrival, so it must look focused. */
.err ul{margin:7px 0 0;padding-left:18px}
.err li{margin-top:3px}
.err a:not(.btn){color:var(--status-attention)}
.err:focus-visible{outline:2px solid var(--status-attention);outline-offset:2px}

/* The answer to a submission. `.warn` is the honest-degradation case: the request did
   not land, and the border has to say so before the words are read. */
.receipt{position:relative;overflow:hidden;border:1px solid var(--border);
         border-radius:var(--radius);padding:22px 24px 22px 28px;margin:24px 0;
         background:var(--gradient-tint-strong);box-shadow:var(--shadow-1)}
.receipt::before{content:"";position:absolute;left:0;top:0;bottom:0;width:5px;
         background:linear-gradient(180deg,var(--brand) 0%,var(--brand-muted) 100%)}
.receipt.warn::before{background:var(--status-attention)}
.receipt h2{font-size:18px}
.receipt p{font-size:13.5px;color:var(--on-surface-muted);margin-top:9px;line-height:1.65}
.receipt a:not(.btn){color:var(--brand)}
.receipt:focus-visible{outline:2px solid var(--brand);outline-offset:3px}
.small{font-size:12px;color:var(--on-surface-subtle);line-height:1.6}

.form-aside{display:flex;flex-direction:column;gap:16px}
.form-aside .panel p{margin-top:6px}
.form-aside .panel a:not(.btn){color:var(--brand)}
.big-link{font-size:20px;font-weight:700;margin-top:6px;letter-spacing:-.015em}
.plain{list-style:none;margin:0;padding:0;font-size:12.5px;
       color:var(--on-surface-muted);line-height:1.6}
.plain li{padding:8px 0;border-bottom:1px solid var(--border)}
.plain li:last-child{border-bottom:0}
.plain.compact li{padding:4px 0;border-bottom:0}

/* The service ladder wording is proposed, not confirmed — handoff §6. Marked rather
   than presented as settled, and the flag comes from the catalogue so confirming it
   is a data change. */
.proposed{font-size:10px;letter-spacing:.1em;text-transform:uppercase;font-weight:700;
          border:1px solid var(--border);border-radius:var(--radius-pill);
          padding:2px 8px;color:var(--on-surface-subtle)}
.proposed-note{margin-top:14px;font-style:italic}

/* The honeypot named in the support contract. Off-screen rather than display:none,
   because a bot that reads the stylesheet skips anything hidden outright — and its
   own aria-hidden and tabindex="-1" keep it away from anyone using the page. */
.hp{position:absolute;left:-9999px;width:1px;height:1px;overflow:hidden}

/* Calling a viewing off, on its own journey step. A row rather than a stacked field:
   it is a small aside to a step, not a form in its own right, and stacking it made the
   step read as though the whole page were about cancelling. */
.step-cancel{display:flex;flex-wrap:wrap;align-items:center;gap:8px;margin-top:9px}
.step-cancel input[type=text]{flex:1 1 210px;min-width:0;font-family:inherit;
  font-size:13px;padding:8px 11px;color:var(--on-surface);background:var(--surface);
  border:1px solid var(--border-strong);border-radius:var(--radius-small)}
.step-cancel input[type=text]:focus{outline:2px solid var(--brand);outline-offset:1px;
  border-color:var(--brand)}
.step-cancel .btn{flex:0 0 auto}

/* "I would also like to see it" — the viewing half of the ask drawer.
   A tinted block rather than three loose controls: it is a second request inside one
   form, and the boundary is what stops somebody reading the date picker as part of
   the question above it. */
.ask-viewing{gap:0;padding:14px 15px;margin-bottom:14px;border-radius:var(--radius);
  background:var(--tint);border:1px solid var(--border)}
.ask-viewing>.checkline{margin:0}
.ask-viewing-detail{margin-top:12px;padding-top:12px;border-top:1px solid var(--border)}
.ask-viewing-detail>.hint{display:block;margin-bottom:11px}
.ask-viewing-detail label[for]{display:block;font-size:10px;letter-spacing:.14em;
  text-transform:uppercase;color:var(--on-surface-muted);margin-bottom:6px}
.ask-viewing-detail input[type=datetime-local]{font-family:inherit;font-size:15px;
  padding:11px 12px;width:100%;box-sizing:border-box;color:var(--on-surface);
  background:var(--surface);border:1px solid var(--border-strong);
  border-radius:var(--radius-sm)}
.ask-viewing-detail input[type=datetime-local]:focus{outline:2px solid var(--brand);
  outline-offset:1px;border-color:var(--brand)}
/* The label wraps the control, so the whole line is a hit target — on a phone a bare
   radio is a four-millimetre square and this form is mostly read on one. */
/* A radio or tick-box line, in a drawer.

   ~~`.choice` / `.choices`~~ until 29 August 2026, which was a NAME COLLISION and not a
   styling problem: `portal.css` already owns both, for the trustee catalogue's cards,
   and it loads after this file. So the same markup rendered as a tight list on the
   public listing page — base.css alone — and as full-width bordered cards with the radio
   stranded on its own line the moment it appeared on an account surface. Renamed rather
   than fought with specificity: two components, two names.

   The label WRAPS its control, so the whole line is a hit target. On a phone a bare
   radio is a four-millimetre square, and this form is mostly read on one. */
.checkline{display:flex;align-items:flex-start;gap:9px;font-size:14px;line-height:1.45;
  color:var(--on-surface);cursor:pointer;padding:3px 0}
.checkline input{flex:0 0 auto;margin-top:2px;accent-color:var(--brand);
  width:16px;height:16px}
.radioset{border:0;margin:0 0 15px;padding:0;display:flex;flex-direction:column;gap:2px}
.radioset legend{font-size:10px;letter-spacing:.14em;text-transform:uppercase;
  color:var(--on-surface-muted);padding:0;margin-bottom:6px}
.radioset label{display:flex;align-items:flex-start;gap:9px;font-size:14px;
  line-height:1.45;color:var(--on-surface);cursor:pointer;padding:4px 0}
.radioset label input{flex:0 0 auto;margin-top:2px;accent-color:var(--brand);
  width:16px;height:16px}

.foot-top{display:flex;align-items:center;justify-content:space-between;
          gap:20px;flex-wrap:wrap}
.foot-links{display:flex;flex-wrap:wrap;gap:16px;margin-top:10px}
.foot-links a:not(.btn){color:var(--on-surface-muted)}

/* Enrolling a second factor: the QR beside the key it encodes. Side by side where there
   is room, stacked where there is not — on a phone the key matters more, because the
   phone is the thing that would have done the scanning. */
.enrol{display:grid;grid-template-columns:auto minmax(0,1fr);gap:24px;align-items:start;margin-top:14px}
.enrol-qr{margin:0;display:flex;flex-direction:column;align-items:center;gap:8px}
.enrol-qr svg{display:block;border-radius:var(--radius-small);
  border:1px solid var(--border);padding:10px;background:#fff}
.enrol-qr figcaption{font-size:var(--size-body-small);color:var(--on-surface-muted);
  max-width:190px;text-align:center}
.enrol-key{margin:0}
/* (its phone layout is with the other breakpoints, at the end of this sheet) */
/* The six-digit box is six digits wide, not the page: it stretched to 1,270px on the
   provider surface, under a label sitting on its top edge. */
/* `.details` in front so portal.css's later `.section-heading` margin does not win. */
.details h3.enrol-prove{margin-top:30px}
form.enrol-prove{margin-top:10px}
.enrol-prove .act-fields label{display:flex;flex-direction:column;gap:8px}
.enrol-prove input[name=code]{max-width:220px;font-size:20px;letter-spacing:.32em;
  font-variant-numeric:tabular-nums;text-align:center}

/* ---- appearance ----------------------------------------------------------- */
/* A segmented control, not a two-state flip. Three options, because the default is
   "follow the device" and a two-way switch cannot say that — once tapped, the visitor
   can never get back to it.

   It is quiet on purpose. This is a preference someone sets once, sitting in a footer
   under a page selling a ₦240M house; the moment it competes with the property for
   attention it is wrong. So: no fill until it is the chosen one, and the chosen one is
   a soft brand tint rather than a saturated block. */
.appearance{display:flex;align-items:center;gap:10px}
.appearance-label{font-size:10px;letter-spacing:var(--micro-tracking);
                  text-transform:uppercase;color:var(--on-surface-subtle)}
/* The track. One border around the set rather than three, so it reads as one control
   with a state instead of three links that happen to be adjacent. */
.appearance-set{display:inline-flex;align-items:center;gap:2px;padding:3px;
                border:1px solid var(--border);border-radius:var(--radius-pill);
                background:var(--gradient-panel)}
.foot .appearance-opt,
.appearance-opt{display:inline-flex;align-items:center;gap:6px;
                padding:5px 11px;border-radius:var(--radius-pill);
                font-size:11.5px;font-weight:600;line-height:1;
                color:var(--on-surface-subtle);white-space:nowrap;
                transition:color .18s ease,background-color .18s ease}
.appearance-icon{display:inline-flex;align-items:center;justify-content:center}
/* The hover state is in the media-query section at the end, behind (hover:hover): on a
   phone :hover sticks after a tap and leaves the control looking like two are chosen. */
.foot .appearance-opt.is-current,
.appearance-opt.is-current{color:var(--on-brand);background:var(--gradient-brand-soft);
                           box-shadow:var(--shadow-brand)}
.appearance-opt:focus-visible{outline:2px solid var(--brand);outline-offset:2px}
/* Underlining a segmented control makes it look broken; the fill already says which
   one is chosen. `.foot a` is (0,1,1) so this needs to match it. */
.foot .appearance-opt:hover{text-decoration:none}

/* ---- the sign-in link exchange -------------------------------------------- */
/* `.provider-note` is a standing statement about how the platform is configured, not
   the outcome of a submission — today it says that the email provider is a mock and
   that nothing was actually sent. Dashed rather than solid so it reads as neither a
   success nor a failure, because it is neither. Not `.notice`, which already exists
   above for a degraded listing grid. Inserted above the breakpoints, since a base rule
   placed after them loses the cascade to nothing. */

.provider-note{border:1px dashed var(--border-strong);border-radius:var(--radius-small);
        padding:15px 17px;margin:20px 0;font-size:12.5px;line-height:1.7;
        color:var(--on-surface-muted);background:var(--gradient-tint)}
.provider-note p+p{margin-top:9px}
.provider-note strong{color:var(--on-surface)}
.provider-note code{font-family:ui-monospace,SFMono-Regular,Menlo,monospace;
        font-size:11.5px;word-break:break-word}
.auth .receipt{margin:0 0 18px}
.auth .receipt h2{font-size:17px;line-height:1.4}
/* The confirm step is a lone button under a paragraph, with no field to space it. */
.auth form.confirm{margin-top:20px}

/* ---- page sections --------------------------------------------------------- */
/* Why the body of the page looked flat: the gradient was spent on the header, the
   cards and two brand bands, and the ground between them was left near-neutral so it
   could never cast a photograph or sit behind a paragraph.

   Tinting alternate sections was tried and rejected. It turned a long page into a
   stack of slabs, and in the concepts whose tint is bone rather than green it striped
   the page beige. The colour now comes from one continuous thing instead: the page
   wash on <body>, three soft blooms drifting down the whole document. A card is one
   gradient across the whole of itself, not a set of stripes, and the page is the same
   idea at page scale — which is why nothing here paints a ground of its own.

   These sections stay full-bleed even though none of them is painted. The .wrap inside
   keeps the same `--page-max` column as everything else, and the section spanning the
   viewport is what lets the page wash run edge to edge behind it. That is why they had
   to leave `main.wrap`: a background on a capped element is a rectangle with two
   visible vertical edges, and the usual escape, a 100vw child, is how a page starts
   scrolling sideways the moment a scrollbar takes 15px of it. Nothing here is ever
   wider than its parent.

   ~~1140px~~ named three times in this paragraph, until 30 August 2026. `--page-max` has
   been 1760px since the token was widened — see its own comment for why — so the number
   here had been wrong for as long as it had been readable, and it is the number a reader
   would have taken away. Named by the token now, so widening it again cannot make this
   paragraph lie a second time. */
.band{padding:56px 0}
/* Two consecutive bands put 112px between a heading and what it heads. On /properties
   and /land the count ("12 properties") sits at the bottom of one band and the grid it
   counts at the top of the next, so the number and its cards are most of a phone screen
   apart and the page reads as though the results failed to load. A band that directly
   follows another closes the gap between them; the rhythm between UNRELATED sections is
   untouched. Found by a design review, 17 August 2026. */
.band + .band{padding-top:0}
/* The section owns the space above its first element, so a section head or a card grid
   arriving at the top of one does not add its own on top. Those margins are kept for
   the case they were written for: following something else inside the same column. */
.band > .wrap > :first-child{margin-top:0}
/* The capture forms are one section that already carries its own vertical padding.
   They take the ground without a second helping of space. */
.band.flush{padding:0}

/* ---- property gallery ------------------------------------------------------ */
/* The cover stays the hero and the rest sit under it as a strip. Both are ordinary
   links to the full-size image, so the whole gallery works before gallery.js arrives
   and works identically if it never does — the viewer is the enhancement, not the
   gallery.

   Geometry is fixed rather than left to the photographs. Property media arrives at
   whatever aspect the phone that took it was held at, and a strip of mixed ratios
   reads as a mistake; every thumbnail is one 4/3 box with the image cropped to fill. */

.gallery{margin:0}
.gallery-hero{position:relative}
/* The hero anchor fills its box: the big photograph is clickable too, because a person
   who clicks it expects it to open, not only the small ones. */
.gallery-hero .gallery-open{display:block;width:100%;height:100%}
.gallery-hero .gallery-open img{width:100%;height:100%;object-fit:cover;display:block}
.gallery-caption{font-size:12px;color:var(--on-surface-muted);margin-top:9px;
        line-height:1.6}
/* The estate-photograph disclaimer, when a listing has no media of its own. */
.shot-note{font-size:11px;color:var(--on-surface-subtle);margin-top:8px;line-height:1.6}

.gallery-strip{list-style:none;display:grid;
        grid-template-columns:repeat(auto-fill,minmax(104px,1fr));
        gap:10px;margin-top:12px}
.gallery-strip a{display:block;aspect-ratio:4/3;overflow:hidden;position:relative;
        border:1px solid var(--border);border-radius:var(--radius-small);
        background:var(--surface-muted);box-shadow:var(--shadow-1)}
.gallery-strip img{width:100%;height:100%;object-fit:cover;display:block;
        transition:transform .5s cubic-bezier(.2,.6,.2,1)}
.gallery-strip a:hover{box-shadow:var(--shadow-2);border-color:var(--border-strong)}
.gallery-strip a:hover img{transform:scale(1.05)}
.gallery-strip a:focus-visible{outline:none;box-shadow:var(--ring)}
.gallery-hero .gallery-open:focus-visible{outline:none;box-shadow:var(--ring)}

/* Plans, drawings and downloads. Kept out of the photograph strip on purpose: a floor
   plan dropped among pictures of rooms reads as one, and the buyer who came back to
   the page for the plan has to hunt eleven thumbnails to find it. */
.gallery-docs{margin-top:26px}
/* The estate tour: full column width, its own corner radius, black bars where the
   aspect differs rather than a stretched frame. */
.gallery-video{width:100%;max-height:70vh;border-radius:10px;background:#000;display:block}
.gallery-docs-head{font-size:17px;font-family:var(--font-display);font-weight:600}
.plan-list{list-style:none;display:grid;
        grid-template-columns:repeat(auto-fill,minmax(210px,1fr));gap:12px;margin-top:12px}
.plan{display:flex;align-items:center;gap:12px;padding:10px;
        border:1px solid var(--border);border-radius:var(--radius);
        background:var(--gradient-panel);box-shadow:var(--shadow-1);
        transition:box-shadow .25s ease,transform .25s ease}
.plan:hover{box-shadow:var(--shadow-2);transform:translateY(-2px)}
.plan:focus-visible{outline:none;box-shadow:var(--ring)}
.plan-shot{flex:0 0 auto;width:64px;aspect-ratio:1/1;overflow:hidden;
        border-radius:var(--radius-small);background:var(--surface-muted);
        border:1px solid var(--border)}
.plan-shot img{width:100%;height:100%;object-fit:cover;display:block}
.plan-text{display:flex;flex-direction:column;gap:3px;min-width:0}
.plan-kind{font-size:10px;letter-spacing:.14em;text-transform:uppercase;
        color:var(--on-surface-subtle)}
.plan-name{font-size:13.5px;color:var(--on-surface);line-height:1.4}

.file-list{list-style:none;display:flex;flex-direction:column;gap:8px;margin-top:12px}
.file{display:flex;align-items:baseline;gap:10px;flex-wrap:wrap;padding:12px 14px;
        border:1px solid var(--border);border-radius:var(--radius-small);
        background:var(--gradient-tint)}
.file:hover{border-color:var(--border-strong)}
.file:focus-visible{outline:none;box-shadow:var(--ring)}
.file-kind{font-size:10px;letter-spacing:.14em;text-transform:uppercase;
        color:var(--on-surface-subtle)}
.file-name{font-size:13.5px;color:var(--brand);font-weight:600}
.file-note{font-size:11px;color:var(--on-surface-subtle)}

/* ---- what you already have on this property -------------------------------- */
/* Shown to a signed-in client who has been here before. Quiet: it is a fact about them,
   not a call to action, and it sits above the buttons rather than replacing them. */
.yours{border:1px solid color-mix(in srgb,var(--brand) 30%,transparent);
  background:color-mix(in srgb,var(--brand) 7%,transparent);
  border-radius:var(--radius-small);padding:13px 15px;margin-top:16px}
.yours-head{font-size:9px;letter-spacing:.16em;text-transform:uppercase;font-weight:700;
  color:var(--on-surface-subtle);margin-bottom:6px}
.yours p{font-size:12.5px;line-height:1.6;color:var(--on-surface-muted)}
.yours .btn{margin-top:10px;width:100%;text-align:center}
.yours-note{font-size:11.5px;line-height:1.6;color:var(--on-surface-subtle);margin-top:10px}

/* ---- the ask drawer ------------------------------------------------------- */
/* THE REGISTER'S SEARCH — one line, 6 September 2026. It used the shared `.filters`
   bar, which is a bordered panel of divided cells built for four selects; one field in
   it read as a caption over a small box in a large empty slab, with the buttons off the
   input's baseline.

   The field owns the border and the focus ring so the whole control lights up together,
   and the magnifier is drawn from the field's own box-shadow-free pseudo-element rather
   than fetched — one request saved and it inherits the ink. Everything is 38px tall,
   which is `.btn`'s 13px text plus its 10px padding, so nothing has to be nudged. */
.finder{display:flex;align-items:center;gap:10px;margin-top:20px;flex-wrap:wrap}
.finder-field{position:relative;flex:1 1 320px;max-width:420px;min-width:0}
.finder-field::before{content:"";position:absolute;left:14px;top:50%;width:9px;height:9px;
  margin-top:-6px;border:1.5px solid var(--on-surface-subtle);border-radius:50%}
.finder-field::after{content:"";position:absolute;left:22px;top:50%;width:5px;height:1.5px;
  margin-top:2px;background:var(--on-surface-subtle);transform:rotate(45deg)}
.finder-field input{width:100%;height:38px;padding:0 14px 0 34px;font-family:inherit;
  font-size:13px;color:var(--on-surface);background:var(--surface);
  border:1px solid var(--border-strong);border-radius:var(--radius-small);
  transition:border-color .18s ease,box-shadow .18s ease}
/* Every placeholder reads as a HINT, not an answer (walkthrough U-3, 15 Oct 2026):
   "AGR-2026-11" in the browser's dark default looked like a value already typed. Italic
   and the subtle ink everywhere, with "e.g." on the ones that are examples. */
::placeholder{color:var(--on-surface-subtle);opacity:1;font-style:italic}
.finder-field input::placeholder{color:var(--on-surface-subtle)}
.finder-field input:focus{outline:0;border-color:var(--brand);box-shadow:var(--ring)}
.finder-field:focus-within::before{border-color:var(--brand)}
.finder-field:focus-within::after{background:var(--brand)}
/* The search field's own clear cross, which Safari and Chrome draw in their own ink. */
.finder-field input::-webkit-search-cancel-button{-webkit-appearance:none;appearance:none}
.finder .btn{height:38px;padding-top:0;padding-bottom:0}
/* Quiet, and only present when there is something to clear — a permanent Clear beside a
   permanent Search is two buttons where the second is usually a no-op. */
.finder-clear{font-size:12px;color:var(--on-surface-muted);text-decoration:none}
.finder-clear:hover{color:var(--brand);text-decoration:underline}

/* A panel over the page rather than a page of its own, because the question is about
   the thing behind it — closing it must put somebody back on the photograph they were
   looking at, not back through the browser's history.

   It opens two ways and both must work: `.is-open` is askdrawer.js's, `:target` is the
   browser's when there is no script. They set the same properties, so there is one
   appearance to maintain rather than two that drift. */

.drawer-scrim{position:fixed;inset:0;z-index:80;display:none;
  background:color-mix(in srgb,var(--gradient-from) 72%,transparent);
  backdrop-filter:blur(4px)}
/* Both ways of opening light the scrim, so there is one appearance rather than two. */
.drawer.is-open + .drawer-scrim,.drawer:target + .drawer-scrim{display:block}
/* And one way of being shut that beats both — see `.drawer.is-shut` below. */
.drawer.is-shut + .drawer-scrim{display:none}

.drawer{position:fixed;top:0;right:0;bottom:0;z-index:81;
  width:min(460px,100%);display:flex;flex-direction:column;
  background:var(--surface);border-left:1px solid var(--border);
  box-shadow:-30px 0 60px -30px color-mix(in srgb,var(--gradient-from) 45%,transparent);
  transform:translateX(100%);--drawer-vis:.34s;visibility:hidden;
  transition:transform .34s cubic-bezier(.2,.7,.3,1),visibility 0s linear var(--drawer-vis);
  overflow:hidden}
.drawer.is-open,.drawer:target{transform:translateX(0);visibility:visible;--drawer-vis:0s}

/* **A CLOSED DRAWER MUST NOT BE TABBABLE.**
   Measured 21 August 2026 on /manage/developments, which has exactly one: Tab reached
   `a.drawer-close` INSIDE the closed panel at keystroke 27. `aria-hidden` takes it out of
   the accessibility tree and the transform takes it off the screen; neither takes it out
   of the FOCUS ORDER — so a keyboard user tabs into a form that is not on screen, and a
   focusable element inside `aria-hidden` is a conformance error besides. The buyers queue
   carries four panels per enquiry, which turns a nuisance into a wall.

   `visibility:hidden` is the one property that removes an element from the focus order
   without removing it from layout, so the slide still animates. Applied instantly on the
   way in, delayed by the length of the slide on the way out.

   The delay is a custom property rather than a second `transition` on each state rule:
   `.drawer.is-open` is (0,2,0) and would outrank the `.drawer{transition:none}` inside
   `prefers-reduced-motion` below. Declaring `transition` once, on `.drawer`, keeps that
   override working. `drawer.js`'s `focusable()` filter tests `offsetParent !== null`,
   which stays non-null under `visibility:hidden`, so the trap on the OPEN panel is
   unaffected. The properties live on the three rules that already existed — a second
   `.drawer{}` block would be a duplicate selector, which `test/portal.test.js` refuses. */

/* ---- drawer widths --------------------------------------------------------
   Chosen by what has to be READ, not by field count. These set `width` only and
   deliberately do not touch `transform`, so the open/shut ladder above is unaffected.

   default 460px  assign — one select and a sentence; also the public ask/reserve drawer,
                  which must not move
   medium  620px  close, log a call — both carry consequence copy capped at 78ch, and
                  620 is already this platform's measure for a decision laid out to be
                  read (`.review{max-width:620px}` in portal.css)
   large   780px  reply — the only one where you read before you write */
.drawer.medium{width:min(620px,100%)}
.drawer.large{width:min(780px,100%)}
/* **JavaScript has closed it, and this has to beat `:target`.**
 *
 * Reported 19 August 2026: the close button and Escape did nothing on a drawer that had
 * been opened by a link carrying the fragment — `/manage/developments?new=1#new-development`
 * from the landing page. `drawer.js` removed `.is-open` and rewrote the URL with
 * `history.replaceState`, and **replaceState does not re-evaluate `:target`**: the address
 * bar loses the fragment and the browser goes on matching the element it was already
 * targeting. Only a real fragment navigation clears it.
 *
 * So the shut state is a class rather than the absence of one. Same specificity as the
 * two rules above and written after them, which is what makes it win. */
.drawer.is-shut{transform:translateX(100%);visibility:hidden;--drawer-vis:.34s}
/* Nothing behind the panel moves while it is open. On iOS a scrolling body under a
   fixed panel is what makes a drawer feel like a web page instead of an app. */
body.has-drawer{overflow:hidden}

.drawer-head{flex:0 0 auto;display:flex;align-items:flex-start;justify-content:space-between;
  gap:16px;padding:22px 24px 16px;border-bottom:1px solid var(--border);
  background:var(--gradient-panel)}
.drawer-head h2{font-size:20px;margin-top:4px}
.drawer-close{flex:0 0 auto;width:34px;height:34px;display:flex;align-items:center;
  justify-content:center;border-radius:50%;font-size:22px;line-height:1;
  color:var(--on-surface-muted);border:1px solid var(--border);
  background:var(--surface);transition:background .2s ease,color .2s ease}
.drawer-close:hover{background:var(--surface-muted);color:var(--on-surface);
  text-decoration:none}
.drawer-close:focus-visible{outline:2px solid var(--brand);outline-offset:2px}

/* The body scrolls, the head and the send button do not go with it — on a phone in
   landscape the form is taller than the screen, and a submit you have to hunt for is a
   form people abandon. */
.drawer-body{flex:1 1 auto;overflow-y:auto;padding:20px 24px 28px}
.drawer-body .note{margin-top:0;margin-bottom:18px}
.drawer-form .btn{width:100%;margin-top:4px}
.drawer-fine{font-size:11px;line-height:1.6;color:var(--on-surface-subtle);margin-top:12px}

/* Who we already know you are. A quiet panel rather than a form: it states a fact the
   account holds, and nothing in it is a control, so it must not look like one. */
.ask-who{border:1px solid var(--border);border-radius:var(--radius-small);
  background:var(--surface-muted);padding:14px 16px;margin-bottom:18px}
.ask-who-head{font-size:9px;letter-spacing:.16em;text-transform:uppercase;font-weight:700;
  color:var(--on-surface-subtle);margin-bottom:8px}
.ask-who dl{display:grid;grid-template-columns:auto minmax(0,1fr);gap:4px 16px;
  font-size:13px;margin:0}
.ask-who dt{color:var(--on-surface-subtle)}
.ask-who dd{color:var(--on-surface);margin:0;overflow-wrap:anywhere}
.ask-who-alt{font-size:11px;color:var(--on-surface-subtle);margin-top:10px}

.drawer-errors{border:1px solid color-mix(in srgb,var(--status-attention) 55%,transparent);
  background:color-mix(in srgb,var(--status-attention) 8%,transparent);
  border-radius:var(--radius-small);padding:12px 14px;margin-bottom:16px;
  font-size:12.5px;line-height:1.6;color:var(--on-surface)}

/* The process, as a list somebody can count. Ticks appear as steps complete, so the
   same markup serves "what will happen" before a request and "where you are" after it. */
.process{list-style:none;margin:0 0 18px;padding:0;display:flex;flex-direction:column;gap:9px}
.process li{display:flex;gap:11px;align-items:flex-start;font-size:13.5px;
  color:var(--on-surface-muted);line-height:1.5}
.process li.is-done{color:var(--on-surface)}
.process-mark{flex:0 0 auto;width:19px;height:19px;margin-top:1px;border-radius:50%;
  border:1px solid var(--border-strong);display:flex;align-items:center;
  justify-content:center;font-size:11px;color:var(--on-hero)}
.process li.is-done .process-mark{background:var(--brand);border-color:var(--brand)}
.process-opt{font-style:normal;font-size:11px;color:var(--on-surface-subtle)}

/* Sign in or create an account, without leaving the property. */
.drawer-tabs{display:flex;gap:6px;margin-bottom:16px;padding:4px;border-radius:var(--radius-small);
  background:var(--surface-muted)}
.drawer-tab{flex:1 1 0;text-align:center;font-size:12.5px;font-weight:600;padding:8px 10px;
  border-radius:calc(var(--radius-small) - 2px);color:var(--on-surface-muted)}
.drawer-tab:hover{text-decoration:none;color:var(--on-surface)}
.drawer-tab.is-on{background:var(--surface);color:var(--on-surface);box-shadow:var(--shadow-1)}
.drawer-notice{border:1px solid color-mix(in srgb,var(--brand) 40%,transparent);
  background:color-mix(in srgb,var(--brand) 8%,transparent);border-radius:var(--radius-small);
  padding:12px 14px;margin-bottom:16px;font-size:12.5px;line-height:1.6}

/* THE TRAIL'S FOOTER — where you are on the left, what changes it on the right, one
   hairline above. Rebuilt 8 September 2026: it was two stacked blocks and a loose submit
   button, which read as leftovers rather than as a control. In `base.css` because both the
   client's History and the staff project History render it — the lesson `.confirmer` and
   `.finder` each taught this estate once. */
.trailfoot{display:flex;align-items:center;justify-content:space-between;gap:16px;
  flex-wrap:wrap;margin-top:20px;padding-top:14px;border-top:1px solid var(--border)}
.trailfoot-count{font-size:12px;color:var(--on-surface-muted);margin:0}
.trailfoot-acts{display:flex;align-items:center;gap:18px;flex-wrap:wrap}

/* ROWS PER PAGE. A label and a number — the words "per page" and a button beside them were
   three things saying what one select already says. */
.pagesize{display:flex;align-items:center;gap:8px;margin:0}
.pagesize label{font-size:10px;letter-spacing:var(--micro-tracking);text-transform:uppercase;
  font-weight:700;color:var(--on-surface-subtle)}
.pagesize select{font-family:inherit;font-size:12.5px;font-weight:600;padding:5px 26px 5px 10px;
  border:1px solid var(--border);border-radius:var(--radius-pill);
  background:var(--surface);color:var(--on-surface);cursor:pointer;
  appearance:none;-webkit-appearance:none;
  background-image:linear-gradient(45deg,transparent 50%,currentColor 50%),
    linear-gradient(135deg,currentColor 50%,transparent 50%);
  background-position:calc(100% - 14px) 52%,calc(100% - 9px) 52%;
  background-size:5px 5px,5px 5px;background-repeat:no-repeat}
.pagesize select:hover{border-color:var(--border-strong)}
.pagesize select:focus-visible{outline:2px solid var(--brand);outline-offset:1px}
/* The no-JavaScript fallback, quiet enough not to compete with the select it serves. */
.pagesize-go{font-family:inherit;font-size:11.5px;font-weight:600;padding:5px 12px;
  border:1px solid var(--border);border-radius:var(--radius-pill);
  background:var(--surface);color:var(--on-surface-muted);cursor:pointer}

/* NEWER / OLDER, and where you are between them. Both ends always render — one of them
   inert — so the bar does not change width as somebody pages through it. */
.trailfoot-pages{display:flex;align-items:center;gap:4px}
.trailfoot-step{font-size:12px;font-weight:600;padding:5px 12px;border-radius:var(--radius-pill);
  color:var(--on-surface);text-decoration:none;border:1px solid transparent}
a.trailfoot-step:hover{background:var(--surface-muted);border-color:var(--border)}
a.trailfoot-step:focus-visible{outline:2px solid var(--brand);outline-offset:1px}
.trailfoot-step.is-off{color:var(--on-surface-subtle);opacity:.45}
.trailfoot-where{font-size:11px;font-weight:700;letter-spacing:.04em;
  color:var(--on-surface-subtle);padding:0 6px;font-variant-numeric:tabular-nums}

/* LIVE SAVES — `live.js`, shared by every surface that loads `portalhead` or
   `workspacehead`. A save that happens where it was pressed: the line sits inside the form
   it belongs to, because a banner at the top of the page telling you a photograph four
   cards down was saved is a message about something you cannot see. */
.live-status{font-size:11.5px;line-height:1.5;margin:8px 0 0;min-height:1em}
.live-status.is-busy{color:var(--on-surface-subtle)}
.live-status.is-ok{color:var(--status-complete)}
.live-status.is-bad{color:var(--status-attention)}

/* The card that has just been replaced, marked for a moment so somebody watching the
 * page sees WHICH one changed. Fading rather than flashing: the point is to draw the
 * eye to the card, not to announce the software. */
/* Applied by live.js to the card it has just replaced, so somebody watching the page
 * sees WHICH one changed. It fades rather than flashes: the point is to draw the eye to
 * the card, not to announce the software. */
@keyframes live-settled{from{background:color-mix(in srgb,var(--status-complete) 14%,transparent)}
  to{background:transparent}}
.is-settled{animation:live-settled 1.1s ease-out}

/* THE CONFIRMATION — `confirmform.js` builds one `<dialog>` per page. A native dialog,
   so the backdrop, the focus trap and Escape are the browser's own and correct. */
/* `margin:auto` is what centres a modal `<dialog>`, and it is the browser's own rule —
   but `base.css` opens with `*{margin:0}`, which beats the UA sheet and pinned this to
   the top-left corner (the user, seeing it there). Restated here rather than exempting
   `dialog` from the reset: the reset is doing its job everywhere else. */
.confirmer{margin:auto;border:0;padding:0;background:transparent;
  max-width:420px;width:calc(100% - 32px)}
.confirmer::backdrop{background:color-mix(in srgb, var(--shadow) 55%, transparent)}
.confirmer-box{padding:22px;border:1px solid var(--border);border-radius:var(--radius);
  background:var(--surface-panel);box-shadow:var(--shadow-3)}
.confirmer-title{font-family:var(--font-display);font-size:18px;margin:0 0 8px}
.confirmer-body{font-size:13px;line-height:1.7;color:var(--on-surface-muted);margin:0 0 18px}
.confirmer-acts{display:flex;justify-content:flex-end;gap:10px}

/* The confirmation. It is the whole panel rather than a line above the form, because
   the form has done its job and leaving it there invites the same question twice. */
.drawer-done{text-align:center;padding:26px 4px 8px}
.drawer-done h3{font-family:var(--font-display);font-size:20px;margin-bottom:10px}
.drawer-done .note{text-align:left;margin-bottom:16px}
.drawer-tick{display:flex;align-items:center;justify-content:center;
  width:52px;height:52px;margin:0 auto 16px;border-radius:50%;
  font-size:22px;color:var(--on-hero);background:var(--gradient-brand)}

/* ---- fullscreen viewer ----------------------------------------------------- */
/* Built by gallery.js and appended to the body, so a visitor with no JavaScript pays
   for none of it — and so no position:fixed box the width of the screen exists on the
   page before anyone opens one, which is the classic way a phone starts scrolling
   sideways.

   The ground is the navigation ink in every concept and both colour modes: a
   photograph is judged against something near-black whichever palette the site is
   wearing, and a viewer that goes bone in light mode washes the picture out.

   Opaque, not a 96% scrim. A few per cent of the page showing through is enough to
   read the headline and the logo behind the photograph, and the whole point of a
   fullscreen viewer is that there is nothing else to look at. */

.viewer{position:fixed;inset:0;z-index:70;display:flex;flex-direction:column;
        overflow:hidden;padding:10px;gap:8px;
        background:var(--surface-nav);color:var(--on-surface-nav)}
.viewer[hidden]{display:none}
.viewer-bar{flex:0 0 auto;display:flex;align-items:center;justify-content:space-between;
        gap:12px;padding:2px 4px}
.viewer-count{font-size:11px;letter-spacing:.14em;text-transform:uppercase;
        color:color-mix(in srgb,var(--on-surface-nav) 78%,transparent)}
.viewer-btn{appearance:none;border:1px solid
        color-mix(in srgb,var(--on-surface-nav) 26%,transparent);
        background:color-mix(in srgb,var(--on-surface-nav) 10%,transparent);
        color:var(--on-surface-nav);border-radius:var(--radius-pill);
        min-width:44px;min-height:44px;font-size:24px;line-height:1;cursor:pointer;
        display:flex;align-items:center;justify-content:center;
        transition:background .2s ease,border-color .2s ease}
.viewer-btn:hover{background:color-mix(in srgb,var(--on-surface-nav) 20%,transparent);
        border-color:color-mix(in srgb,var(--on-surface-nav) 48%,transparent)}
.viewer-btn:focus-visible{outline:none;
        box-shadow:0 0 0 3px color-mix(in srgb,var(--on-surface-nav) 55%,transparent)}
.viewer-btn[hidden]{display:none}
/* min-height:0 so the flex child may be shorter than its content — without it the
   image pushes the caption off the bottom of the screen instead of scaling down. */
.viewer-stage{flex:1 1 auto;min-height:0;position:relative;display:flex;
        align-items:center;justify-content:center;gap:10px}
.viewer-frame{flex:1 1 auto;min-width:0;height:100%;display:flex;
        align-items:center;justify-content:center}
.viewer-img{max-width:100%;max-height:100%;width:auto;height:auto;
        object-fit:contain;border-radius:var(--radius-small);
        box-shadow:var(--shadow-3)}
.viewer-caption{flex:0 0 auto;text-align:center;font-size:13px;line-height:1.6;
        padding:0 6px 4px;
        color:color-mix(in srgb,var(--on-surface-nav) 88%,transparent)}
.viewer-caption[hidden]{display:none}
/* The page behind must not scroll under an open viewer. Locked on the root rather
   than the body so the scrollbar's width stays reserved and the page does not jump
   sideways as the viewer opens. */
.is-viewing{overflow:hidden}
.is-viewing body{overflow:hidden}
/* A gallery link that has been taken over says so to a pointer. Without JavaScript it
   keeps the ordinary link cursor, which is what it still is. */
.gallery-open.is-enhanced{cursor:zoom-in}

/* The viewer's other kinds — a document frame and a player, sized like the image:
   never wider than the stage, never pushing the caption off. */
.viewer-doc{width:min(940px,100%);height:100%;border:0;border-radius:var(--radius-small);
        background:#fff;box-shadow:var(--shadow-3)}
.viewer-doc[hidden]{display:none}
.viewer-video{max-width:100%;max-height:100%;border-radius:var(--radius-small);
        box-shadow:var(--shadow-3)}
.viewer-video[hidden]{display:none}

/* ---- responsive ----------------------------------------------------------- */
/* Every breakpoint lives here, after the base rules. Scattered through the file
   a media query can sit BEFORE the rule it overrides and silently lose the
   cascade — which is what happened to the stats band. Widest first, narrowest
   last, so a smaller breakpoint always wins. */


/* ---------------------------------------------------------------- the assistant
   Built by assistant.js, never rendered server-side — there is no no-JavaScript path
   and a visitor without it sees the contact form, which is what the assistant hands
   them anyway. So none of these rules apply to anything unless the script ran. */
.assistant-launcher{position:fixed;right:18px;bottom:18px;z-index:40;
    padding:11px 18px;border-radius:999px;border:1px solid var(--brand);
    background:var(--brand);color:var(--on-brand);font-family:var(--font-body);
    font-size:13px;font-weight:600;cursor:pointer;box-shadow:var(--shadow-1)}
.assistant-launcher:hover{background:var(--brand-muted);border-color:var(--brand-muted)}

.assistant-panel{position:fixed;right:18px;bottom:70px;z-index:41;
    width:min(360px,calc(100vw - 36px));max-height:min(520px,calc(100vh - 110px));
    display:flex;flex-direction:column;
    background:var(--surface);border:1px solid var(--border);
    border-radius:var(--radius);box-shadow:var(--shadow-2,var(--shadow-1))}
.assistant-panel[hidden]{display:none}

.assistant-head{display:flex;align-items:center;justify-content:space-between;
    padding:12px 14px;border-bottom:1px solid var(--border)}
.assistant-head h2{font-family:var(--font-display);font-size:15px;margin:0}
.assistant-close{background:none;border:0;font-size:20px;line-height:1;cursor:pointer;
    color:var(--on-surface-muted);padding:0 4px}

.assistant-log{flex:1;overflow-y:auto;padding:12px 14px;display:flex;
    flex-direction:column;gap:10px}
.assistant-line{font-size:13px;line-height:1.6;margin:0}
.assistant-who{display:block;font-size:10.5px;letter-spacing:.1em;text-transform:uppercase;
    color:var(--on-surface-muted);margin-bottom:2px}
.assistant-you .assistant-text{color:var(--on-surface)}
.assistant-ecoterra .assistant-text{color:var(--on-surface-muted)}
/* A real person's words are not the stub's. Full-strength text and the brand rule down
   the side, so the transcript shows at a glance where the answers came from. */
.assistant-agent{border-left:2px solid var(--brand);padding-left:9px}
.assistant-agent .assistant-who{color:var(--brand)}
.assistant-agent .assistant-text{color:var(--on-surface)}

/* Whether anybody is there. Sits above the transcript and is filled in before the
   visitor types — a dot rather than a colour alone, since colour is not available to
   every reader. */
.assistant-presence{margin:0;padding:9px 14px;font-size:12px;line-height:1.5;
  color:var(--on-surface-muted);border-bottom:1px solid var(--border)}
.assistant-presence:empty{display:none;border:0;padding:0}
.assistant-presence.is-here{color:var(--on-surface)}
.assistant-presence.is-here::before,.assistant-presence.is-away::before{
  content:"";display:inline-block;width:7px;height:7px;border-radius:50%;
  margin-right:7px;vertical-align:middle}
.assistant-presence.is-here::before{background:var(--ok, var(--brand))}
.assistant-presence.is-away::before{background:var(--on-surface-muted)}

.assistant-escalate{border-top:1px solid var(--border);padding-top:10px;margin-top:2px}
.assistant-escalate p{font-size:12.5px;color:var(--on-surface-muted);line-height:1.55;
    margin-bottom:8px}
.assistant-escalate-form{display:flex;flex-direction:column;gap:6px}

.assistant-form{display:flex;gap:8px;padding:10px 14px;border-top:1px solid var(--border)}
.assistant-input{flex:1;min-width:0}
.assistant-panel input{padding:8px 10px;font-size:13px;font-family:var(--font-body);
    border:1px solid var(--border);border-radius:var(--radius-small);
    background:var(--surface);color:var(--on-surface)}
.assistant-panel input:focus-visible{outline:2px solid var(--brand);outline-offset:1px}

/* A launcher over the whole width would cover the footer's own actions on a phone. */

/* ---------------------------------------------------------- an empty browse
 *
 * A browse with nothing in it is still a page somebody arrived at on purpose, and the
 * two ways it can be empty are different problems: filters that matched nothing are the
 * visitor's own doing and one click from fixed, while an empty inventory is ours to
 * answer for. Both used to be one line of grey text with the actions buried in it as
 * inline links — which reads as an error message rather than as a page.
 *
 * Centred and bounded rather than full width: a sentence measured across 1700px is a
 * sentence nobody finishes.
 */
.empty-browse{border:1px solid var(--border);border-radius:var(--radius);
  background:var(--gradient-tint);padding:52px 32px 46px;text-align:center;
  margin:8px 0 6px}
.empty-mark{display:inline-flex;align-items:center;justify-content:center;
  border-radius:50%;border:1px solid var(--border-strong);color:var(--brand);
  background:var(--surface);margin-bottom:18px}
.empty-browse h2{font-family:var(--font-display);font-size:23px;line-height:1.2;
  margin:0 auto;max-width:24ch}
.empty-browse p{font-size:13.5px;line-height:1.65;color:var(--on-surface-muted);
  margin:12px auto 0;max-width:56ch}
.empty-acts{display:flex;flex-wrap:wrap;gap:12px;justify-content:center;margin-top:24px}
/* Sizing in its own rule: nothing on this platform may decide display and dimensions
   together — a (0,3,0) sizing rule outranking a swap rule once put two logos in the
   header. */
.empty-browse .empty-mark{width:62px;height:62px}
.empty-fine{font-size:11.5px;color:var(--on-surface-subtle);max-width:52ch}


/* ------------------------------------------------ the property page, selling
 *
 * It was a gallery, a price and two buttons — which is what every property site in
 * Nigeria is. What Ecoterra sells is the part after the photographs, and none of it was
 * on the page where the decision is made. These are the pieces that carry it: the
 * summary somebody wrote when they listed it, the five facts a buyer scans before
 * reading a word, and what buying through a trustee actually means.
 */

/* The five facts, scanned not read. Wraps to two rows on a phone rather than shrinking
   into something nobody can parse. */
.glance{list-style:none;display:flex;flex-wrap:wrap;gap:10px;margin:22px 0 0;padding:0}
.glance li{flex:1 1 104px;border:1px solid var(--border);border-radius:var(--radius-small);
  background:var(--surface);padding:12px 14px}
.glance-n{display:block;font-family:var(--font-display);font-size:19px;line-height:1.15;
  color:var(--on-surface)}
.glance-k{display:block;font-size:11px;color:var(--on-surface-subtle);margin-top:2px}

/* The summary. Larger than the body copy and narrower than the column: it is the one
   paragraph a buyer certainly reads, and a 90-character measure is not read. */
.listing-lede{font-size:16.5px;line-height:1.62;color:var(--on-surface);
  margin:24px 0 0;max-width:62ch}
.listing-head{font-family:var(--font-display);font-size:18px;line-height:1.3;
  margin:30px 0 0}

/* What buying here means. Three claims, each of which the platform can actually keep —
   the trustee, the title, the record. Not a marketing band: a list, in the reading
   column, where somebody deciding will meet it. */
.assurances{list-style:none;display:grid;grid-template-columns:1fr;gap:2px;margin:14px 0 0;
  padding:0;border:1px solid var(--border);border-radius:var(--radius);overflow:hidden;
  background:var(--border)}
.assurances li{background:var(--surface);padding:16px 18px}
.assurances h3{font-size:13.5px;font-weight:600;margin:0}
.assurances p{font-size:12.5px;line-height:1.6;color:var(--on-surface-muted);margin:6px 0 0;
  max-width:70ch}

/* AMENITIES — the selling block.

   It was a row of outline chips in the same 10px micro-caps this site uses for a status
   tag, which is how the one list of what somebody GETS for their money ended up as the
   quietest thing on the page. A panel of its own, tinted so the eye stops at it; ticks
   rather than borders, because each line is a thing the house has; and reading size,
   because eleven items in micro-caps is eleven items nobody reads.

   `auto-fill` rather than a fixed column count: a plot with three amenities and a duplex
   with twenty-four both have to look deliberate. The tick is a coloured glyph and not a
   filled circle — a circle needs a width and a height on a rule that already sets
   `display`, and this sheet does not do that. */
.amenities-panel{background:var(--gradient-tint);border:1px solid var(--border);
                 border-radius:var(--radius);padding:20px 24px 24px;margin-top:28px}
.amenities-panel .listing-head{margin-top:0}
.amenity-list{display:grid;grid-template-columns:repeat(auto-fill,minmax(200px,1fr));
              gap:11px 24px;margin:16px 0 0;padding:0;list-style:none}
.amenity-list li{display:flex;align-items:baseline;gap:10px;
                 font-size:14px;line-height:1.35;color:var(--on-surface)}
.amenity-tick{color:var(--brand);font-weight:700;font-size:13px}

/* THE RECORD. Every fact including the empty ones — a row that disappears cannot be
   missed, and a buyer cannot tell "nobody has said" from "there isn't one". Two columns
   on a wide page, one on a phone (the breakpoint at the foot of this file). The empty
   rows are dimmed rather than hidden: present enough to be asked about, quiet enough
   that the filled ones are what the eye lands on. */
.spec-table{display:grid;grid-template-columns:repeat(2,minmax(0,1fr));gap:0 30px;
            margin:14px 0 0;font-size:13px}
.spec-table > div{display:flex;justify-content:space-between;gap:16px;
                  padding:9px 0;border-bottom:1px solid var(--border)}
.spec-table dt{color:var(--on-surface-subtle)}
.spec-table dd{color:var(--on-surface);text-align:right;font-weight:500}
.spec-table .unsaid dd{color:var(--on-surface-subtle);font-weight:400}
.spec-note{font-size:11px;color:var(--on-surface-subtle);margin:12px 0 0}

/* How many photographs there are, said out loud. The strip shows four and scrolls;
   nothing told anybody there were another eleven behind it. */
.shot-count{font-size:12px;color:var(--on-surface-subtle);margin:10px 0 0;
            letter-spacing:.02em}

/* THE PRICE FOLLOWS THE READER. A buyer scrolls a long way down a property page —
   photographs, summary, estate, the other homes — and every one of those inches is a
   step away from the price, the status and the way to make contact. Sticky, so the
   decision is never off screen. Scoped to `.detail` because `.facts` is also used
   where there is no second column, and unstuck below 960px where the layout is one
   column and a pinned panel would sit on top of the reading. */
.detail > .facts{position:sticky;top:18px}

/* CALL, OR MESSAGE. A Nigerian buyer looking at a ₦350m house does not fill in a form —
   they ring, or they send a WhatsApp, and they do it from the page they are on. Full
   width in the facts panel so neither is the poor relation of the two form buttons. */
.talk{display:flex;flex-direction:column;gap:8px;margin:14px 0 4px}
.talk .btn{width:100%;text-align:center}
.talk-chat,.talk-share{font-size:12px}

/* The estate's other homes, tighter than the browse grid — this is a footnote to a
   decision, not a search result. */
.grid-tight{margin-top:14px}
.grid.grid-tight{grid-template-columns:repeat(auto-fill,minmax(210px,1fr));gap:14px}


/* Four classes this page had been using that base.css does not define. Public pages load
   base.css ALONE — `portal.css` is the signed-in surface — so `.pill`, `.section-sub`,
   `.nothing` and `.band.attention` all rendered as bare text here. Two of them were on the
   page before today: the "none of these has signed with Ecoterra yet" warning, which is
   the most important sentence on it, was showing as an unframed paragraph.

   Named for this page rather than copied out of portal.css. A second `.pill` in a second
   stylesheet is two components that drift, and the one the signed-in surface uses carries
   status semantics this page has no use for. */
.publicnote{margin:6px 0 0;font-size:13.5px;line-height:1.65;color:var(--on-surface-muted);
  max-width:62ch}
.warnband{border:1px solid var(--border);border-left:4px solid var(--status-attention);
  border-radius:var(--radius);background:var(--surface-panel);padding:20px 24px;
  margin-top:22px}
.warnband h2{margin:0;font-family:var(--font-display);font-size:17px;line-height:1.35;
  color:var(--on-surface)}
.warnband p{margin:9px 0 0;font-size:13.5px;line-height:1.7;color:var(--on-surface-muted);
  max-width:80ch}
.emptynote{border:1px dashed var(--border-strong);border-radius:var(--radius);
  padding:22px 24px;margin-top:22px;color:var(--on-surface-muted);font-size:13.5px}
.emptynote p{margin:0}

/* ================================================= THE PUBLIC TRUSTEE CATALOGUE

   30 August 2026, at the user's direction: *"there is no way to actually see that Sterling
   Trust is a trustee — it is hidden."*

   It was. The page led with four explainer tiles and three question tiles, and the
   organisations — the only thing on it a visitor cannot get anywhere else — sat between
   them in an unstyled table whose four columns of unequal text had collapsed into one
   another. The argument had crowded out the evidence.

   Cards rather than a table, and not only for weight: a table cannot carry the paragraph a
   firm writes about itself, which is the thing a buyer is actually choosing on.

   ---------------------------------------------------------------------------------------
   SECOND PASS, same day: *"still looks off."* It was, and the cause was the width of the
   page rather than the spacing inside it.

   `--page-max` is 1760px because this site is mostly photography. This page has none, and
   the live catalogue holds exactly ONE firm — so `auto-fit` gave that firm a single track
   1,560px wide while `.firmcard-about` capped its text at 52ch, and the only evidence on
   the page rendered as a 370px ribbon at the far left of an almost empty card. A one-firm
   catalogue is the normal case here, not an edge case, and it was the case nothing had
   been composed for.

   Three moves, in the order they matter:

   1. The page takes a reading column (`.wrap.reading`) instead of the site's photographic
      one, so no section is ever wider than its content can fill.
   2. A lone card lays its interior out in two columns — the firm's words against a panel
      of facts — so width becomes composition instead of emptiness. Two and three firms
      keep the stacked interior, because at a third of the column there is no second
      column to give them.
   3. The hero, which left the whole right half of the window empty, gained a second
      column of its own.  */

/* The reading column. A CAP, not a target: at 1120px and below this changes nothing, so
   the phone and laptop layouts are untouched by it. It exists for the 1440px and 2500px
   monitors, where the site's 1760px frame — correct for a wall of property photographs —
   turns three paragraphs of argument into three short lines adrift in a very wide room. */
/* ~~This class used to cap the page at 1120px.~~ Removed 31 August 2026, at the user's
   direction — and the literal declaration is described rather than reproduced here,
   because the test that forbids it reads the whole stylesheet and cannot tell a comment
   from a rule. It should not have to:
   *"look at the other views that are stretched to the end of the pages, and this has a lot
   of margin left and right."* It did, and it was the only page on the site that did. A
   reading column was the wrong answer to a card that stretched: it narrowed everything to
   fix one thing, and made this page visibly not part of the site.

   The card is what needed containing, and `.firmlist` below contains it — see the note
   there. The class is kept as a no-op so the markup reads the same; nothing sets a width. */
.wrap.reading{max-width:var(--page-max)}

/* ---- the hero -------------------------------------------------------------------- */

/* 56px of band, then 44px of section padding, then a 66px section-head margin, put roughly
   250px of nothing between the lede and the first heading — on a page whose whole job is
   to get somebody to the firms. The band owns the rhythm here and the firms section adds
   almost none of its own. */
.trust-hero-band{padding:46px 0 38px}
/* Stretched rather than top-aligned, and the copy column is a flex column so the jump
   link can be pushed to its foot. The panel is the taller of the two by 120px at 1024px,
   and a hero whose left column stops dead two-thirds of the way down is the same
   complaint as the one this page started with, only smaller. Sending the link to the
   bottom gives the two columns a shared baseline and turns the gap into spacing. */
.trust-hero{display:grid;grid-template-columns:minmax(0,1fr) minmax(300px,370px);
  gap:clamp(30px,4.5vw,68px);align-items:stretch}
.trust-hero-copy{display:flex;flex-direction:column}
/* The eyebrow is a CHIP, and a flex column stretches its children across the cross axis
   — so making this column a flex container turned a 150px pill into a 460px bar with
   four words at one end of it. Only the chip needs to opt out: the heading and the lede
   are blocks that should fill the column and are measured in `ch` anyway. */
.trust-hero .eyebrow{align-self:flex-start}
.trust-hero h1{margin:14px 0 16px}
/* Held well short of the panel beside it. The lede is the only long line in the hero and
   an unmeasured one would run to the panel's edge, which is where a two-column hero stops
   reading as two columns. */
.trust-hero .lede{max-width:50ch}
.trust-hero-jump{margin:auto 0 0;padding-top:24px;font-size:13px;font-weight:600}

/* The custody diagram, and the reason the hero has a second column at all.

   Deliberately NOT a restatement of "How this works, briefly" further down: that section
   lists the rules, this one shows the route. Somebody who reads only the hero should still
   be able to answer "so where is my money, physically?" — which is the question the page
   exists for and the one the old hero left to a paragraph. */
.custody-panel{border:1px solid var(--border);border-radius:var(--radius);
  background:var(--surface-panel);box-shadow:var(--shadow-1);padding:22px 24px 20px}
.custody-title{margin:0 0 16px;font-size:10.5px;letter-spacing:var(--micro-tracking);
  text-transform:uppercase;font-weight:700;color:var(--on-surface-muted)}
.custody{list-style:none;margin:0;padding:0}
.custody-step{position:relative;padding:0 0 16px 26px}
.custody-step:last-child{padding-bottom:0}
/* The rail is drawn between the dots rather than behind them, and the last step has none —
   a line running off the bottom of the panel would promise a fourth step that does not
   exist, and the point of the diagram is that the money stops here. */
.custody-step:not(:last-child)::after{content:"";position:absolute;left:5px;top:17px;
  bottom:1px;width:1px;background:var(--border-strong)}
.custody-step::before{content:"";position:absolute;left:0;top:5px;width:11px;height:11px;
  border-radius:50%;border:2px solid var(--border-strong);background:var(--surface-panel)}
/* The middle step is the entire claim of the page, so it is the one the eye lands on. */
.custody-step.is-held::before{border-color:var(--brand);background:var(--brand)}
.custody-who{margin:0;font-size:13.5px;font-weight:700;color:var(--on-surface)}
.custody-what{margin:3px 0 0;font-size:13px;line-height:1.65;color:var(--on-surface-muted)}
.custody-foot{margin:16px 0 0;padding-top:14px;border-top:1px solid var(--border);
  font-size:12.5px;line-height:1.6;color:var(--on-surface-muted)}

/* ---- the firms ------------------------------------------------------------------- */

/* The section head already carries 66px above it for the case of following other content.
   Here it follows a band that has just set its own bottom padding, and the two together
   were most of the dead space. */
.trust-firms{padding-bottom:8px}
.trust-firms .section-head{margin-top:0}
/* Fills the right-hand end of a head that was otherwise 700px of nothing, and answers the
   first question a chooser has: how much choice is there? */
.section-count{margin:0;font-size:12px;letter-spacing:var(--micro-tracking);
  text-transform:uppercase;font-weight:600;color:var(--on-surface-muted);white-space:nowrap}

/* THREE ACROSS, and that is what fixes the lone card.

   `auto-fit` collapses the tracks nothing occupies and hands the survivor the whole row —
   which is exactly why one firm rendered as a slab the width of the page. `auto-fill`
   would keep the tracks but still deals as many as fit, so the count changes with the
   window rather than with the design.

   So the columns are stated. One firm sits in one of three, which is what a catalogue of
   one should look like: a card, with room beside it for the ones that are coming. Two sit
   side by side. Four wrap onto a second row. */
.firmlist{list-style:none;margin:22px 0 0;padding:0;display:grid;gap:20px;
  /* `stretch`, not `start`. The facts panel is what a visitor compares across firms, and
     `.firmcard-side` already pushes it to the bottom of its card — so equal-height cards
     put every fee on the same line and a ragged row does not. */
  grid-template-columns:repeat(3,minmax(0,1fr));align-items:stretch}
.firmcard{border:1px solid var(--border);border-radius:var(--radius);
  background:var(--surface-panel);box-shadow:var(--shadow-1);padding:24px 26px 26px;
  display:flex;flex-direction:column}
/* A rule under the head, so the identity of the firm reads as a record header rather than
   as the first of several similar-looking blocks. */
.firmcard-head{padding-bottom:15px;border-bottom:1px solid var(--border)}
/* Kind and badge share one line; the name takes the next. Beside the name, the badge
   wrapped onto its own row as soon as the name was long, so in a row of three cards it
   appeared in two different places and the heads stopped lining up. */
.firmcard-kindline{display:flex;flex-wrap:wrap;gap:8px 14px;align-items:center;
  justify-content:space-between}
.firmcard-kindline .kind{flex:1 1 auto;min-width:0}
/* The card's own badge. NOT portal.css's `.pill` — that lives on the signed-in surface,
   which this page never loads, and it carries status semantics ("live", "holding") that
   mean nothing to a visitor. */
.firmcard-flag{flex:0 0 auto;display:inline-block;padding:4px 11px;border-radius:var(--radius-pill);
  font-size:10.5px;font-weight:700;letter-spacing:var(--micro-tracking);text-transform:uppercase;
  border:1px solid var(--border-strong);color:var(--on-surface-muted);white-space:nowrap}
.firmcard-flag.can-hold{border-color:var(--brand);color:var(--brand)}
.firmcard .kind{margin:0;font-size:10.5px;letter-spacing:var(--micro-tracking);
  text-transform:uppercase;color:var(--on-surface-muted);font-weight:600}
.firmcard h3{margin:4px 0 0;font-family:var(--font-display);font-size:19px;line-height:1.25;
  letter-spacing:-.014em;color:var(--on-surface)}
/* The firm's own words, and the reason the card exists. Held to a reading measure and
   allowed the room a paragraph needs — this is what somebody chooses on. */
.firmcard-body{margin-top:16px}
.firmcard-about{margin:0;font-size:14px;line-height:1.75;color:var(--on-surface);
  max-width:58ch}
/* The rest of what the firm wrote. The live entry is six paragraphs; all six on the face
   of the card push the fee and the regulator below the fold and make a row of cards
   impossible to compare, which is the one thing this list is for. Native `<details>`, so
   it costs no script and find-in-page still opens it. */
.firmcard-more{margin-top:14px}
.firmcard-more > summary{list-style:none;cursor:pointer;position:relative;
  padding:0 22px 0 0;font-size:12.5px;font-weight:600;color:var(--brand)}
.firmcard-more > summary::-webkit-details-marker{display:none}
.firmcard-more > summary::after{content:"";position:absolute;right:4px;top:3px;width:7px;
  height:7px;border-right:2px solid var(--brand);border-bottom:2px solid var(--brand);
  transform:rotate(45deg)}
.firmcard-more[open] > summary::after{transform:rotate(-135deg);top:6px}
.firmcard-more > summary:focus-visible{outline:2px solid var(--brand);outline-offset:3px}
.firmcard-more > p{margin:12px 0 0;font-size:13.5px;line-height:1.75;
  color:var(--on-surface-muted);max-width:58ch}

/* The facts, pinned to the foot of the card so that a row of firms with descriptions of
   very different lengths still lines its regulators and its fees up with each other.
   Comparison is the job; ragged rows are what stops it. */
.firmcard-side{margin-top:auto;padding-top:20px}
/* Label ABOVE value, never beside it. Right-aligned values put "0.75% of each payment,
   split between the client and the developer" hard against the far edge of a card that
   can be 1,100px wide with its label stranded at the other end — two halves of one
   sentence a screen apart. Stacked reads identically at 300px and at 1,100px. */
.firmcard-facts{margin:0;border:1px solid var(--border);border-radius:var(--radius-small);
  background:var(--surface-muted);padding:2px 16px}
.firmcard-facts .fact{padding:12px 0;border-top:1px solid var(--border)}
.firmcard-facts .fact:first-child{border-top:0}
.firmcard-facts dt{font-size:10.5px;letter-spacing:var(--micro-tracking);
  text-transform:uppercase;font-weight:600;color:var(--on-surface-muted)}
.firmcard-facts dd{margin:5px 0 0;font-size:13.5px;line-height:1.6;color:var(--on-surface);
  overflow-wrap:anywhere}
/* `:not(.btn)`, as everywhere in this file: a bare `a` here would recolour a button
   somebody later drops into the card, and the suite refuses it for that reason. */
.firmcard-facts a:not(.btn){color:var(--brand)}
/* An example firm says so twice — the pill and this line — because a visitor who reads
   only one of them must not come away believing it can hold their money. */
.firmcard-note{margin:14px 0 0;font-size:12px;line-height:1.6;color:var(--on-surface-muted)}

/* ---- what a firm is approved to do --------------------------------------------------

   12 September 2026, with the provider model. A firm is approved PER SERVICE and prices
   each one separately — Sterling holds money at 0.5% of each payment and also charges a
   flat ₦150,000 for a registry search — and the card had one fee row for the whole firm,
   so it could not say either number. It said "Not stated", for every firm, on the live
   site.

   Placed ABOVE `.firmcard-facts` in the card, because what a firm may do is what somebody
   is choosing on; the regulator only matters once you know what you are asking them to
   do. The fee moved out of the facts panel and into these rows, where it belongs to a
   service rather than to an organisation.

   Hairlines rather than a second panel. The card already ends in the muted `.firmcard-facts`
   box, and two stacked panels inside one card read as two cards badly joined. These rows
   sit directly on the card's own surface and are separated the way a record is. */
.firmcard-services{margin-top:18px;padding-top:16px;border-top:1px solid var(--border)}
.svcblock-head{margin:0 0 12px;font-size:10.5px;letter-spacing:var(--micro-tracking);
  text-transform:uppercase;font-weight:700;color:var(--on-surface-muted)}
.svclist{list-style:none;margin:0;padding:0}
/* EVERY row carries the 3px rail and the indent it costs, transparent unless the row is
   the money one. Putting the border only on `.is-money` shifted that one row 16px right
   of its neighbours, which in a list of three reads as a rendering fault rather than as
   emphasis. */
.svcitem{padding:12px 0 12px 13px;border-top:1px solid var(--border);
  border-left:3px solid transparent}
.svcitem:first-child{border-top:0;padding-top:0}
.svcitem:last-child{padding-bottom:0}
/* The custody service, marked. `--brand` resolves to a deep green on light and a light
   green on dark, so the rail is visible against `--surface-panel` in both. */
.svcitem.is-money{border-left-color:var(--brand)}
.svcitem-top{display:flex;flex-wrap:wrap;gap:6px 10px;align-items:baseline}
.svcitem-name{margin:0;font-size:14px;font-weight:700;color:var(--on-surface)}
/* "Holds your money", and the reason the rail is never the only marker: base.css already
   states, on `.tile .status`, that a status may not be carried by colour alone. Somebody
   scanning a firm approved for three services has to be able to READ which one takes
   custody.

   `--brand-soft` / `--on-brand-soft` is a DECLARED PAIR in the token package and flips
   together with the theme — a pale ground under dark green type in light, a deep ground
   under pale green type in dark — so neither state has to be checked by eye. (No literal
   colour here: the suite reads this whole file and cannot tell a comment from a rule.)
   It is used rather than `.firmcard-flag`'s outline style so the two badges cannot be
   confused: one is about the FIRM, this one is about one of its services. */
.svcitem-flag{flex:0 0 auto;display:inline-block;padding:3px 9px;
  border-radius:var(--radius-pill);background:var(--brand-soft);color:var(--on-brand-soft);
  font-size:9.5px;font-weight:700;letter-spacing:var(--micro-tracking);
  text-transform:uppercase;white-space:nowrap}
.svcitem-fee{margin:4px 0 0;font-size:13px;line-height:1.6;color:var(--on-surface)}
/* An unpriced service is a real state — a firm may be approved before it has said what it
   charges — and it is muted rather than hidden. A row with no fee line at all would read
   as free. */
.svcitem-nofee{color:var(--on-surface-muted)}
/* The regulator and licence under the service they belong to (27 Sep 2026). */
.svcitem-reg{margin:3px 0 0;font-size:12.5px;line-height:1.6;color:var(--on-surface-muted)}
.svcitem-about{margin:5px 0 0;font-size:12.5px;line-height:1.65;
  color:var(--on-surface-muted);max-width:58ch}
/* Approved for NOTHING yet, which is what an organisation looks like on its first day.
   Dashed, like `.emptynote`, because the two say the same kind of thing: this is empty on
   purpose and we are telling you so. */
.svcnone{margin:0;padding:12px 14px;border:1px dashed var(--border-strong);
  border-radius:var(--radius-small);font-size:13px;line-height:1.65;
  color:var(--on-surface-muted)}

/* ---- the five services, explained ----------------------------------------------------

   The section below the firms. New 12 September 2026: the page had no way of telling a
   visitor that "provider" now means five different jobs, so a card reading "Approved for:
   Survey" arrived with nothing to read it against.

   Five stated columns, matching `.firmlist`'s reasoning — `auto-fit` would deal a
   different number of cards at every window width, and a list whose shape changes with
   the window reads as broken rather than as responsive. These are five short cards and
   there will be five until somebody installs a sixth service. */
.trust-services{padding-top:8px}
.svcgrid{list-style:none;margin:22px 0 0;padding:0;display:grid;gap:16px;
  grid-template-columns:repeat(5,minmax(0,1fr));align-items:stretch}
/* The top rule is a quiet marker and NOT the thing that carries the meaning.

   Every card gets the same 3px rule so the escrow one is distinguished by the COLOUR of a
   thing that is there rather than by the presence of a thing the others lack — a row where
   one card alone has a rule reads as four cards and an odd one out, and the heights would
   differ by 2px as well.

   **Measured, both themes**: `--brand` on `--surface-panel` is 12.74:1 in light and 5.77:1
   in dark, so the escrow rule is emphatic in both. `--border-strong` on the same ground is
   1.65:1 and 2.03:1 — deliberately below the 3:1 that WCAG asks of a control you must SEE
   to operate, because this is not one. It is a hairline in the same register as
   `.firmcard`'s own border (1.27:1), and it is decoration. What actually says which
   service takes custody is the words in `.svcitem-flag` — "Holds your money" — for the
   reason base.css states on `.tile .status`: a status may never be carried by colour
   alone. Raising this rule to 3:1 would make five loud rules and lose the distinction. */
.svccard{display:flex;flex-direction:column;padding:18px 18px 20px;
  border:1px solid var(--border);border-top:3px solid var(--border-strong);
  border-radius:var(--radius);background:var(--surface-panel);box-shadow:var(--shadow-1)}
.svccard.is-money{border-top-color:var(--brand)}
.svccard-name{margin:0;font-family:var(--font-display);font-size:16px;line-height:1.3;
  letter-spacing:-.012em;color:var(--on-surface)}
/* Whether a client can skip it. `--accent` is the token package's secondary — a bronze in
   light, a warmer gold in dark — and carries its own contrast against `--surface-panel` in
   both. It is used here and nowhere else on this page, so it reads as a property of the
   service rather than as decoration. */
.svccard-req{margin:8px 0 0;font-size:10px;letter-spacing:var(--micro-tracking);
  text-transform:uppercase;font-weight:700;color:var(--accent)}
.svccard-about{margin:9px 0 0;font-size:12.5px;line-height:1.65;
  color:var(--on-surface-muted)}

/* ~~The lone card spread its interior into two columns.~~ Removed 31 August 2026: it
   existed only because `auto-fit` handed a single firm the whole row, and the row is now
   three stated columns, so a lone card is a third of the width and has nothing to spread.
   Struck rather than deleted because the reasoning was sound for the layout it was written
   for, and somebody widening this grid again will want to know why it was there. */

/* `.note` is defined in portal.css, which no public page loads, and `/trustees` was the
   only public view using it — so three paragraphs here rendered at the browser's default
   size, colour and full window width. Named for this page rather than copied across,
   for the reason `.publicnote` above gives: a second copy of a portal component in a
   second stylesheet is two things that drift. */
.trustnote{margin:18px 0 0;font-size:13px;line-height:1.7;color:var(--on-surface-muted);
  max-width:76ch}
.trustnote strong{color:var(--on-surface)}

/* The argument and the questions, side by side. Each was a full-width section holding a
   74ch column, so each left most of the window empty and cost a screenful of scrolling on
   a page whose evidence is above them. Neither is the reason anybody came and they are the
   same weight as each other, so they close the page as one section in two columns. DOM
   order is unchanged: the argument still precedes the questions. */
.trust-close{display:grid;grid-template-columns:minmax(0,1fr) minmax(0,1fr);
  gap:clamp(30px,4.5vw,68px);align-items:start}
.trust-close-col > h2{margin:0}

/* The argument, summarised. Four sentences under the evidence rather than four tiles
   above it — the long version lives on /approach, which said all of this at length and
   was linked from nowhere on this page. */
.plainpoints{list-style:none;margin:18px 0 0;padding:0;display:grid;gap:12px;
  max-width:74ch}
.plainpoints li{position:relative;padding-left:20px;font-size:13.5px;line-height:1.7;
  color:var(--on-surface-muted)}
.plainpoints li strong{color:var(--on-surface)}
.plainpoints li::before{content:"";position:absolute;left:2px;top:10px;width:6px;height:6px;
  border-radius:50%;background:var(--brand)}

/* The questions, collapsed. Three tiles of text nobody had asked for yet was a screenful
   between the firms and the way out of the page. */
.qlist{display:grid;gap:0;margin-top:18px;border-top:1px solid var(--border);
  max-width:74ch}
.q{border-bottom:1px solid var(--border)}
.q > summary{list-style:none;cursor:pointer;padding:15px 30px 15px 0;position:relative;
  font-size:14.5px;font-weight:600;color:var(--on-surface)}
.q > summary::-webkit-details-marker{display:none}
.q > summary::after{content:"";position:absolute;right:6px;top:21px;width:8px;height:8px;
  border-right:2px solid var(--on-surface-muted);border-bottom:2px solid var(--on-surface-muted);
  transform:rotate(45deg);transition:transform .2s ease}
.q[open] > summary::after{transform:rotate(-135deg);top:24px}
.q > summary:hover{color:var(--brand)}
.q > summary:focus-visible{outline:2px solid var(--brand);outline-offset:2px}
.q > p{margin:0 0 18px;font-size:13.5px;line-height:1.75;color:var(--on-surface-muted);
  max-width:68ch}

/* ================================================================ A SALE PILL

   31 August 2026, at the user's direction: a green pill on a property to say a promotion
   is running, with what the unit was priced at struck through beside what it costs now.

   `--status-complete` rather than `--brand`: the brand green is the whole site, and a
   badge in it is a badge that reads as chrome. The status green is the one this palette
   already uses for "this is good news", and it is the only place on a card that is
   allowed to be loud. */
.salepill{position:absolute;left:14px;top:14px;z-index:2;display:inline-flex;
  align-items:center;gap:6px;padding:6px 13px;border-radius:var(--radius-pill);
  background:var(--status-complete);color:var(--on-brand);
  font-size:10.5px;font-weight:700;letter-spacing:var(--micro-tracking);
  text-transform:uppercase;box-shadow:var(--shadow-2)}
/* On the listing page it sits in the facts column, in the flow rather than over a photo. */
.salepill.inline{position:static;display:inline-flex}
/* The photograph is the positioning context, and ONLY where a pill exists.

   A bare `.shot{position:relative}` was the first attempt and the suite refused it:
   portal.css defines `.shot` too, both sheets load on an account page, and a base rule
   redefining it silently restyles the signed-in component. `.shot.has-sale` is set in the
   markup beside the pill, so the two cannot get out of step — and it is a class rather
   than `:has()` so there is no browser on which the badge pins itself to the wrong
   ancestor and lands over the title. */
.shot.has-sale{position:relative}

/* WHAT IT WAS. Struck, quieter than the price, and never on its own — it is only
   rendered when the property service says the former price genuinely exceeds the current
   one, so this style can never be the thing that makes a false saving look real. */
.price-was{margin-left:9px;font-size:.78em;font-weight:400;color:var(--on-surface-subtle);
  text-decoration:line-through;text-decoration-thickness:1px}
.salepill-ends{margin:8px 0 0;font-size:12px;line-height:1.5;color:var(--on-surface-muted)}

/* ---------------------------------------------------------------- maps
   26 September 2026. One place on a listing or estate page, the browse map beside
   the results on /properties, and the workspace picker. Every colour is a token, so
   the map's chrome agrees with the page in both themes; Google's own tiles follow the
   colour scheme the page passes. The canvas is a plain block until the script fills
   it, so a page with no key shows nothing where the map would be and the links
   beneath still work. The picker lives here too — it is the same control. */
.map-canvas{width:100%;aspect-ratio:16/10;min-height:260px;border:1px solid var(--border);
  border-radius:var(--radius);background:var(--surface-muted);overflow:hidden;position:relative;
  box-shadow:var(--shadow-1)}
.map-canvas.is-failed{display:flex;align-items:center;justify-content:center;padding:20px}
.map-canvas .map-note{font-size:13px;color:var(--on-surface-muted);text-align:center;max-width:36ch;margin:0}
.map-links{display:flex;gap:18px;flex-wrap:wrap;align-items:center;margin-top:12px;font-size:13px}
.map-link{font-weight:500;color:var(--brand);border-bottom:1px solid transparent}
.map-link:hover{border-bottom-color:var(--brand)}
.map-fine{font-size:12px;color:var(--on-surface-subtle);margin-top:8px}
.place .map-canvas{margin-top:14px}

/* A word ON the map: "Estate pin" on an inherited position, the picker's prompt. */
.map-badge{display:inline-block;margin:10px;padding:5px 10px;border-radius:var(--radius-pill);
  background:var(--surface);color:var(--on-surface);border:1px solid var(--border);
  box-shadow:var(--shadow-1);font-size:11px;letter-spacing:.06em;text-transform:uppercase;font-weight:600}

/* Our marker: a price on a pill, tail beneath, in the page's own colours. */
.map-pin{position:absolute;transform:translate(-50%,calc(-100% - 6px));cursor:pointer;
  font:inherit;padding:0;border:0;background:transparent;z-index:1}
.map-pin-price{display:inline-block;padding:6px 10px;border-radius:var(--radius-pill);
  background:var(--surface);color:var(--on-surface);border:1px solid var(--border-strong);
  box-shadow:var(--shadow-2);font-size:12px;font-weight:600;letter-spacing:.01em;line-height:1;
  white-space:nowrap;transition:transform .15s ease,background-color .15s ease,color .15s ease}
.map-pin::after{content:"";position:absolute;left:50%;bottom:-5px;width:10px;height:10px;
  transform:translateX(-50%) rotate(45deg);background:var(--surface);
  border-right:1px solid var(--border-strong);border-bottom:1px solid var(--border-strong)}
.map-pin-sale{position:absolute;top:-9px;right:-6px;padding:2px 6px;border-radius:var(--radius-pill);
  background:var(--status-complete);color:var(--on-brand);font-size:9px;font-weight:700;
  letter-spacing:.08em;text-transform:uppercase}
.map-pin.is-gone .map-pin-price{color:var(--on-surface-subtle);background:var(--surface-muted);
  text-decoration:line-through}
.map-pin.is-hover,.map-pin.is-active{z-index:3}
.map-pin.is-hover .map-pin-price{transform:translateY(-2px)}
.map-pin.is-active .map-pin-price,.map-pin.is-hover .map-pin-price{background:var(--brand);color:var(--on-brand);
  border-color:var(--brand)}
.map-pin.is-active::after,.map-pin.is-hover::after{background:var(--brand);border-color:var(--brand)}
.map-pin:focus-visible{outline:0}
.map-pin:focus-visible .map-pin-price{outline:2px solid var(--brand);outline-offset:3px}

/* The card that opens off a pin — ours, in tokens, sat where a card belongs. */
.map-preview{position:absolute;left:12px;bottom:12px;z-index:5;width:min(300px,calc(100% - 24px));
  border:1px solid var(--border);border-radius:var(--radius);background:var(--surface);
  box-shadow:var(--shadow-3);overflow:hidden}
.map-preview[hidden]{display:none}
.map-preview-link{display:block;color:inherit}
.map-preview-shot{display:block;width:100%;aspect-ratio:16/9;object-fit:cover;background:var(--surface-muted)}
.map-preview-noshot{display:flex;align-items:center;justify-content:center;font-size:12px;
  color:var(--on-surface-subtle)}
.map-preview-body{display:flex;flex-direction:column;gap:4px;padding:12px 14px 14px}
.map-preview-ref{font-size:10px;letter-spacing:.14em;text-transform:uppercase;color:var(--on-surface-subtle)}
.map-preview-title{font-family:var(--font-display);font-size:15px;line-height:1.3;font-weight:600;
  display:-webkit-box;-webkit-line-clamp:2;-webkit-box-orient:vertical;overflow:hidden}
.map-preview-price{font-size:15px;font-weight:600;color:var(--brand)}
.map-preview-sale{font-style:normal;font-size:10px;font-weight:700;letter-spacing:.08em;text-transform:uppercase;
  margin-left:8px;padding:2px 7px;border-radius:var(--radius-pill);background:var(--status-complete);color:var(--on-brand)}
.map-preview-where{font-size:12px;color:var(--on-surface-muted)}
.map-preview-tags{display:flex;gap:6px;flex-wrap:wrap;margin-top:2px}
.map-preview-cta{font-size:12px;font-weight:600;color:var(--brand);margin-top:4px}
.map-preview-close{position:absolute;top:8px;right:8px;z-index:2;width:28px;height:28px;border-radius:50%;
  border:0;background:var(--surface);color:var(--on-surface);box-shadow:var(--shadow-1);cursor:pointer;
  font-size:18px;line-height:1;display:flex;align-items:center;justify-content:center}
.map-preview-close:hover{color:var(--brand)}

/* The browse: List | Map. In map view the results narrow to a column beside a map
   that stays put while the column scrolls, so a pin and its card are on screen together. */
.browse-head{display:flex;align-items:center;justify-content:space-between;gap:16px;flex-wrap:wrap;margin-top:20px}
.browse-head .count{margin-top:0}
.view-switch{display:inline-flex;padding:3px;border:1px solid var(--border);border-radius:var(--radius-pill);
  background:var(--surface-muted)}
.view-switch[hidden]{display:none}
.view-switch button{font:inherit;font-size:12px;font-weight:600;letter-spacing:.04em;padding:7px 16px;
  border:0;border-radius:var(--radius-pill);background:transparent;color:var(--on-surface-muted);cursor:pointer;
  display:inline-flex;align-items:center;gap:7px;transition:background-color .15s ease,color .15s ease}
.view-switch button:hover{color:var(--on-surface)}
.view-switch button.is-on{background:var(--surface);color:var(--brand);box-shadow:var(--shadow-1)}
.view-switch button:focus-visible{outline:2px solid var(--brand);outline-offset:2px}
.view-switch svg{width:14px;height:14px;fill:none;stroke:currentColor;stroke-width:1.8;stroke-linecap:round;stroke-linejoin:round}
.browse-body{position:relative}
.browse-map{display:none}
/* Wrapping the results in the browse breaks the band-follows-band rule that closes the
   gap between a count and its cards, so it is restated for what is inside and after. */
.browse-results .band:first-child,.browse-results .results > .band:first-child{padding-top:0}
.browse + .band{padding-top:0}
/* Map view: the body takes the page gutter itself, and the two columns give up theirs,
   so the cards keep their left margin and the map its right one. */
.browse[data-view="map"] > .band{padding-bottom:22px}
.browse[data-view="map"] .browse-body{display:grid;grid-template-columns:minmax(300px,0.72fr) minmax(0,1.28fr);gap:22px;
  align-items:start;max-width:var(--page-max);margin:0 auto;padding:0 clamp(20px,2.6vw,48px)}
.browse[data-view="map"] .browse-map{display:block;position:sticky;top:16px}
.browse[data-view="map"] .browse-body .band{padding:0}
.browse[data-view="map"] .browse-body .wrap{padding:0}
.browse[data-view="map"] .browse-results .grid{grid-template-columns:1fr;gap:12px;margin-top:16px}
.browse[data-view="map"] .browse-results .count{margin-top:4px}
.browse[data-view="map"] .browse-results{max-height:calc(100vh - 32px);overflow:auto;padding-right:4px;
  scrollbar-width:thin}
.tile.is-hover{border-color:var(--brand);box-shadow:var(--shadow-3)}
.tile.is-active{border-color:var(--brand);box-shadow:0 0 0 2px var(--brand),var(--shadow-3)}
.map-panel .map-canvas{aspect-ratio:auto;height:calc(100vh - 32px);min-height:420px}
.map-bar{display:flex;gap:14px;flex-wrap:wrap;align-items:center;justify-content:space-between;
  margin-bottom:10px;font-size:13px;color:var(--on-surface-muted);min-height:28px}
.map-status{flex:1 1 200px}
.map-status.is-warn{color:var(--status-attention)}
.map-status.is-quiet{color:var(--on-surface-subtle)}
.map-bar .map-link{font-size:12px}
.map-bar .map-link[hidden]{display:none}
.map-switch{display:inline-flex;align-items:center;gap:9px;cursor:pointer;user-select:none;font-size:12px;
  color:var(--on-surface-muted)}
.map-switch input{position:absolute;opacity:0;width:0;height:0}
.map-switch-track{width:34px;height:20px;border-radius:var(--radius-pill);background:var(--border-strong);
  position:relative;transition:background-color .15s ease;flex:0 0 auto}
.map-switch-track::after{content:"";position:absolute;top:2px;left:2px;width:16px;height:16px;border-radius:50%;
  background:var(--surface);box-shadow:var(--shadow-1);transition:transform .15s ease}
.map-switch input:checked + .map-switch-track{background:var(--brand)}
.map-switch input:checked + .map-switch-track::after{transform:translateX(14px)}
.map-switch input:focus-visible + .map-switch-track{outline:2px solid var(--brand);outline-offset:2px}
.map-float{position:absolute;top:12px;left:50%;transform:translateX(-50%);z-index:4;
  visibility:hidden;opacity:0;transition:opacity .15s ease}
.map-panel.has-moved .map-float{visibility:visible;opacity:1}
.map-panel.is-searching .map-float .btn{opacity:.7}
.map-float .btn{padding:9px 16px;font-size:12px;border-radius:var(--radius-pill);box-shadow:var(--shadow-3)}
.map-float .btn::before{content:"";display:inline-block;width:8px;height:8px;margin-right:8px;border-radius:50%;
  background:currentColor;opacity:.75}
.map-panel.is-searching .map-float .btn::before{animation:map-pulse 1s ease-in-out infinite}
@keyframes map-pulse{0%,100%{opacity:.3}50%{opacity:1}}
.map-panel.is-searching .map-canvas::after{content:"";position:absolute;inset:0;background:var(--surface);opacity:.15;
  pointer-events:none}

/* The workspace picker. */
.map-pick{aspect-ratio:16/9;min-height:240px;margin-top:8px}
.map-pick-row{display:flex;gap:12px;align-items:center;flex-wrap:wrap;margin-top:8px;font-size:12px;
  color:var(--on-surface-subtle)}
.map-pick-row .map-readout{font-variant-numeric:tabular-nums;color:var(--on-surface);
  padding:4px 8px;border:1px solid var(--border);border-radius:var(--radius-small);background:var(--surface-muted)}
.map-pick-row .btn{padding:6px 12px;font-size:12px}

/* The Filters button on the browse — after realtor.ca's Filters button — opens the
   site's own right-hand drawer (`.drawer`, `#filters`), the same panel every other act
   uses. The grid below is what the drawer holds: only attributes the property service
   filters on. */
/* In the brand colour, so it reads as the way in (the user, 26 September 2026:
   "colour the background of the filter button to make it obvious"). */
.filters .filters-more-button{flex:0 0 auto;flex-direction:row;align-items:center;gap:8px;
  padding:0 20px;font-size:13px;font-weight:600;color:var(--on-brand);background:var(--brand);white-space:nowrap;border-right:0}
.filters .filters-more-button:hover{filter:saturate(1.06) brightness(1.08);color:var(--on-brand)}
/* Focus comes BACK to this button when the drawer closes, and the bar's generic
   focused-field rule (`.f:focus-within` → brand-soft) would paint it pale under white
   text — found by the user, 26 September 2026. The button keeps its own colours and
   shows focus as a ring in the text colour, inset so it stays inside the bar. */
.filters .filters-more-button:focus,.filters .filters-more-button:focus-within{background:var(--brand);color:var(--on-brand)}
.filters .filters-more-button:focus-visible{outline:2px solid var(--on-brand);outline-offset:-5px}
.filters .filters-more-button .filters-more-count{background:var(--on-brand);color:var(--brand)}
/* The bar's label rule turns every span into 9px capitals; the button's word is a word. */
.filters .filters-more-button > span{font-size:13px;letter-spacing:.02em;text-transform:none;color:inherit;display:inline-flex;align-items:center;gap:8px}
.fm-radios{margin-bottom:16px}
.fm-radio-row{display:flex;gap:18px;flex-wrap:wrap;font-size:14px}
.fm-radio-row label{display:inline-flex;align-items:center;gap:7px;cursor:pointer}
.fm-radio-row input{accent-color:var(--brand)}
.filters-more-button svg{width:16px;height:16px;fill:none;stroke:currentColor;stroke-width:1.8;stroke-linecap:round}
.filters-more-count{display:inline-flex;align-items:center;justify-content:center;min-width:20px;height:20px;padding:0 6px;
  border-radius:var(--radius-pill);background:var(--brand);color:var(--on-brand);font-size:11px;font-weight:700;margin-left:2px}
.filters-more-grid{display:grid;grid-template-columns:repeat(2,minmax(0,1fr));gap:16px 18px}
.fm{display:flex;flex-direction:column;gap:6px;min-width:0}
.fm > span{font-size:10px;letter-spacing:.14em;text-transform:uppercase;color:var(--on-surface-subtle)}
.fm select{font-family:inherit;font-size:13px;padding:10px 30px 10px 11px;border:1px solid var(--border);border-radius:var(--radius-small);
  background:var(--surface);color:var(--on-surface);appearance:none;-webkit-appearance:none;min-width:0;width:100%}
.fm select:focus{outline:2px solid var(--brand);outline-offset:1px}
.fm-range{grid-column:span 2}
.fm-range-pair{display:flex;align-items:center;gap:8px}
.fm-dash{color:var(--on-surface-subtle)}
.filters-drawer .btn{width:auto;margin-top:0}
.filters-more-actions{display:flex;justify-content:flex-end;align-items:center;gap:18px;margin-top:22px;
  padding-top:16px;border-top:1px solid var(--border)}
.filters-more-reset{font-size:13px;color:var(--on-surface-muted);border-bottom:1px solid var(--border)}
.filters-more-reset:hover{color:var(--brand);border-bottom-color:var(--brand)}

/* The location box — "City, area, estate or reference", after realtor.ca. */
.place-bar{display:flex;gap:0;align-items:stretch;position:relative;margin-top:22px;
  border:1px solid var(--border-strong);border-radius:var(--radius-pill);background:var(--surface);
  box-shadow:var(--shadow-1);max-width:720px}
.place-bar-field{display:flex;align-items:center;gap:10px;flex:1 1 auto;padding:0 6px 0 18px;min-width:0}
.place-bar-icon{width:18px;height:18px;flex:0 0 auto;fill:none;stroke:var(--on-surface-subtle);stroke-width:1.6;
  stroke-linecap:round;stroke-linejoin:round}
.place-bar input{flex:1 1 auto;min-width:0;font:inherit;font-size:15px;padding:14px 0;border:0;background:transparent;
  color:var(--on-surface);outline:0}
.place-bar input::placeholder{color:var(--on-surface-subtle)}
.place-bar input::-webkit-search-cancel-button{-webkit-appearance:none}
.place-bar:focus-within{border-color:var(--brand);box-shadow:0 0 0 3px var(--brand-soft)}
.place-bar-go{margin:5px;border-radius:var(--radius-pill);padding:10px 18px;display:inline-flex;align-items:center;gap:8px;
  font-size:13px}
.place-bar-go svg{width:16px;height:16px;fill:none;stroke:currentColor;stroke-width:1.8;stroke-linecap:round}
.place-bar--hero{max-width:820px;margin:0 auto}
.place-bar--hero input{font-size:16px;padding:16px 0}
.place-band{padding:26px 0 8px}
.place-band-lede{text-align:center;font-size:13px;letter-spacing:.06em;text-transform:uppercase;
  color:var(--on-surface-subtle);margin:0 0 12px}
.place-suggest{position:absolute;left:0;right:0;top:calc(100% + 6px);z-index:30;max-height:min(60vh,420px);overflow:auto;
  border:1px solid var(--border);border-radius:var(--radius);background:var(--surface);box-shadow:var(--shadow-3);
  padding:6px 0;text-align:left}
.place-suggest[hidden]{display:none}
.place-suggest-title{font-size:10px;letter-spacing:.14em;text-transform:uppercase;color:var(--on-surface-subtle);
  padding:10px 16px 4px}
.place-suggest-item{display:flex;justify-content:space-between;gap:12px;align-items:baseline;padding:9px 16px;
  color:var(--on-surface);font-size:14px;cursor:pointer}
.place-suggest-item:hover,.place-suggest-item.is-active{background:var(--brand-soft);color:var(--on-brand-soft,var(--on-surface))}
.place-suggest-label{flex:1 1 auto;min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
.place-suggest-detail{flex:0 0 auto;font-size:12px;color:var(--on-surface-subtle)}
.place-suggest-empty{padding:12px 16px;font-size:13px;color:var(--on-surface-muted)}

/* A bubble: one count per area, after realtor.ca. */
.map-pin--bubble{transform:translate(-50%,-50%)}
.map-pin--bubble::after{display:none}
.map-pin-count{display:inline-flex;align-items:center;justify-content:center;min-width:38px;height:38px;padding:0 8px;
  border-radius:50%;background:var(--brand);color:var(--on-brand);font-size:13px;font-weight:700;
  box-shadow:0 0 0 4px color-mix(in srgb, var(--brand) 22%, transparent),var(--shadow-2);
  transition:transform .15s ease}
.map-pin--bubble.is-hover .map-pin-count,.map-pin--bubble.is-active .map-pin-count{transform:scale(1.12);
  box-shadow:0 0 0 6px color-mix(in srgb, var(--brand) 30%, transparent),var(--shadow-3)}
.map-pin--bubble:focus-visible .map-pin-count{outline:2px solid var(--on-surface);outline-offset:2px}

/* The panel beside a bubble: "N properties", scrolling, one row each. */
.map-panel-list{position:absolute;z-index:6;width:min(340px,calc(100% - 16px));max-height:380px;display:flex;flex-direction:column;
  border:1px solid var(--border);border-radius:var(--radius);background:var(--surface);box-shadow:var(--shadow-3);overflow:hidden}
.map-panel-list[hidden]{display:none}
.map-panel-head{display:flex;align-items:center;gap:10px;padding:10px 44px 10px 14px;border-bottom:1px solid var(--border);
  font-size:13px;position:relative;flex:0 0 auto}
.map-panel-head strong{flex:1 1 auto}
.map-panel-zoom{font:inherit;font-size:12px;font-weight:600;color:var(--brand);background:transparent;border:0;cursor:pointer;padding:0}
.map-panel-zoom:hover{text-decoration:underline}
.map-panel-body{overflow:auto;flex:1 1 auto;scrollbar-width:thin}
.map-panel-note{padding:14px;font-size:13px;color:var(--on-surface-muted);margin:0}

/* A listing as a row: the panel's shape and the list column's shape in map view. */
.map-row{display:grid;grid-template-columns:104px 1fr;gap:12px;padding:10px 12px;border-bottom:1px solid var(--border);
  color:var(--on-surface);align-items:center}
.map-row:last-child{border-bottom:0}
.map-row:hover{background:var(--brand-soft)}
.map-row-shot{width:104px;aspect-ratio:4/3;object-fit:cover;border-radius:var(--radius-small);background:var(--surface-muted);display:block}
.map-row-noshot{display:flex;align-items:center;justify-content:center;font-size:10px;letter-spacing:.06em;text-transform:uppercase;
  color:var(--on-surface-subtle);text-align:center;padding:4px}
.map-row-body{display:flex;flex-direction:column;gap:2px;min-width:0}
.map-row-price{font-size:15px;color:var(--brand)}
.map-row-title{font-size:13px;font-weight:600;line-height:1.3;display:-webkit-box;-webkit-line-clamp:2;-webkit-box-orient:vertical;overflow:hidden}
.map-row-facts,.map-row-where{font-size:12px;color:var(--on-surface-muted);white-space:nowrap;overflow:hidden;text-overflow:ellipsis}

/* Map view, after realtor.ca: a narrow list of rows on the left (the column widths are
   on .browse-body above), a wide map on the right. `>` so the card-geometry rule that
   counts `.tile .shot` definitions sees one; this is a layout of the row, not a second
   definition of the photograph. */
.browse[data-view="map"] .browse-results .tile{flex-direction:row;align-items:stretch}
.browse[data-view="map"] .browse-results .tile > .shot{flex:0 0 150px;aspect-ratio:auto;min-height:118px}
.browse[data-view="map"] .browse-results .tile .body{padding:12px 14px;gap:4px}
.browse[data-view="map"] .browse-results .tile h3{font-size:14px;-webkit-line-clamp:2}
.browse[data-view="map"] .browse-results .tile .price{font-size:15px}
.browse[data-view="map"] .browse-results .tile .tags{margin-top:auto}
.browse[data-view="map"] .browse-results .tile:hover{transform:none}
.browse[data-view="map"] .browse-results .pager{margin-top:14px}

/* The mobile menu sheet arriving. Declared here with the other keyframes and BEFORE the
   breakpoint section — `test/portal.test.js` insists nothing but breakpoints follows the
   first `@media`, because a base rule after one loses the cascade in silence. */
@keyframes navsheet{from{opacity:0;transform:translateY(-6px)}to{opacity:1;transform:none}}


@media (min-width:901px){ .nav-toggle{display:none} }
@media (prefers-reduced-motion: reduce){
  .slide{transition:none}
  /* The hamburger's bars and the menu sheet — added 12 September 2026 with both. */
  .nav-toggle span{transition:none}
  .nav.is-enhanced .nav-links.is-open{animation:none}
  /* The drawer arrives rather than slides. It still moves — it has to be somewhere it
     was not — but the 340ms travel across the screen is what somebody asking for less
     motion is asking not to have. */
  .drawer{transition:none}
  .tile,.card,.step,.feature .shot,.feature .shot::after,.feature .shot img,
  .tile .shot img,.ladder li,.ladder .rung,.arrow,.theme-chip,.filters .f,
  a.btn, button.btn, .btn{transition:none}
  .tile:hover,.btn:hover{transform:none}
  .tile:hover .shot img,.feature:hover .shot img{transform:none}
  /* The viewer is a modal that appears over the whole screen. Someone who has asked
     for less motion gets it plainly, with no zoom on the thumbnails behind it. */
  .gallery-strip img,.plan,.viewer-btn{transition:none}
  .gallery-strip a:hover img{transform:none}
  .plan:hover{transform:none}
}
@media (prefers-color-scheme: dark){
  :root:not([data-theme="light"]) .logo-on-light{display:none}
  :root:not([data-theme="light"]) .logo-on-dark{display:block}
}
@media (max-width:900px){ .entry-grid{grid-template-columns:repeat(2,1fr)} }
@media (max-width:900px){
  /* Collapsed only once nav.js has taken over. Without it the links stay visible —
     a collapsed menu with no script to open it is a site with no navigation. */
  /* THE MENU ON A PHONE — rebuilt 12 September 2026, reported from an iPhone.

     It was the desktop row turned on its side: every link the same weight, a hairline
     under each including the last, 12px of padding against a 44px tap target, and the
     phone number and Sign in landing in the middle of the list as though they were two
     more pages. It worked and read as a placeholder.

     It is now a SHEET: it sits under the header, owns the width, and separates the six
     places you can go from the two things you can do. Still the same markup and the same
     `.is-open` class — nothing here needs nav.js to learn anything new. */
  .nav-links{flex-direction:column;align-items:stretch;gap:0;
             width:100%;order:3;margin-top:10px;padding:8px;
             /* A sheet inside the gutter rather than a bleed to the window edges. The
                bleed wanted `.wrap`'s padding as a token to cancel, and `.wrap` lays out
                78 views — not a rule to re-shape for a menu. This reads as a surface
                without touching anything outside the header. */
             background:var(--surface-muted);border:1px solid var(--border);
             border-radius:var(--radius);box-shadow:var(--shadow-1)}
  .nav.is-enhanced .nav-links{display:none}
  .nav.is-enhanced .nav-links.is-open{display:flex;animation:navsheet .16s ease-out}

  /* 48px minimum, which is the tap target this list never had. The rule is BETWEEN
     items, not under every one — a line under the last item is a line under nothing. */
  .nav-links a:not(.btn){width:100%;min-height:48px;display:flex;align-items:center;
                         padding:12px 14px;border-bottom:0;border-radius:var(--radius-small);
                         font-size:16px}
  .nav-links a:not(.btn) + a:not(.btn){box-shadow:inset 0 1px 0 var(--border)}

  /* Where you are, marked by a rail rather than by an underline. An inset underline on a
     full-width row reads as a divider, which is exactly what sits between these rows. */
  .nav-links a:not(.btn).is-here{background:var(--surface-muted);
    box-shadow:inset 3px 0 0 var(--brand);font-weight:600}
  .nav-links a:not(.btn).is-here + a:not(.btn){box-shadow:inset 0 1px 0 var(--border)}

  /* WHAT YOU CAN DO, kept apart from where you can go. The gap does the separating and
     the top rule closes it off; the phone number stops being the seventh page. */
  .nav-gap{margin-left:0;height:1px;background:var(--gradient-rule);margin-top:12px}
  .nav-links .nav-phone{min-height:48px;padding:12px 14px;font-size:16px}
  .nav-links a.btn{width:100%;justify-content:center;margin-top:10px;border-bottom:0;
                   min-height:48px;font-size:16px}
  .brand-logo{height:44px}
}
@media (max-width:960px){ .grid{grid-template-columns:repeat(2,1fr)} .detail{grid-template-columns:minmax(0,1fr)}
  .detail > .facts{position:static} .spec-table{grid-template-columns:1fr}
  .amenities-panel{padding:18px} }
/* ~~A fourth card column above 1500px.~~ Removed 19 August 2026. It was the only
   min-width rule in the sheet and was justified as adding a column "once there is room
   for a fourth card at a size worth looking at" — which is not what it did. `.wrap` caps
   at 1664px, so a fourth column cannot make cards bigger, only thinner: at a 1499px
   viewport a card was 459px, at 1500px it became 339px, and no width above that ever
   took it past 400px. Widening the window shrank the photograph by a quarter and never
   gave it back.

   The property grid is where that hurt. A card is mostly a photograph of a house, and at
   400px it reads as a fragment of a façade rather than a building. Three columns grow
   smoothly to 540px instead of snapping backwards.

   The invariant this violated is now asserted in test/portal.test.js: card width must
   never DECREASE as the viewport grows. That is the rule a future fourth column has to
   satisfy, and a wider `.wrap` would be the way to earn it. */
@media (min-width:1500px){ .entry-grid{gap:20px} }
/* Below this a large drawer covers more than it leaves behind, which is the point at
   which it should have been a page. It becomes the small one instead. */
@media (max-width:900px){ .drawer.medium,.drawer.large{width:min(460px,100%)} }
@media (max-width:1080px){ .hero-note{display:none} }
/* Below the text column's width the stage word and the headline fight for the same
   space, and the headline wins — it is the sentence. The plate itself STAYS: it is the
   slide's ground, not an ornament on it, and hiding it would leave the page's own bone
   colour behind a scrim built for a photograph. The note moves to the top, where the
   headline is not, so the page still says the picture is missing. */
/* Full width on a phone: a 460px panel on a 390px screen is a panel with a sliver of
   unusable page beside it, and the sliver is what people tap by mistake. */
@media (max-width:520px){ .drawer{width:100%;border-left:0} }
@media (max-width:900px){ .slide-plate{justify-content:flex-start;padding-top:22px}
                          .slide-plate .plate-mark{display:none} }
@media (max-width:820px){ .acct{grid-template-columns:1fr} .row2{grid-template-columns:1fr} }
@media (max-width:900px){ .form-page{grid-template-columns:1fr;gap:30px;
                          padding-top:36px;padding-bottom:70px} }
/* Two across, so the vertical hairlines have to become horizontal ones or the band
   reads as four cells with three rules in the wrong places. */
@media (max-width:820px){ .stats{grid-template-columns:repeat(2,1fr)}
                          .stats div{border-top:1px solid
                             color-mix(in srgb,var(--on-gradient) 15%,transparent)}
                          .stats div:nth-child(-n+2){border-top:0}
                          .stats div:nth-child(odd){border-left:0} }
@media (max-width:520px){ .stats{grid-template-columns:1fr}
                          .stats div{padding:18px 14px;border-left:0}
                          .stats div:first-child{border-top:0}
                          .stats .n{font-size:26px} }
@media (max-width:820px){ .filters{flex-direction:column}
                          .filters .f{border-right:0;border-bottom:1px solid var(--border);
                                      flex:1 1 auto}
                          .filters .go{border-bottom:0} }
@media (max-width:720px){ .fork{grid-template-columns:1fr}
                          .subscribe{grid-template-columns:1fr;padding:28px}
                          .ladder li{gap:14px;padding:18px 10px}
                          .features{grid-template-columns:1fr;gap:30px}
                          .pull{font-size:17px;padding:22px 22px 22px 26px}
                          .qa{grid-template-columns:1fr;gap:0}
                          .qa dt{padding-bottom:6px}
                          .qa dd{padding-top:0;border-top:0}
                          .steps{grid-template-columns:1fr} }
@media (max-width:640px){ .grid{grid-template-columns:1fr}
                          .entry-grid{grid-template-columns:1fr}
                          .wrap{padding:0 16px}
                          .foot::before{left:16px;right:16px}
                          .section-head{margin-top:48px}
                          /* Still clear of the 40px feather, so the first line of a
                             band is on settled colour on a phone as well. */
                          .band{padding:42px 0}
                          body{background-size:100% 480px,100% 300px}
                          .qa dd,.prose,.lede{max-width:none} }
@media (max-width:640px){ .hero-full{min-height:64vh}
                          .slide .inner{padding-bottom:76px} }
@media (max-width:640px){
  .theme-switch{left:8px;right:8px;bottom:8px;justify-content:center;
                padding:8px 10px;gap:5px}
  .theme-switch-note{display:none}
  .theme-chip{font-size:10px;padding:4px 9px}
}
/* On a phone the two arrows in the row cost 100px of a 390px screen, and the
   photograph — the reason the viewer exists — gets what is left. They move onto the
   image instead, inset from the edge so neither sits under a thumb resting on the
   glass. Swiping does the same job and is what most people will reach for. */
@media (max-width:640px){
  .viewer{padding:8px}
  .viewer-stage{gap:0}
  .viewer-nav{position:absolute;bottom:6px;z-index:1}
  .viewer-prev{left:6px}
  .viewer-next{right:6px}
  .viewer-frame{width:100%}
  .gallery-strip{grid-template-columns:repeat(auto-fill,minmax(88px,1fr));gap:8px}
  .plan-list{grid-template-columns:1fr}
}

/* Hover is a pointer capability, not a screen size. Without this the appearance
   control keeps its hover fill after a tap on a phone, so two of the three segments
   look chosen at once — which is precisely what a segmented control must never do. */

@media (hover:hover){
  .foot .appearance-opt:hover{color:var(--on-surface);
        background:color-mix(in srgb,var(--brand) 8%,transparent)}
}

/* Stacked, and full width, so the control is not a row of 11px targets squeezed
   against the copyright line on a 390px screen.

   `min-height:44px` because padding alone gave a 23px target — under half the 44px
   minimum, and this is a control a person uses with a thumb. Measured rather than
   assumed: the text is 11.5px on a line-height of 1, so the padding has to do all the
   work and 9px each side was not close. */
@media (max-width:560px){
  .foot-top{flex-direction:column;align-items:flex-start;gap:14px}
  .appearance{flex-direction:column;align-items:flex-start;gap:7px}
  .appearance-set{width:100%;justify-content:space-between}
  .appearance-opt{flex:1 1 0;justify-content:center;padding:0 8px;min-height:44px;
        font-size:12.5px}
}

@media (max-width:480px){
  .assistant-launcher{right:12px;bottom:12px;padding:10px 15px}
  .assistant-panel{right:12px;left:12px;bottom:62px;width:auto}
}

/* ---- /trustees ------------------------------------------------------------------
   Two columns become one at 1000px rather than at the site's usual 900px: the hero
   panel and the firm's fact panel both have a floor near 300px, and between 900 and
   1000 the column beside them is narrower than the paragraph it has to hold. */
@media (max-width:1180px){
  /* Two across, before three become too narrow to read a firm's opening statement in.
     Stated rather than left to `auto-fill`, so the count changes with the design and not
     with whatever width the window happens to be. */
  .firmlist{grid-template-columns:repeat(2,minmax(0,1fr))}
  /* Three, not two. The service cards hold a name and about thirty words each, so they
     stay readable a good deal narrower than a firm card does — dropping them to two here
     would leave two very wide cards and a third stranded on its own row. */
  .svcgrid{grid-template-columns:repeat(3,minmax(0,1fr))}
}

@media (max-width:1000px){
  .trust-hero{grid-template-columns:minmax(0,1fr)}
  .svcgrid{grid-template-columns:repeat(2,minmax(0,1fr))}
  /* Not full width once it is under the lede rather than beside it. A rail of three
     short steps stretched across a 960px page reads as a table of contents, not as a
     diagram of a route. */
  .custody-panel{max-width:520px}
  .trust-hero .lede{max-width:58ch}
  /* Back to the stacked interior. `display:flex` is what turns the grid areas off —
     with no grid, `grid-area` is inert and the source order is the layout. */
  .trust-close{grid-template-columns:minmax(0,1fr);gap:44px}
}

@media (max-width:640px){
  .trust-hero-band{padding:34px 0 30px}
  /* One. Two columns on a phone is two columns of broken words. */
  .firmlist{grid-template-columns:minmax(0,1fr)}
  .svcgrid{grid-template-columns:minmax(0,1fr)}
  .firmcard{padding:20px 18px 22px}
  /* The rail and its indent survive on a phone — it is the only thing marking the
     custody service once the cards are one per row and there is nothing beside them to
     compare against. Only the breathing room goes. */
  .svcitem{padding-left:11px}
  .custody-panel{padding:20px 18px 18px}
  /* The count sits under the heading on a phone rather than beside it, where
     `space-between` would push it to the far right of a wrapped row and leave it
     looking like a stray fragment of the sentence above it. */
  .section-count{margin-top:10px}
}

/* The authenticator enrolment, stacked: on a phone the key matters more than the QR,
   because the phone is the thing that would have done the scanning. */
@media (max-width:720px){
    .enrol{grid-template-columns:minmax(0,1fr)}
    .enrol-qr{order:2}
}

/* maps — the phone: the map above the list, its own height, the switch full width */
@media (max-width:900px){
  .browse[data-view="map"] .browse-body{display:flex;flex-direction:column}
  .browse[data-view="map"] .browse-map{position:static;order:-1;margin-bottom:18px}
  .browse[data-view="map"] .browse-results{max-height:none;overflow:visible;padding-right:0}
  .browse[data-view="map"] .browse-results .grid{grid-template-columns:1fr}
  .map-panel .map-canvas{height:62vh;min-height:360px}
  .map-preview{left:8px;right:8px;bottom:8px;width:auto}
  .map-preview-link{display:grid;grid-template-columns:96px 1fr}
  .map-preview-shot{height:100%;aspect-ratio:auto}
  .map-preview-title{-webkit-line-clamp:1}
}
@media (prefers-reduced-motion: reduce){
  .map-pin-price,.view-switch button,.map-switch-track,.map-switch-track::after,.map-float{transition:none}
  .map-panel.is-searching .map-float .btn::before{animation:none}
}
@media (max-width:640px){
  .place-bar{flex-wrap:nowrap}
  .place-bar-go span{display:none}
  .place-bar-go{padding:10px 14px}
  .browse[data-view="map"] .browse-results .tile > .shot{flex-basis:110px}
  .map-panel-list{left:8px !important;right:8px;top:auto !important;bottom:8px;width:auto;max-height:52%}
}
@media (max-width:640px){
  .filters-more-grid{grid-template-columns:1fr}
  .fm-range{grid-column:auto}
  .filters .filters-more-button{flex:1 1 100%;justify-content:center;padding:12px}
}
