/* Holdfast Stays
   Ground is paper. The petrol that used to be the ground is now the ink, so
   the site keeps its own colour without carrying it as darkness.

   Changed 2026-08-21, at Pierce's second asking. The dark build is in
   ~/.claude/backups/holdfast-site-predark-to-light-20260821.tar.gz and the
   reasoning for it is still at the foot of DESIGN.md - it was a real argument
   (24 Florida houses blaze against a dark ground) and it lost to the owner of
   the business, twice, which is the correct way for it to lose.

   What this is NOT is the warm beige-and-brown every generated site ships.
   That was the trap the dark ground was avoiding and it is still a trap. The
   ground here is a cool paper carrying a whisper of the same petrol hue as the
   ink (chroma 0.003 at hue 205), so white and near-white photographs sit on it
   without disappearing, and every rule on the page is a hairline of the same
   family. Air comes from space and hairlines, not from beige.

   See DESIGN.md for what is deliberately absent. */

:root{
  /* Ground, in three steps. --paper is the page, --paper-2 is anything
     recessed (an image while it loads, a form field), --rule is every
     hairline. On a light ground the rule has to be a real value rather than a
     transparency, or it dissolves over a photograph. */
  --paper:     #FAF4EE;
  --paper-2:   #F6F0E7;
  --rule:      #DCD2C4;

  /* Ink, 2026-08-24. The Holdfast Holdings palette has no colour dark enough
     to set text in: its darkest, #766B5F, measures 4.76:1 on the paper and
     nothing else clears 2.4:1. So --ink is that taupe taken down to #3E3831,
     which measures 10.6:1 and stays in the same warm family rather than
     falling back to a black. --ink-2 is the palette taupe unchanged, used
     only for secondary text on --paper where its 4.76:1 still passes. */
  --ink:       #3E3831;
  --ink-2:     #766B5F;

  /* The orange is gone, 2026-08-24, at Pierce's instruction. The tan that
     replaces it inherits the identical constraint, which is worth writing
     down because it is not obvious: #C39D6B measures 2.30:1 on the paper and
     the orange it replaced measured 2.30:1. They fail the same way. So --signal
     is a FILL only, --signal-deep is the tan taken down to 4.77:1 and is what
     every accent WORD, rule and counter uses, and --signal-ink is the text
     colour that sits ON a tan button at 4.60:1. Never white on tan: 2.51:1. */
  --signal:     #C39D6B;
  --signal-lo:  #B08A57;
  --signal-deep:#8A6637;
  --signal-ink: #3E3831;
  --danger:     oklch(0.50 0.17 25);
  --sand:       #E5DAC6;
  --sage:       #BBB7A4;

  /* Photographs need an edge on a light ground. On the dark build a picture
     ended where the darkness began; on paper a bright kitchen bleeds into the
     page. One hairline-soft shadow, tinted with the ink rather than neutral
     black, does the whole job - it is what makes the photographs look placed
     rather than pasted. */
  --lift: 0 1px 2px rgb(62 56 49 / .08), 0 10px 34px rgb(62 56 49 / .09);
  --lift-hi: 0 2px 4px rgb(62 56 49 / .10), 0 18px 50px rgb(62 56 49 / .13);

  --display:"Young Serif",Georgia,serif;
  --body:"Archivo",system-ui,-apple-system,sans-serif;

  --pad:clamp(20px,5vw,80px);
  --ease:cubic-bezier(.16,.84,.3,1);

  --z-bar:10; --z-overlay:20;
}

*{box-sizing:border-box;margin:0;padding:0}
html{-webkit-text-size-adjust:100%}
body{
  background:var(--paper);color:var(--ink);
  font-family:var(--body);font-size:17px;line-height:1.65;
  /* No -webkit-font-smoothing here. It was correct on the dark build - light
     type on a dark ground blooms and antialiased thins it back - and it is
     wrong now: on dark-on-light it thins the type until the page looks faint,
     which is the opposite of what a light design needs. */
  overflow-x:hidden;
}
img{display:block;max-width:100%}
a{color:inherit}

/* ------------------------------------------------- the surfaces we did draw
   Selection, caret, focus ring, scrollbar and numerals ship with browser
   defaults that belong to no design system. The default blue selection is the
   one thing on the page that is not ours. */
::selection{background:var(--signal);color:var(--signal-ink)}
:root{color-scheme:light;scrollbar-color:var(--rule) var(--paper)}
input,textarea,select{caret-color:var(--signal)}
::-webkit-scrollbar{width:11px;height:11px}
::-webkit-scrollbar-track{background:var(--paper)}
::-webkit-scrollbar-thumb{background:var(--rule);border:3px solid var(--paper);border-radius:6px}
::-webkit-scrollbar-thumb:hover{background:var(--ink-2)}
:focus-visible{outline:2px solid var(--signal);outline-offset:3px;border-radius:2px}
:where(a,button,summary):focus{outline:none}
/* Anchored sections land under the sticky bar without this. */
:target,[id]{scroll-margin-top:86px}
.prose a{text-decoration-thickness:1px;text-underline-offset:3px;text-decoration-color:var(--signal-deep)}

/* ---------------------------------------------------------------- top bar */
.bar{
  position:sticky;top:0;z-index:var(--z-bar);
  display:flex;align-items:center;gap:clamp(16px,4vw,48px);
  padding:16px var(--pad);
  background:color-mix(in oklch,var(--paper) 82%,transparent);
  backdrop-filter:blur(14px) saturate(1.4);
  border-bottom:1px solid var(--rule);
}
.mark{font-family:var(--display);font-size:22px;text-decoration:none;letter-spacing:-.01em}
.bar nav{display:flex;gap:clamp(14px,2.4vw,30px);margin-left:auto}
.bar nav a{color:var(--ink-2);text-decoration:none;font-size:15px}
.bar nav a:hover{color:var(--ink)}

/* ----------------------------------------------------------------- buttons */
.btn{
  display:inline-block;text-decoration:none;font-size:15px;font-weight:500;
  padding:12px 22px;border-radius:2px;transition:background .18s var(--ease),color .18s var(--ease);
}
.btn-signal{background:var(--signal);color:var(--signal-ink)}
.btn-signal:hover{background:var(--signal-lo)}
.btn-ghost{border:1px solid var(--rule);color:var(--ink)}
.btn-ghost:hover{border-color:var(--ink-2)}
.btn-lg{font-size:17px;padding:16px 30px}

/* -------------------------------------------------------------------- hero
   Asymmetric on purpose. The display column is wider than the type column,
   and the type column starts lower, so the fold is not a centred stack. */
.hero{
  padding:clamp(56px,10vw,132px) var(--pad) clamp(36px,6vw,64px);
  display:grid;gap:clamp(24px,4vw,64px);
  grid-template-columns:minmax(0,1.35fr) minmax(0,1fr);
  align-items:end;
}
.hero h1{
  font-family:var(--display);font-weight:400;
  font-size:clamp(44px,7.4vw,94px);line-height:1.02;letter-spacing:-.025em;
  text-wrap:balance;
}
.hero h1 .l{display:block;overflow:hidden}
.hero h1 .l>span{display:block}
.hero-side{padding-bottom:clamp(4px,1vw,14px)}
.lede{color:var(--ink-2);max-width:44ch;font-size:clamp(16px,1.15vw,18px);line-height:1.7}
.acts{display:flex;flex-wrap:wrap;gap:12px;margin-top:26px}

/* ------------------------------------------------------------- hero plate */
.plate{position:relative;margin:0 var(--pad)}
.plate img{width:100%;height:clamp(280px,58vh,640px);object-fit:cover;border-radius:3px;box-shadow:var(--lift)}
.plate figcaption{
  position:absolute;left:18px;bottom:14px;
  font-size:13px;color:#fff;text-shadow:0 2px 18px rgba(0,0,0,.7);
}

/* ------------------------------------------------------------------- split */
.split{
  display:grid;gap:clamp(28px,5vw,72px);
  grid-template-columns:minmax(0,.85fr) minmax(0,1.15fr);
  align-items:start;
  padding:clamp(64px,11vw,160px) var(--pad) clamp(40px,7vw,90px);
}
.split-type h2{
  font-family:var(--display);font-weight:400;
  font-size:clamp(26px,3.1vw,42px);line-height:1.16;letter-spacing:-.02em;
  margin-bottom:18px;text-wrap:balance;
}
.split-type p{color:var(--ink-2);max-width:58ch;margin-bottom:14px;text-wrap:pretty}
.terms{margin:30px 0;border-top:1px solid var(--rule)}
.terms>div{
  display:flex;justify-content:space-between;gap:20px;align-items:baseline;
  padding:13px 0;border-bottom:1px solid var(--rule);
}
.terms dt{color:var(--ink-2);font-size:15px}
.terms dd{text-align:right;font-weight:500}
/* Four identical rectangles in a 2x2 is the contact sheet, not a composition.
   One frame leads and the others answer it, which is also how the houses are
   actually looked at. */
.split-band{
  display:grid;grid-template-columns:1fr 1fr;
  grid-auto-rows:clamp(104px,13vw,168px);gap:10px;
}
.split-band img{width:100%;height:100%;object-fit:cover;border-radius:3px;box-shadow:var(--lift)}
.split-band img:nth-child(1){grid-column:1/-1;grid-row:span 2}
.split-band img:nth-child(2){grid-row:span 2}
.split-band img:nth-child(3){grid-row:span 2}
.split-band img:nth-child(4){grid-column:1/-1;grid-row:span 1}

/* --------------------------------------------------------------------- band
   The hero, the split and the tally all run title against content across the
   full width. Four sections underneath them did not, so they read as a narrow
   column of text with half a screen of nothing beside it. Same two-column
   ratio as .split, so the page keeps one rhythm the whole way down. */
.band{
  display:grid;gap:clamp(20px,4vw,64px);
  grid-template-columns:minmax(0,.85fr) minmax(0,1.15fr);align-items:start;
}
.band>h2{margin:0}
.band-body{min-width:0}
@media (max-width:900px){
  .band{grid-template-columns:1fr;gap:20px}
}

/* ------------------------------------------------------------------ ledger
   Numbers as a record rather than four big figures in a row. */
.where{padding:clamp(30px,6vw,80px) var(--pad)}
.where h2{
  font-family:var(--display);font-weight:400;
  font-size:clamp(24px,2.6vw,34px);letter-spacing:-.02em;line-height:1.12;
}
.ledger{width:100%;max-width:760px;border-collapse:collapse;font-size:16px}
.ledger caption{text-align:left;color:var(--ink-2);font-size:15px;padding-bottom:12px}
.ledger th,.ledger td{padding:12px 0;border-bottom:1px solid var(--rule);text-align:left;font-weight:400}
.ledger th{color:var(--ink)}
.ledger td{color:var(--ink-2);text-align:right}
.ledger tfoot th,.ledger tfoot td{border-bottom:none;padding-top:16px;color:var(--ink);font-weight:500}

/* ------------------------------------------------------------------ houses */
.houses{padding:clamp(50px,8vw,110px) var(--pad) clamp(30px,5vw,60px)}
.houses-head{display:flex;flex-wrap:wrap;gap:14px;align-items:baseline;justify-content:space-between;margin-bottom:32px}
.houses-head h2{font-family:var(--display);font-weight:400;font-size:clamp(30px,4.4vw,58px);letter-spacing:-.025em}
.houses-head p{color:var(--ink-2)}
.grid{display:grid;grid-template-columns:repeat(auto-fit,minmax(280px,1fr));gap:clamp(14px,1.6vw,24px)}
/* The featured row is a fixed three, not auto-fit. Auto-fit puts four or five
   across a wide screen and six houses then land as 4+2, which reads as a grid
   that ran out rather than a selection. */
.grid-feature{grid-template-columns:repeat(3,1fr)}
.feature-all{margin-top:clamp(26px,3.5vw,44px)}
@media (max-width:900px){.grid-feature{grid-template-columns:repeat(2,1fr)}}
@media (max-width:560px){.grid-feature{grid-template-columns:1fr}}
.house{text-decoration:none;display:block}
.house-img{overflow:hidden;border-radius:3px;background:var(--paper-2);box-shadow:var(--lift);transition:box-shadow .4s var(--ease)}
.house-img img{
  width:100%;height:clamp(200px,22vw,280px);object-fit:cover;
  transition:transform .6s var(--ease),filter .4s var(--ease);
  /* The dark build held the photographs back a little so they did not shout
     against the ground. On paper the ground is doing none of that work and a
     held-back photograph just looks washed, so they run at full strength. */
  filter:saturate(1);
}
.house:hover .house-img{box-shadow:var(--lift-hi)}
.house:hover .house-img img{transform:scale(1.04);filter:saturate(1.04)}
.house-meta{padding:13px 2px 0}
.house-meta h3{font-family:var(--display);font-weight:400;font-size:20px;letter-spacing:-.01em}
.house-meta .house-where{color:var(--ink-2);font-size:15px;margin-top:2px}
.house-meta .spec{color:var(--ink-2);font-size:15px;opacity:.85;margin-top:1px}

/* ------------------------------------------------------------------ closer */
.closer{
  padding:clamp(70px,12vw,170px) var(--pad);
  border-top:1px solid var(--rule);margin-top:clamp(40px,7vw,90px);
}
.closer h2{
  font-family:var(--display);font-weight:400;
  font-size:clamp(32px,5.6vw,74px);letter-spacing:-.03em;line-height:1.05;
  max-width:16ch;text-wrap:balance;
}
.closer p{color:var(--ink-2);max-width:56ch;margin:18px 0 30px;text-wrap:pretty}

/* Two orphan lines read as a page that ran out rather than one that ended. */
.foot{
  display:grid;gap:clamp(18px,3vw,34px);align-items:baseline;
  grid-template-columns:minmax(0,.85fr) minmax(0,1.15fr);
  padding:clamp(34px,5vw,56px) var(--pad) clamp(28px,4vw,44px);
  border-top:1px solid var(--rule);
  color:var(--ink-2);font-size:15px;
}
.foot-mark{font-family:var(--display);font-size:20px;color:var(--ink);letter-spacing:-.01em}
.foot-nav{display:flex;flex-wrap:wrap;gap:8px clamp(16px,2.4vw,30px)}
.foot-nav a{color:var(--ink-2);text-decoration:none}
.foot-nav a:hover{color:var(--ink)}
.foot-fine{grid-column:1/-1;opacity:.86;font-size:13px}
.foot a{color:var(--ink-2)}
@media (max-width:900px){
  .foot{grid-template-columns:1fr;gap:16px}
}

/* ------------------------------------------------------------------ motion
   Additive. Without JS the html element has no .motion class and everything
   is simply visible, which is how it must be: a reveal that never fires
   ships a blank page. */
.motion .hero h1 .l>span{transform:translateY(104%);animation:rise .95s var(--ease) var(--d,0s) forwards}
@keyframes rise{to{transform:translateY(0)}}
.motion .hero-side{opacity:0;animation:fade .8s ease .34s forwards}
@keyframes fade{to{opacity:1}}
.motion .plate img{opacity:0;transform:scale(.985);animation:plate 1.1s var(--ease) .2s forwards}
@keyframes plate{to{opacity:1;transform:scale(1)}}
.motion .reveal{opacity:0;transform:translateY(14px);transition:opacity .7s var(--ease),transform .7s var(--ease)}
.motion .reveal.in{opacity:1;transform:none}

@media (prefers-reduced-motion:reduce){
  .motion .hero h1 .l>span,.motion .hero-side,.motion .plate img,.motion .reveal{
    animation:none!important;transition:none!important;opacity:1!important;transform:none!important}
  .house-img img{transition:none}
}

/* ------------------------------------------------------------- responsive */
@media (max-width:900px){
  .hero{grid-template-columns:1fr;align-items:start;gap:22px}
  .split{grid-template-columns:1fr}
  .bar nav{display:none}
}
@media (max-width:520px){
  body{font-size:16px}
  .split-band{grid-template-columns:1fr 1fr}
  .terms>div{flex-direction:column;gap:2px}
  .terms dd{text-align:left}
}

/* ------------------------------------------------------------ house pages */
.house-page{padding-bottom:20px}
.ph-head{
  display:flex;flex-wrap:wrap;gap:16px;align-items:baseline;justify-content:space-between;
  padding:clamp(40px,7vw,88px) var(--pad) clamp(20px,3vw,34px);
}
.ph-head h1{
  font-family:var(--display);font-weight:400;
  font-size:clamp(34px,6vw,76px);letter-spacing:-.028em;line-height:1.03;text-wrap:balance;
}
.ph-where{color:var(--ink-2);margin-top:6px}
.ph-spec{color:var(--ink-2);font-size:15px}
/* The old page ran one narrow column of grey text down the left and left the
   right half of a 1440 viewport empty. The facts an owner or a guest actually
   scans for belong in that space. */
.ph-body{
  display:grid;gap:clamp(28px,5vw,72px);
  grid-template-columns:minmax(0,1.4fr) minmax(0,.7fr);align-items:start;
  padding:clamp(34px,6vw,72px) var(--pad) clamp(24px,4vw,44px);
}
.ph-lede{max-width:66ch}
.ph-lede .btn{margin-top:26px}
.ph-facts{border-top:1px solid var(--rule)}
.ph-facts>div{
  display:flex;justify-content:space-between;gap:18px;align-items:baseline;
  padding:11px 0;border-bottom:1px solid var(--rule);
}
.ph-facts dt{color:var(--ink-2);font-size:15px}
.ph-facts dd{text-align:right;font-weight:500;font-variant-numeric:tabular-nums}
/* Declared here rather than in the shared responsive block: .ph-body is
   defined further down the file, so an earlier media query loses on source
   order and the facts column ran off the side of a phone. */
@media (max-width:900px){
  .ph-body{grid-template-columns:1fr;gap:26px}
}
.prose p{color:var(--ink-2);margin-bottom:14px;max-width:68ch;text-wrap:pretty}
.prose p:last-child{margin-bottom:0}

/* Owner-written listing copy, given its structure back. */
.ph-sub{
  font-family:var(--display);font-weight:400;font-size:clamp(17px,1.5vw,20px);
  letter-spacing:-.012em;color:var(--ink);margin:26px 0 12px;
}
.ph-sub:first-child{margin-top:0}
.pl{list-style:none;margin:0 0 18px;max-width:68ch;display:grid;gap:7px}
.pl li{
  color:var(--ink-2);padding-left:18px;position:relative;text-wrap:pretty;
}
.pl li::before{
  content:"";position:absolute;left:0;top:.68em;
  width:8px;height:1px;background:var(--ink-2);opacity:.7;
}
.pl .k{color:var(--ink);display:inline-block;min-width:9.5em;padding-right:10px}
@media (max-width:640px){
  .pl .k{display:block;min-width:0;padding-right:0}
}

.detail{border-top:1px solid var(--rule)}
.detail summary{
  display:flex;align-items:center;justify-content:space-between;gap:20px;
  padding:clamp(18px,2.4vw,26px) var(--pad);cursor:pointer;list-style:none;
}
.detail summary::-webkit-details-marker{display:none}
.detail summary h2{
  font-family:var(--display);font-weight:400;
  font-size:clamp(19px,1.9vw,25px);letter-spacing:-.015em;
}
.detail .chev{
  width:11px;height:11px;flex:none;border-right:1.5px solid var(--ink-2);
  border-bottom:1.5px solid var(--ink-2);transform:rotate(45deg) translateY(-3px);
  transition:transform .25s var(--ease);
}
.detail[open] .chev{transform:rotate(225deg) translateY(-3px)}
.detail summary:hover h2{color:var(--signal-deep)}
.detail .prose{padding:0 var(--pad) clamp(24px,3.4vw,40px);max-width:78ch}
/* A uniform grid of identical crops flattens a wide living room and a close
   shot of a coffee table into the same object. The six-column cycle gives the
   set a rhythm and lets the wide frames stay wide. */
.gallery{
  display:grid;grid-template-columns:repeat(6,1fr);
  gap:clamp(8px,1vw,14px);padding:clamp(24px,4vw,52px) var(--pad);
}
.gallery figure{overflow:hidden;border-radius:3px;background:var(--paper-2);box-shadow:var(--lift)}
.gallery img{
  width:100%;height:100%;object-fit:cover;
  transition:transform .8s var(--ease);
}
.gallery figure:hover img{transform:scale(1.03)}
/* Fixed row heights rather than per-figure aspect ratios: mixed ratios inside
   one row leave the short frame floating with a hole under it. The cycle of
   five tiles exactly, so no row is ever left part-empty. */
.gallery{grid-auto-rows:clamp(116px,12.8vw,182px)}
.gallery figure{grid-column:span 2;grid-row:span 1}
.gallery figure:nth-child(5n+1){grid-column:span 4;grid-row:span 2}
.gallery figure:nth-child(5n+4),
.gallery figure:nth-child(5n+5){grid-column:span 3}
@media (max-width:900px){
  .gallery{grid-template-columns:repeat(2,1fr);grid-auto-rows:clamp(104px,17vw,150px)}
  .gallery figure{grid-column:span 1}
  .gallery figure:nth-child(5n+1){grid-column:span 2;grid-row:span 2}
  .gallery figure:nth-child(5n+4),
  .gallery figure:nth-child(5n+5){grid-column:span 1}
}
@media (prefers-reduced-motion:reduce){
  .gallery img{transition:none}
}

.prevnext{display:flex;justify-content:space-between;gap:16px;padding:0 var(--pad) clamp(40px,6vw,80px)}
.pn{
  text-decoration:none;color:var(--ink);font-family:var(--display);font-size:20px;
  display:flex;flex-direction:column;gap:3px;
}
.pn span{font-family:var(--body);font-size:13px;color:var(--ink-2)}
.pn-r{text-align:right;margin-left:auto}
.pn:hover{color:var(--signal-deep)}

@media (max-width:820px){
  .detail{grid-template-columns:1fr;gap:8px}
}

/* ------------------------------------------------------------------ cycler
   The first slide carries .on from the server, so with JavaScript off the
   hero is a single photograph rather than an empty box. */
.cycler{position:relative;margin:0 var(--pad)}
.slides{position:relative;height:clamp(300px,60vh,660px);border-radius:3px;overflow:hidden;background:var(--paper-2);box-shadow:var(--lift)}
.slide{
  position:absolute;inset:0;opacity:0;text-decoration:none;
  transition:opacity 1.1s var(--ease);pointer-events:none;
}
.slide.on{opacity:1;pointer-events:auto}
.slide img{width:100%;height:100%;object-fit:cover}
/* The caption sat on whatever the photograph happened to put under it, which
   on a lit pool at dusk is white on white. A scrim is a legibility device
   here, not a decorative overlay. */
.slide::after{
  content:"";position:absolute;inset:auto 0 0 0;height:34%;pointer-events:none;
  background:linear-gradient(to top,rgba(0,0,0,.62),rgba(0,0,0,.18) 55%,transparent);
}
.slide-cap{z-index:1}
.slide-cap{
  position:absolute;left:20px;bottom:16px;color:#fff;
  font-size:17px;text-shadow:0 2px 20px rgba(0,0,0,.75);
  display:flex;flex-direction:column;gap:1px;
}
.slide-cap em{font-style:normal;font-size:13px;opacity:.85}
.slide:hover .slide-cap{text-decoration:underline;text-underline-offset:3px}
.dots{display:flex;gap:7px;justify-content:flex-end;padding-top:12px}
.dot{
  width:26px;height:3px;border:none;padding:0;cursor:pointer;border-radius:2px;
  background-color:var(--rule);transition:background-color .3s var(--ease);
}
/* background-color, not the background shorthand. The shorthand resets
   background-clip, and .dot.on outranks the .dot rule in the touch-target
   block at the foot of this file, so the active dot painted its whole 40x43
   hit area as a solid tan slab instead of a 26x3 bar. Caught on a mobile
   render; it looked like a design decision, which is the dangerous kind of
   bug. */
.dot.on{background-color:var(--signal)}

/* The required star. --signal-deep rather than --signal because this is a
   WORD, not a fill: the tan proper is 2.30:1 on the paper and would be a
   decorative smudge next to a label rather than a mark anyone can see. */
.lbl{display:inline}
.req{color:var(--danger);font-weight:600;padding-left:2px}
.req-note{
  grid-column:1/-1;margin:0 0 4px;
  font-size:13px;color:var(--ink-2);letter-spacing:.01em;
}
.req-note .req{font-weight:600}

/* -------------------------------------------------------------------- peek */
.peek{padding:clamp(56px,9vw,120px) var(--pad);border-top:1px solid var(--rule)}
.peek h2{
  font-family:var(--display);font-weight:400;
  font-size:clamp(28px,4.2vw,54px);letter-spacing:-.025em;line-height:1.08;
  max-width:19ch;text-wrap:balance;
}
.peek p{color:var(--ink-2);margin:14px 0 28px;max-width:52ch}

@media (prefers-reduced-motion:reduce){
  .slide{transition:none}
}

/* ------------------------------------------------------------------- tally
   Deliberately not four small cards in a row. Three lines at display scale,
   each with the sentence that makes the number mean something. */
.tally{padding:clamp(56px,9vw,130px) var(--pad) clamp(30px,5vw,60px);border-top:1px solid var(--rule)}
.tally-row{
  display:grid;grid-template-columns:minmax(0,auto) minmax(0,1fr);
  gap:clamp(16px,3vw,54px);align-items:baseline;
  padding:clamp(16px,2.4vw,26px) 0;border-bottom:1px solid var(--rule);
}
.fig{
  font-family:var(--display);font-weight:400;
  font-size:clamp(42px,8vw,108px);line-height:.94;letter-spacing:-.035em;
  font-variant-numeric:tabular-nums;white-space:nowrap;
}
.tally-row p{color:var(--ink-2);max-width:34ch;text-wrap:pretty}
.tally-note{color:var(--ink-2);opacity:.86;font-size:15px;padding-top:16px;max-width:60ch}
@media (max-width:760px){
  .tally-row{grid-template-columns:1fr;gap:4px;align-items:start}
}

/* ------------------------------------------------------------- book direct */
.direct{padding:clamp(56px,9vw,120px) var(--pad);border-top:1px solid var(--rule)}
.direct h2{
  font-family:var(--display);font-weight:400;
  font-size:clamp(27px,4vw,50px);letter-spacing:-.025em;line-height:1.1;
  max-width:20ch;text-wrap:balance;
}
.direct p{color:var(--ink-2);margin:16px 0 28px;max-width:60ch;text-wrap:pretty}

/* ----------------------------------------------------------------- filters */
.filters{display:flex;flex-wrap:wrap;gap:14px;align-items:end;margin:0 0 28px}
.filters label,.enq label{
  display:flex;flex-direction:column;gap:6px;font-size:15px;color:var(--ink-2);
}
.filters select,.enq select,.enq input,.enq textarea{
  background:oklch(1 0 0);border:1px solid var(--rule);color:var(--ink);
  font-family:inherit;font-size:16px;padding:11px 13px;border-radius:2px;
  min-width:190px;appearance:none;
}
.filters select:focus,.enq :is(select,input,textarea):focus{
  outline:2px solid var(--signal);outline-offset:-1px;border-color:transparent;
}
.filter-count{color:var(--ink-2);font-size:15px;padding-bottom:12px}
.no-match{color:var(--ink-2);padding:30px 0}
.house[hidden]{display:none}

/* -------------------------------------------------------------------- form */
.enq{
  display:grid;gap:16px;margin-top:34px;max-width:760px;
  grid-template-columns:repeat(auto-fit,minmax(220px,1fr));
}
.enq .f-full{grid-column:1/-1}
/* `.enq label{display:flex}` outranks the user-agent rule for [hidden], so
   the booking-only and managing-only fields were both on screen at once. */
.enq label[hidden],[hidden]{display:none}
.enq input,.enq select,.enq textarea{width:100%;min-width:0}
.enq textarea{resize:vertical}
.enq .pot{position:absolute;left:-9999px;width:1px;height:1px;opacity:0}
.consent{
  grid-column:1/-1;margin:0;
  font-size:13.5px;line-height:1.5;color:var(--ink-2);max-width:64ch;
}
.enq-foot{display:flex;flex-wrap:wrap;align-items:center;gap:16px}
.enq-msg{font-size:15px;color:var(--ink-2)}
.enq-msg.ok{color:var(--signal-deep)}
.enq label.bad input,.enq label.bad select{border-color:oklch(0.55 0.19 25)}
.enq label.bad::after{content:attr(data-err);font-size:13px;color:var(--danger)}

/* ------------------------------------------------------------------ quotes */
.quotes{padding:clamp(30px,5vw,64px) var(--pad);border-top:1px solid var(--rule)}
.quotes h2{font-family:var(--display);font-weight:400;font-size:clamp(20px,2.2vw,28px);margin-bottom:22px}
.quote-row{display:grid;grid-template-columns:repeat(auto-fit,minmax(260px,1fr));gap:clamp(16px,2vw,30px)}
.quote blockquote{
  color:var(--ink);font-size:16px;line-height:1.6;margin:0;
  padding-left:16px;border-left:1px solid var(--signal);
}
.quote figcaption{color:var(--ink-2);font-size:13px;margin-top:10px;padding-left:17px}

/* ---------------------------------------------------------- what you keep */
.keeps{margin:26px 0 30px;border-top:1px solid var(--rule)}
.keeps>div{
  display:grid;grid-template-columns:78px 1fr;gap:16px;align-items:baseline;
  padding:12px 0;border-bottom:1px solid var(--rule);
}
/* Sentence case, not tracked-out uppercase. The uppercase micro-label is the
   house style of every generated page on the internet and DESIGN.md rules it
   out by name; the word "Yours" carries this row on its own. */
.keeps dt{
  font-family:var(--display);font-size:15px;color:var(--ink-2);letter-spacing:-.01em;
}
.keeps .ours dt{color:var(--signal-deep)}
.keeps dd{color:var(--ink)}

/* --------------------------------------------------------------------- faq */
.faq{padding:clamp(56px,9vw,120px) var(--pad) clamp(20px,3vw,40px);border-top:1px solid var(--rule)}
.faq h2{
  font-family:var(--display);font-weight:400;
  font-size:clamp(30px,4.6vw,60px);letter-spacing:-.028em;line-height:1.05;
}
.faq .detail summary h3{
  font-family:var(--display);font-weight:400;
  font-size:clamp(17px,1.7vw,22px);letter-spacing:-.012em;
}
.faq .detail summary{padding-left:0;padding-right:0}
.faq .detail .prose{padding-left:0;padding-right:0}

/* --------------------------------------------------------------- estimator */
.est{padding:clamp(50px,8vw,110px) var(--pad);border-top:1px solid var(--rule)}
.est h2{
  font-family:var(--display);font-weight:400;
  font-size:clamp(28px,4.2vw,54px);letter-spacing:-.025em;line-height:1.06;
  max-width:16ch;text-wrap:balance;
}
.est-form{display:flex;flex-wrap:wrap;gap:14px;margin:28px 0 30px}
.est-form label{display:flex;flex-direction:column;gap:6px;font-size:15px;color:var(--ink-2)}
.est-form select{
  background:oklch(1 0 0);border:1px solid var(--rule);color:var(--ink);
  font-family:inherit;font-size:16px;padding:11px 13px;border-radius:2px;min-width:200px;appearance:none;
}
.est-form select:focus{outline:2px solid var(--signal);outline-offset:-1px;border-color:transparent}
.est-out{border-top:1px solid var(--rule);padding-top:22px}
.est-fig{
  font-family:var(--display);font-weight:400;
  font-size:clamp(36px,6.4vw,84px);line-height:1;letter-spacing:-.035em;
  display:block;font-variant-numeric:tabular-nums;
}
.est-basis{color:var(--ink-2);margin-top:12px;max-width:56ch}
.fineprint{
  color:var(--ink-2);opacity:.86;font-size:13px;line-height:1.6;
  margin-top:22px;max-width:70ch;
}

.ledger-note{color:var(--ink-2);opacity:.86;font-size:13px;padding-top:14px;max-width:62ch}

/* ------------------------------------------------- header mark and portal */
/* The header said only "Holdfast" with no mark, so the one place every page
   carries the brand carried the least of it. */
.mark{display:inline-flex;align-items:center;gap:10px}
.mark img{display:block;width:30px;height:30px;flex:none}
.mark span{white-space:nowrap}

/* Owners arrive looking for their portal, not a booking. It sits before the
   booking button and is quieter than it, because it serves the smaller
   audience of the two. */
.owner-login{
  color:var(--ink-2);text-decoration:none;font-size:15px;
  padding:12px 16px;border:1px solid var(--rule);border-radius:2px;
  transition:border-color .18s var(--ease),color .18s var(--ease);
  white-space:nowrap;
}
.owner-login:hover{color:var(--ink);border-color:var(--ink-2)}

@media (max-width:760px){
  .mark span{font-size:18px}
  .owner-login{padding:10px 12px;font-size:14px}
}

/* ------------------------------------------------------------- amenities */
/* A plain list of what the house has, in the order a guest decides on. Not a
   grid of icons: an icon for "washer and dryer" needs a caption anyway. */
.amens{
  list-style:none;margin:22px 0 0;padding:0;
  display:flex;flex-wrap:wrap;gap:8px;
}
.amens li{
  font-size:14px;color:var(--ink-2);
  border:1px solid var(--rule);border-radius:2px;
  padding:6px 11px;white-space:nowrap;
}
.rating-line{
  font-size:15px;color:var(--ink-2);margin:0 0 22px;
}
.rating-line strong{
  font-family:var(--display);font-size:22px;color:var(--ink);
  margin-right:4px;
}

/* ------------------------------------------------- mobile header and CTA */
/*
   At 390px the header row was wider than the viewport, which pushed the whole
   document sideways: the hero image, the lede and the second button all ran
   off the right edge. `.bar` is a nowrap flex row carrying a wordmark, four
   nav links, the owner login and the booking button, and nothing told it what
   to give up first.
*/
@media (max-width:860px){
  /* The nav anchors all point at sections further down the same page, so on a
     phone they are duplicating a scroll. */
  .bar nav{display:none}
  .owner-login{margin-left:auto}
}

@media (max-width:560px){
  /* The booking button becomes the sticky bottom CTA rather than a third
     thing competing for the header. Done by moving the button that is already
     there, so a house page keeps its own deep link and the home page keeps
     "Book a stay" without either template knowing about this. */
  /* backdrop-filter makes .bar a containing block for fixed descendants, so
     `bottom:0` resolved to the bottom of the HEADER and the button pinned
     itself across the top of the page, over the wordmark. Dropping the blur
     at this width hands the button back to the viewport, and a blur behind a
     40px-tall bar is no loss on a phone. */
  .bar{backdrop-filter:none;background:var(--paper)}
  .bar .btn-signal{
    position:fixed;left:0;right:0;bottom:0;z-index:60;
    margin:0;border-radius:0;text-align:center;
    padding:16px 20px;font-size:16px;
    box-shadow:0 -6px 22px rgb(62 56 49 / .18);
  }
  /* Room for it, plus the phone's own home indicator. */
  body{padding-bottom:calc(60px + env(safe-area-inset-bottom,0px))}
  .bar .btn-signal{padding-bottom:calc(16px + env(safe-area-inset-bottom,0px))}

  .mark img{width:26px;height:26px}
  .mark span{font-size:17px}
  .owner-login{padding:9px 11px;font-size:13px}
  .bar{gap:10px;padding:12px var(--pad)}
}

/* No overflow-x:hidden guard here on purpose. Measured at a true 390px
   viewport, scrollWidth equals clientWidth, so there is nothing to guard
   against, and `overflow` on html or body makes it the scroll container and
   silently kills `position:sticky` on the header. */

/* ------------------------------------------------------- market pages */
.market .mk-hero{padding:clamp(60px,10vw,140px) var(--pad) clamp(30px,4vw,50px);max-width:56rem}
/* .band brings no padding of its own. On the home page it is always paired
   with a section class that supplies it (.direct, .faq, .closer, .where); the
   market page used it bare, so every section ran edge to edge with no gutter. */
.market .band{padding:clamp(40px,7vw,90px) var(--pad);border-top:1px solid var(--rule)}
.market .tally{padding:clamp(30px,6vw,70px) var(--pad)}
.market .mk-hero h1{
  font-family:var(--display);font-weight:400;
  font-size:clamp(32px,5.2vw,62px);letter-spacing:-.03em;line-height:1.05;
  text-wrap:balance;margin:0 0 22px;
}
.market .mk-hero .prose{max-width:56ch;margin-bottom:30px}
.lic{margin:24px 0 0;display:grid;gap:0}
.lic-row{
  display:grid;grid-template-columns:minmax(160px,15rem) 1fr;gap:20px;
  padding:16px 0;border-top:1px solid var(--rule);
}
.lic-row dt{font-family:var(--display);font-size:17px;color:var(--ink)}
.lic-row dd{margin:0;color:var(--ink-2);text-wrap:pretty}
ol.steps{list-style:none;counter-reset:st;margin:0;padding:0;display:grid;gap:22px}
ol.steps>li{counter-increment:st;display:grid;grid-template-columns:2.4rem 1fr;gap:0 14px}
/* Both the heading and the paragraph belong in the second column. Without
   this the grid auto-places them one after another, so the counter took
   cell 1, the h3 took cell 2, and the paragraph wrapped onto the next row
   into the 2.4rem counter column: one word per line down the whole page. */
ol.steps>li>*{grid-column:2}
ol.steps>li::before{
  content:counter(st);grid-column:1;grid-row:1;
  font-family:var(--display);font-size:20px;color:var(--signal-deep);
}
ol.steps>li>h3{margin-bottom:6px}
ol.steps h3{font-family:var(--display);font-weight:400;font-size:19px;margin:0 0 4px}
ol.steps p{margin:0;color:var(--ink-2);text-wrap:pretty}
.two-col{display:grid;grid-template-columns:1fr 1fr;gap:34px;margin-top:8px}
.two-col h3{font-family:var(--display);font-weight:400;font-size:18px;margin:0 0 12px}
ul.ticks{list-style:none;margin:0;padding:0;display:grid;gap:8px}
ul.ticks li{color:var(--ink-2);padding-left:18px;position:relative;font-size:15px}
ul.ticks li::before{content:"-";position:absolute;left:0;color:var(--signal-deep)}
ul.ticks.neg li::before{color:var(--ink-2)}
@media (max-width:760px){
  .lic-row{grid-template-columns:1fr;gap:4px}
  .two-col{grid-template-columns:1fr;gap:24px}
}
.foot-markets{margin-top:10px;opacity:.86;font-size:14px}
/* Social sits on its own line under the page nav rather than mixed into it -
   these leave the site and the others do not, and that is worth one row of
   separation. Text, not icons: there is no icon language anywhere else here. */
.foot-social{grid-column:1/-1;margin-top:4px;gap:8px clamp(14px,2vw,24px)}
.foot-social a{color:var(--ink-2);text-decoration:none}
.foot-social a:hover{color:var(--ink)}

/* ------------------------------------------------------------------ legal
   Privacy and terms. Long-form reading, so the measure is tighter than the
   marketing prose and the headings carry more air above than below - the
   heading belongs to the paragraph under it, not the one before. */
.legal h1{margin-bottom:6px}
.legal-date{font-size:14px;color:var(--ink-3,var(--ink-2));margin-bottom:34px!important}
.legal h2{
  font-family:var(--display);font-weight:400;
  font-size:clamp(19px,1.7vw,23px);letter-spacing:-.012em;
  color:var(--ink);margin:38px 0 12px;
}
.legal p{max-width:64ch}
.legal strong{color:var(--ink)}
.legal ul{list-style:none;margin:0 0 14px;padding:0;max-width:64ch;display:grid;gap:10px}
.legal li{color:var(--ink-2);padding-left:18px;position:relative;text-wrap:pretty}
.legal li::before{
  content:"";position:absolute;left:0;top:.62em;
  width:6px;height:1px;background:var(--signal);
}

/* --------------------------------------------------------------- supplies
   112 items in seven sections. A multi-column list because a single column of
   one-word labels is a very long thin page to scroll on a phone, and these
   are scanned rather than read. */
.supplies h2{
  font-family:var(--display);font-weight:400;
  font-size:clamp(20px,1.9vw,26px);letter-spacing:-.012em;
  color:var(--ink);margin:44px 0 8px;
}
.supplies .lede{max-width:60ch}
.sup-sec{margin-bottom:6px}
.sup-list{
  list-style:none;margin:16px 0 0;padding:0;
  display:grid;gap:9px 26px;
  grid-template-columns:repeat(auto-fill,minmax(160px,1fr));
}
.sup-list li{color:var(--ink-2);text-wrap:pretty}
.sup-list a{
  color:var(--ink);text-decoration:underline;
  text-decoration-thickness:1px;text-underline-offset:3px;
  text-decoration-color:color-mix(in oklch,var(--signal-deep) 45%,transparent);
}
.sup-list a:hover{text-decoration-color:var(--signal-deep)}
/* Items the original page never linked. Shown, but visibly not a link. */
.sup-plain{color:var(--ink-2);opacity:.8}
.sup-disc{
  border-left:2px solid var(--signal);
  padding:12px 0 12px 16px;margin:22px 0 8px;max-width:60ch;
  font-size:15px;color:var(--ink-2);
}
.sup-disc strong{color:var(--ink)}
.sup-disc-foot{margin-top:44px;font-size:14px}
@media (max-width:560px){
  .sup-list{grid-template-columns:repeat(auto-fill,minmax(132px,1fr));gap:9px 18px}
}

/* Service area. Sits under the live ledger and is a different kind of claim
   from it, so it reads as prose rather than more table. */
.where .area{margin-top:18px;max-width:62ch}
.where .area+.area{margin-top:12px}

/* ==================================================================== motion
   Added 2026-08-21. Pierce sent midu.design and billow.so and asked for the
   feeling those have: "the whole background seems to flow together, pieces
   move with page movement, some pieces look like they are cut out and form
   back together". Both references are Framer sites running Lenis for inertia
   scroll and transform-driven scroll effects, so this is that vocabulary
   rebuilt by hand rather than a library dropped in.

   Everything here is additive and lives under .motion, which only exists when
   JavaScript ran. With JS off the page is exactly what it was. Every rule is
   switched off again under prefers-reduced-motion at the foot of this block. */

/* The continuous ground. One fixed field behind the whole document rather than
   a background per section, so scrolling moves through a single space instead
   of past a series of panels. This is most of what "flows together" means. */
/* Rebuilt for the light ground 2026-08-21. The dark version washed the page
   with 8% and 16% of mid-lightness colour, which on paper is not a haze - it
   is a beige stain, and beige is the exact thing this palette exists to avoid.
   These are high-lightness washes at a few percent: on a screen they read as
   the page being lit from three directions rather than as colour, and because
   the field is fixed and drifts, the light moves as you scroll instead of the
   page passing a series of tinted panels. */
/* The field drifts UP as the page scrolls down, and it only ever drifts up, so
   the overhang it needs is at the bottom and it is not a fixed amount - it is
   the whole distance the field will travel over the length of the page.

   `inset:-12vh 0` gave it 114px of slack at the bottom of a 950px viewport and
   the drift passes that at about 1,900px of scroll. Past there the field's own
   bottom edge climbs into view and everything below it is bare paper: a white
   band pinned to the bottom of the window, growing as you go, 242px of it by
   the foot of the home page. Pierce saw it and sent a screenshot; measured at
   five scroll positions before touching anything.

   The dark build had exactly the same bug and it was invisible, because the
   field's tints over a dark ground barely differed from the ground itself. Going
   light is what exposed it.

   So the bottom overhang is set from JavaScript to the real maximum travel for
   this page's height, recomputed on resize. The gradient centres are placed in
   vh from the element's own top rather than in percentages, so growing that
   overhang cannot slide them around. */
.field{
  position:fixed;z-index:0;pointer-events:none;
  top:-12vh;left:0;right:0;
  bottom:calc(-12vh - var(--fy-span,12vh));
  background:
    radial-gradient(62vw 62vw at 8% 5vh,
      color-mix(in oklch,oklch(0.86 0.055 205) 42%,transparent),transparent 64%),
    radial-gradient(76vw 76vw at 94% 42vh,
      color-mix(in oklch,oklch(0.90 0.055 48) 34%,transparent),transparent 66%),
    radial-gradient(56vw 56vw at 22% 107vh,
      color-mix(in oklch,oklch(0.87 0.050 195) 34%,transparent),transparent 62%);
  transform:translate3d(0,var(--fy,0px),0);
  will-change:transform;
}
body>header,body>main,body>footer,body>section{position:relative;z-index:1}

/* Parallax. object-position rather than a transform, because every one of
   these images is object-fit:cover inside a fixed box already - shifting the
   focal point moves the picture within its own frame with no wrapper element,
   no overflow to hide and no layout shift. */
.motion [data-px]{object-position:50% calc(50% + var(--py,0px))}

/* "Cut out and formed back together." The frame opens from its own centre line
   instead of the picture fading in, so the image assembles rather than appears. */
/* 32%, not more. At 46% the closed state is an 8% sliver and for the whole
   second it is opening the page reads as a rendering fault rather than an
   effect - checked on screen, not guessed. 32% still assembles visibly from
   the centre line while every frame stays recognisable the whole way. */
.motion .cut{clip-path:inset(0 32% 0 32%);transition:clip-path 1.05s var(--ease)}
.motion .cut.in{clip-path:inset(0 0 0 0)}

/* Section headings arrive a beat before their body, which is what stops a
   reveal reading as one lump sliding up. */
.motion .reveal>h2{transition-delay:.06s}

/* Things that lag the page. An image drifts inside its own frame by moving its
   focal point; anything that is not a cover image drifts by moving itself, a
   handful of pixels against the scroll, which reads as sitting further back
   than the text beside it. Transform only - nothing here can shift a layout. */
.motion [data-dy]{transform:translate3d(0,var(--dy,0px),0)}

/* House cards deal themselves out along the row. The transform is a lift plus
   the last half-percent of scale, not a slide: a card that travels far enough
   to notice reads as a page still loading. */
.motion .card-in{
  opacity:0;transform:translateY(20px) scale(.994);
  transition:opacity .8s var(--ease),transform .8s var(--ease),box-shadow .4s var(--ease);
}
.motion .card-in.in{opacity:1;transform:none}

@media (prefers-reduced-motion:reduce){
  .field{transform:none!important}
  .motion [data-px]{object-position:50% 50%!important}
  .motion .cut{clip-path:none!important;transition:none!important}
  .motion .card-in{opacity:1!important;transform:none!important;transition:none!important}
  .motion [data-dy]{transform:none!important}
}

/* ---------------------------------------------------------------------------
   Added 2026-08-22. Uses the LIVE tokens — --paper-2, --rule, --ink, --ink-2,
   --signal-deep. DESIGN.md still describes the old dark build (--deep, --chalk)
   and those variables do not exist any more; styling written against them
   silently renders as nothing. Read :root in this file, not the design doc.
   --------------------------------------------------------------------------- */

/* Footer. Three real columns with explicit rows, so the brand and contact
   details stack under each other on the left instead of being auto-placed into
   whatever cell came next — which is what left a tall hole in column one.

   .foot-nav is shared by three navs (links, markets, social), so the main one
   has to be selected by excluding the other two, or the column rule lands on
   all three and the social row stacks vertically. */
.foot{
  grid-template-columns:minmax(0,1fr) minmax(0,1fr) minmax(0,1.2fr);
  align-items:start;
  row-gap:clamp(16px,2vw,24px);
  column-gap:clamp(24px,4vw,56px);
}
.foot-brand{grid-column:1;grid-row:1;display:flex;flex-direction:column;gap:clamp(14px,1.6vw,20px);align-items:start}
.foot-mark{margin:0}
.foot-contact{display:flex;flex-direction:column;gap:5px;font-size:15px}
.foot-contact a{color:var(--ink);text-decoration:none;width:fit-content;border-bottom:1px solid var(--rule)}
.foot-contact a:hover{color:var(--signal-deep);border-bottom-color:currentColor}
.foot-nav:not(.foot-markets):not(.foot-social){
  grid-column:2;grid-row:1;flex-direction:column;gap:8px;align-items:start;
}
.foot-markets{
  grid-column:3;grid-row:1;margin:0;
  display:flex;flex-direction:column;gap:8px;align-items:start;font-size:14px;
}
.foot-social{
  grid-column:1/-1;grid-row:2;margin:0;
  flex-direction:row;flex-wrap:wrap;gap:8px clamp(14px,2vw,24px);
}
.foot-fine{
  grid-column:1/-1;grid-row:3;margin:0;
  border-top:1px solid var(--rule);padding-top:14px;
}
@media (max-width:760px){
  .foot{grid-template-columns:1fr}
  .foot-brand{grid-column:1;grid-row:auto}
  .foot-nav:not(.foot-markets):not(.foot-social){grid-column:1;grid-row:auto}
  .foot-markets{grid-column:1;grid-row:auto}
  .foot-social{grid-row:auto}
  .foot-fine{grid-row:auto}
}

.voices.band{grid-template-columns:1fr;gap:clamp(18px,2.4vw,28px)}
.voices.band>h2{max-width:26ch}

/* Owner testimonials. Real cards: a recessed ground and a hairline, which is
   how every other panel on this site is drawn. */
.voices-grid{
  display:grid;
  grid-template-columns:repeat(3,minmax(0,1fr));
  gap:clamp(14px,1.6vw,20px);
  align-items:stretch;
}
.voice{
  margin:0;padding:clamp(20px,2.2vw,26px);
  background:var(--paper-2);
  border:1px solid var(--rule);
  border-radius:10px;
  display:flex;flex-direction:column;gap:14px;
  height:100%;
}
.voice p{margin:0;font-size:16px;line-height:1.55;color:var(--ink)}
.voice p::before{content:"\201C"}
.voice p::after{content:"\201D"}
.voice cite{
  font-style:normal;font-size:13.5px;color:var(--ink-2);
  letter-spacing:.02em;margin-top:auto;
}
@media (max-width:1000px){.voices-grid{grid-template-columns:repeat(2,minmax(0,1fr))}}
@media (max-width:640px){.voices-grid{grid-template-columns:1fr}}
.voices-note{margin-top:clamp(14px,1.6vw,20px);font-size:13px;color:var(--ink-2)}
.voice-inline{margin:1.6rem 0;max-width:38rem}

.closer-call{margin-top:14px;font-size:16px}
.closer-call a{color:var(--signal-deep);text-decoration:none;border-bottom:1px solid currentColor}

/* The mark: larger, centred, and the way home from every page.

   Done by taking the mark out of flow and pinning it to the centre, rather
   than with a three column grid — a grid would have put the owner-login and
   the book button in the same cell, stacked on top of each other, because both
   are direct children of .bar.

   Only above 860px. On a phone a centred mark and two right-hand buttons run
   into each other, so below that it goes back to sitting on the left. */
.bar .mark img{width:38px;height:38px}
.bar .mark{font-size:clamp(21px,2.2vw,25px)}

@media (min-width:861px){
  .bar{position:sticky}
  .bar .mark{
    position:absolute;left:50%;top:50%;
    transform:translate(-50%,-50%);
    margin:0;
  }
  .bar nav{margin-left:0}
  .bar .owner-login{margin-left:auto}
}
@media (max-width:860px){
  .bar .mark img{width:32px;height:32px}
}

/* The founder portrait on the About page. Sits alongside the opening section
   on a wide screen and goes full width on a phone, so it reads as a portrait
   rather than a banner. width/height are declared so the text does not jump
   while it loads. */
.portrait{margin:0 0 clamp(20px,3vw,32px);max-width:320px}
.portrait img{display:block;width:100%;height:auto;border-radius:10px;border:1px solid var(--rule)}
.portrait figcaption{margin-top:9px;font-size:13.5px;color:var(--ink-2)}
@media (min-width:761px){
  /* 38px matches the top margin on .legal h2, so the top of the photograph
     lines up with the top of the heading beside it rather than floating above it. */
  .portrait{float:right;margin:38px 0 20px clamp(24px,3vw,40px);shape-outside:margin-box}
}
@media (max-width:760px){.portrait{max-width:100%}}

/* -------------------------------------------------- touch targets, 2026-08-24
   A mobile audit at 320, 390 and 430px found no layout faults: nothing
   overflows, nothing is under 12px. What it found was that almost every
   control is a comfortable size to READ and an uncomfortable size to HIT.
   Apple asks for 44px. The carousel dots were 26x3.

   The first attempt at this used an invisible ::after overlay so that nothing
   in the layout would move. It measured well and was wrong: overlays on a
   wrapped list overlap, and a later link paints above an earlier one, so aiming
   at a link and landing 10px low opened its neighbour. A mis-tap is worse than
   a small target, because the person does not know it happened until the wrong
   page loads. So these are real boxes with real padding. The footer is a
   little taller on a phone. That is the correct trade.

   The dots keep their 26x3 appearance: background-clip confines the paint to
   the content box while the padding does the catching. */
@media (max-width:820px){

  .dots{gap:16px;padding-top:6px}
  .dot{
    box-sizing:content-box;
    padding:20px 7px;
    background-clip:content-box;
  }

  .foot-nav{gap:0 clamp(16px,2.4vw,30px)}
  .foot-nav a{display:inline-block;padding:11px 0}

  .foot-contact{gap:0}
  .foot-contact a{display:inline-block;padding:10px 0}
}

/* ------------------------------------------------------ mobile menu, 2026-08-24
   Until today the header nav was simply display:none below 900px and there was
   nothing in its place, so on a phone the only way to reach anything was the
   footer at the very bottom of the page. That was fine while the nav pointed
   at sections of the same page. It stopped being fine when About, Pricing and
   Questions became real pages.

   Built on <details>, not a button and a class. The reason is the rule this
   site already keeps: content is reachable with JavaScript disabled. The
   element ships with `open`, so with no JS the links are simply visible in a
   stack. site.js closes it at phone widths on load, which turns it into a
   menu. Nothing is hidden behind script that was not visible without it. */

.menu{display:contents}
.menu-btn{display:none}

@media (max-width:900px){
  .menu{display:block;order:9;position:static}
  .owner-login{margin-left:auto}

  .menu-btn{
    display:flex;align-items:center;justify-content:center;
    width:44px;height:44px;
    cursor:pointer;list-style:none;
  }
  /* Safari draws its own disclosure triangle on a summary and ignores
     list-style, so it needs the vendor pseudo as well. */
  .menu-btn::-webkit-details-marker{display:none}

  .menu-ico,
  .menu-ico::before,
  .menu-ico::after{
    display:block;width:21px;height:2px;border-radius:2px;
    background:var(--ink);transition:transform .2s var(--ease),opacity .2s var(--ease);
  }
  .menu-ico{position:relative}
  .menu-ico::before,.menu-ico::after{content:"";position:absolute;left:0}
  .menu-ico::before{top:-6px}
  .menu-ico::after{top:6px}

  .menu[open] .menu-ico{background:transparent}
  .menu[open] .menu-ico::before{transform:translateY(6px) rotate(45deg)}
  .menu[open] .menu-ico::after{transform:translateY(-6px) rotate(-45deg)}

  /* The panel. .bar is sticky, which makes it the containing block, so this
     hangs off the bottom edge of the header rather than the top of the page. */
  .bar .menu[open] > nav{
    display:flex;flex-direction:column;gap:0;
    position:absolute;left:0;right:0;top:100%;
    margin:0;padding:6px var(--pad) 14px;
    background:var(--paper);border-bottom:1px solid var(--rule);
    box-shadow:var(--lift);
  }
  .bar .menu[open] > nav a{
    padding:14px 0;font-size:17px;color:var(--ink);
    border-bottom:1px solid var(--rule);
  }
  .bar .menu[open] > nav a:last-child{border-bottom:none}
}

/* An iPhone SE is 320px wide and the header row needs 362px once the menu
   button is in it. The wordmark goes and the palm stays: same link, same
   brand, 135px cheaper. Above 380px both are back. */
@media (max-width:380px){
  .mark span{display:none}
}

@media (max-width:900px) and (prefers-reduced-motion:reduce){
  .menu-ico,.menu-ico::before,.menu-ico::after{transition:none}
}

/* --------------------------------------------------- skip link, 2026-08-24
   WCAG 2.4.1 Bypass Blocks, Level A. Someone on a keyboard or a screen reader
   was walking five to eight header links on every page before reaching the
   content. Added during the compliance audit; Florida filed 961 website
   accessibility suits in 2025 and this is the first thing a scanner reports.

   Off-screen rather than display:none, because display:none removes it from
   the focus order and then it does nothing at all. */
.skip{
  position:absolute;left:-9999px;top:0;z-index:200;
  background:var(--ink);color:var(--paper);
  padding:12px 18px;border-radius:0 0 3px 0;
  font-size:15px;font-weight:500;text-decoration:none;
}
.skip:focus{left:0}
/* The target takes focus programmatically via tabindex="-1", and a focus ring
   drawn around the whole page body is noise rather than help. */
#main:focus{outline:none}
#main:focus-visible{outline:2px solid var(--signal-deep);outline-offset:-4px}

/* ------------------------------------------- accessibility block, 2026-08-24
   Sits above the booking button on every house page, because someone deciding
   whether a house will work for them needs it before the button and not after.
   Set on the recessed ground rather than as a warning: it is a statement of
   fact about a building, not an alert. */
.access{
  padding:clamp(30px,4vw,44px) var(--pad);
  background:var(--paper-2);border-top:1px solid var(--rule);border-bottom:1px solid var(--rule);
}
.access h2{
  font-family:var(--display);font-weight:400;
  font-size:clamp(21px,2.4vw,27px);letter-spacing:-.015em;line-height:1.15;
  margin:0 0 14px;
}
.access p{
  margin:0 0 12px;max-width:66ch;font-size:16px;line-height:1.65;color:var(--ink-2);
}
.access p:last-child{margin-bottom:0}
.access a{color:var(--signal-deep)}

/* Per-house accessibility facts. A definition list rather than prose because
   somebody scanning for one thing, "can I get in the front door", should find
   it without reading a paragraph. */
.access-list{margin:18px 0 0;max-width:64ch}
.access-row{
  display:grid;grid-template-columns:130px 1fr;gap:6px 18px;
  padding:11px 0;border-top:1px solid var(--rule);
}
.access-row:last-child{border-bottom:1px solid var(--rule)}
.access-row dt{font-size:13px;letter-spacing:.06em;text-transform:uppercase;color:var(--signal-deep)}
.access-row dd{margin:0;font-size:16px;line-height:1.55;color:var(--ink)}
.access-note{font-size:14.5px;font-style:italic;margin-top:16px}
@media (max-width:560px){
  .access-row{grid-template-columns:1fr;gap:3px}
}
