/* ═══════════════════════════════════════════════════════════════
   Aimei Bang Saray
   Calm · warm · premium · minimal
   ═══════════════════════════════════════════════════════════════ */

:root{
  /* Palette — washi paper, sumi ink, indigo, cedar */
  --paper:#FAF7F2;
  --paper-2:#F3EEE6;
  --ink:#1A1614;
  --ink-soft:#4A423B;
  /* Darkened 10 Sep 2026. #7A7068 measured 4.18:1 on --paper-2 (#F3EEE6) —
     it passed on white paper and failed on every tinted section, which is
     exactly where the café menu lives. #6F665E measures 4.86:1 on the tint
     and 5.26:1 on the paper, so it now passes on both grounds. */
  --muted:#6F665E;
  --line:#E6DFD5;
  --indigo:#2C4A5E;
  --indigo-dark:#1F3644;
  --cedar:#A67C52;
  --white:#FFFFFF;

  --serif:'Cormorant Garamond',Georgia,serif;
  --serif-th:'Noto Serif Thai',serif;
  --sans:'IBM Plex Sans Thai','IBM Plex Sans',-apple-system,BlinkMacSystemFont,'Segoe UI',sans-serif;

  --measure:68ch;
  --gutter:clamp(1.25rem,5vw,3rem);
  --wrap:1180px;
  --radius:2px;
  --shadow:0 1px 2px rgba(26,22,20,.04),0 8px 32px rgba(26,22,20,.06);
  --ease:cubic-bezier(.4,0,.2,1);
}

*,*::before,*::after{box-sizing:border-box}
html{-webkit-text-size-adjust:100%;scroll-behavior:smooth}
@media (prefers-reduced-motion:reduce){
  html{scroll-behavior:auto}
  *{animation-duration:.01ms!important;transition-duration:.01ms!important}
}

body{
  margin:0;
  background:var(--paper);
  color:var(--ink);
  font-family:var(--sans);
  font-size:clamp(1rem,.97rem + .15vw,1.0625rem);
  font-weight:300;
  line-height:1.75;
  -webkit-font-smoothing:antialiased;
}

/* ── Typography ─────────────────────────────────────────────── */
h1,h2,h3,h4{
  font-family:var(--serif);
  font-weight:300;
  line-height:1.15;
  letter-spacing:-.01em;
  color:var(--ink);
  margin:0 0 .6em;
  text-wrap:balance;
}
html[lang="th"] h1,html[lang="th"] h2,html[lang="th"] h3,html[lang="th"] h4{
  font-family:var(--serif-th);
  line-height:1.4;
  letter-spacing:0;
}
h1{font-size:clamp(2.4rem,1.7rem + 3.2vw,4.25rem)}
h2{font-size:clamp(1.85rem,1.4rem + 2vw,2.9rem)}
h3{font-size:clamp(1.3rem,1.15rem + .7vw,1.7rem)}
h4{font-size:1.1rem;font-family:var(--sans);font-weight:500;letter-spacing:.01em}

p{margin:0 0 1.25em;max-width:var(--measure)}
a{color:var(--indigo);text-decoration:none;transition:color .2s var(--ease)}
a:hover{color:var(--cedar)}
img{max-width:100%;height:auto;display:block}
hr{border:0;border-top:1px solid var(--line);margin:3rem 0}

.eyebrow{
  font-family:var(--sans);
  font-size:.72rem;
  font-weight:500;
  letter-spacing:.22em;
  text-transform:uppercase;
  color:var(--cedar);
  margin:0 0 1.25rem;
}
html[lang="th"] .eyebrow{letter-spacing:.08em}
.lede{font-size:1.15em;color:var(--ink-soft);max-width:60ch}
.muted{color:var(--muted)}

/* ── Layout ─────────────────────────────────────────────────── */
.wrap{max-width:var(--wrap);margin-inline:auto;padding-inline:var(--gutter)}
.wrap--narrow{max-width:820px}
.section{padding-block:clamp(4rem,9vw,8rem)}
.section--tint{background:var(--paper-2)}
.section--ink{background:var(--indigo-dark);color:#EFE9E0}
.section--ink h2,.section--ink h3{color:#FBF8F3}
.section--ink p{color:#CFC6BA}

.center{text-align:center}
.center p{margin-inline:auto}

.grid{display:grid;gap:clamp(1.5rem,3vw,2.75rem)}
@media(min-width:720px){
  .grid--2{grid-template-columns:repeat(2,1fr)}
  .grid--3{grid-template-columns:repeat(3,1fr)}
}
@media(min-width:900px){
  .split{display:grid;grid-template-columns:1fr 1fr;gap:clamp(2rem,5vw,5rem);align-items:center}
  .split--wide{grid-template-columns:1.15fr .85fr}
}

.stack>*+*{margin-top:1.5rem}

/* ── Skip link ──────────────────────────────────────────────── */
.skip{
  position:absolute;left:-9999px;top:0;z-index:200;
  background:var(--ink);color:var(--paper);padding:.75rem 1.25rem;
}
.skip:focus{left:0}

/* ── Header ─────────────────────────────────────────────────── */
.site-header{
  position:sticky;top:0;z-index:100;
  background:rgba(250,247,242,.92);
  backdrop-filter:saturate(140%) blur(12px);
  border-bottom:1px solid var(--line);
}
.site-header__inner{
  display:flex;align-items:center;gap:1.5rem;
  min-height:76px;
}
.brand{display:flex;align-items:center;gap:.75rem;flex:0 0 auto}
.brand img{height:44px;width:auto}
.brand__name{
  font-family:var(--serif);font-size:1.35rem;letter-spacing:.02em;color:var(--ink);
}
html[lang="th"] .brand__name{font-family:var(--serif-th)}

.nav{margin-left:auto;display:none}
@media(min-width:1040px){.nav{display:flex;align-items:center;gap:2rem}}
.nav a{
  font-size:.85rem;letter-spacing:.06em;text-transform:uppercase;
  color:var(--ink-soft);font-weight:400;
}
html[lang="th"] .nav a{text-transform:none;letter-spacing:0;font-size:.95rem}
.nav a:hover,.nav a[aria-current="page"]{color:var(--cedar)}

.header-actions{margin-left:auto;display:flex;align-items:center;gap:.75rem}
@media(min-width:1040px){.header-actions{margin-left:0}}

/* display was never set here, so this was a plain inline <a> everywhere except
   touch devices — which is why the globe added on 17 Aug 2026 sat on the text
   baseline instead of beside it, and why `gap` did nothing. Set it once, here,
   rather than patching it per breakpoint.

   The colour is a step darker than --muted now. As --muted it read as a caption
   rather than a control, which is exactly why it was being overlooked. */
.lang-switch{
  display:inline-flex;align-items:center;gap:.35rem;
  font-size:.78rem;letter-spacing:.1em;text-transform:uppercase;
  color:var(--ink,#1A1614);border:1px solid var(--line);
  padding:.4rem .7rem;border-radius:var(--radius);
  text-decoration:none;white-space:nowrap;
}
.lang-switch__globe{flex:0 0 auto;opacity:.75}
.lang-switch:hover{border-color:var(--cedar);color:var(--cedar)}
.lang-switch:hover .lang-switch__globe{opacity:1}
/* Thai is not an uppercase script — text-transform does nothing to it, but the
   .1em tracking pushes the glyphs apart badly. Only the Latin label wants it. */
.lang-switch span[lang="th"]{letter-spacing:0;text-transform:none;font-size:.85rem}

/* Mobile nav */
.nav-toggle{
  display:inline-flex;align-items:center;justify-content:center;
  width:44px;height:44px;background:none;border:1px solid var(--line);
  border-radius:var(--radius);cursor:pointer;color:var(--ink);
}
@media(min-width:1040px){.nav-toggle{display:none}}
.nav-mobile{
  display:none;border-top:1px solid var(--line);background:var(--paper);
  padding:1rem var(--gutter) 2rem;
}
.nav-mobile[data-open="true"]{display:block}
.nav-mobile a{
  display:block;padding:.85rem 0;border-bottom:1px solid var(--line);
  color:var(--ink-soft);font-size:1.05rem;
}

/* ── Buttons ────────────────────────────────────────────────── */
.btn{
  display:inline-flex;align-items:center;justify-content:center;gap:.5rem;
  font-family:var(--sans);font-size:.82rem;font-weight:500;
  letter-spacing:.14em;text-transform:uppercase;
  padding:.95rem 1.9rem;border-radius:var(--radius);
  border:1px solid transparent;cursor:pointer;
  transition:background .25s var(--ease),color .25s var(--ease),border-color .25s var(--ease);
  text-align:center;
}
html[lang="th"] .btn{letter-spacing:.02em;text-transform:none;font-size:.95rem}
.btn--primary{background:var(--indigo);color:#fff}
.btn--primary:hover{background:var(--indigo-dark);color:#fff}
.btn--ghost{border-color:var(--ink);color:var(--ink);background:none}
.btn--ghost:hover{background:var(--ink);color:var(--paper)}
.btn--light{border-color:rgba(255,255,255,.5);color:#fff}
.btn--light:hover{background:#fff;color:var(--indigo-dark)}
.btn--sm{padding:.6rem 1.1rem;font-size:.75rem}
.btn--block{width:100%}
.btn[disabled]{opacity:.45;cursor:not-allowed}

/* ── Hero ───────────────────────────────────────────────────────────
   PORTRAIT-FIRST. 42 of the 43 photographs on this site are portrait or
   square; exactly one is landscape. A wide hero band therefore takes a
   tall photograph and throws away the top and bottom of it, which is why
   the old hero read as a slice of wall rather than a chosen picture.

   On a phone the hero is rendered at 9:16 — the shape the library was
   already shot in — so nothing is cropped away. From 768px up it becomes
   the wide band it has to be, with the focal point held above centre.   */
.hero{
  position:relative;aspect-ratio:9/16;max-height:88vh;
  display:grid;align-items:end;isolation:isolate;
  /* Guard rail, 17 Aug 2026. The real fix for the phone bug is
     `aspect-ratio:auto` in the mobile block — see the long note there. This
     line just makes it impossible for the hero to exceed its container again,
     and is a no-op everywhere the layout is already correct. */
  max-width:100%;
}
@supports (height:1svh){ .hero{max-height:88svh} }
.hero__media{position:absolute;inset:0;z-index:-2;overflow:hidden}
.hero__media img{width:100%;height:100%;object-fit:cover;object-position:center}
.hero::after{
  content:'';position:absolute;inset:0;z-index:-1;
  background:linear-gradient(to top,rgba(18,14,12,.82) 0%,rgba(18,14,12,.28) 55%,rgba(18,14,12,.05) 100%);
}
.hero__inner{padding-block:clamp(2.5rem,8vw,6rem);color:#fff}

@media (min-width:768px){
  .hero{aspect-ratio:auto;min-height:clamp(520px,82vh,780px);max-height:none}
  .hero__media img{object-position:center 38%}
  .hero::after{
    background:linear-gradient(to top,rgba(20,16,14,.72) 0%,rgba(20,16,14,.25) 45%,rgba(20,16,14,.15) 100%);
  }
}
.hero h1{color:#fff;max-width:16ch;margin-bottom:.35em}
.hero p{color:rgba(255,255,255,.9);font-size:1.15rem;max-width:46ch}
.hero .eyebrow{color:rgba(255,255,255,.75)}
.hero__cta{display:flex;flex-wrap:wrap;gap:.85rem;margin-top:2rem}

/* ── Cards ──────────────────────────────────────────────────── */
.card{
  background:var(--white);border:1px solid var(--line);border-radius:var(--radius);
  overflow:hidden;display:flex;flex-direction:column;
  transition:transform .35s var(--ease),box-shadow .35s var(--ease);
}
.card:hover{transform:translateY(-4px);box-shadow:var(--shadow)}
.card__media{aspect-ratio:4/3;overflow:hidden;background:var(--paper-2)}
.card__media img{width:100%;height:100%;object-fit:cover;transition:transform .6s var(--ease)}
.card:hover .card__media img{transform:scale(1.04)}
.card__body{padding:1.75rem;display:flex;flex-direction:column;flex:1}
.card__body h3{margin-bottom:.4rem}
.card__meta{
  display:flex;flex-wrap:wrap;gap:1rem;color:var(--muted);
  font-size:.82rem;letter-spacing:.04em;margin-bottom:1rem;
}
.card__body p{flex:1;font-size:.95rem;color:var(--ink-soft)}
.card__foot{margin-top:1.25rem}

/* ── Feature list ───────────────────────────────────────────── */
.features{list-style:none;padding:0;margin:0;display:grid;gap:.8rem}
@media(min-width:640px){.features{grid-template-columns:repeat(2,1fr)}}
.features li{
  position:relative;padding-left:1.6rem;color:var(--ink-soft);font-size:.95rem;
}
.features li::before{
  content:'';position:absolute;left:0;top:.65em;
  width:7px;height:7px;border:1px solid var(--cedar);transform:rotate(45deg);
}

/* ── Booking widget ─────────────────────────────────────────── */
.booking-bar{
  background:var(--white);border:1px solid var(--line);border-radius:var(--radius);
  padding:1.5rem;box-shadow:var(--shadow);
}
.booking-bar__grid{display:grid;gap:1rem}
@media(min-width:760px){
  .booking-bar__grid{grid-template-columns:1fr 1fr .8fr auto;align-items:end}
}
.field{display:flex;flex-direction:column;gap:.4rem}
.field label{
  font-size:.72rem;letter-spacing:.14em;text-transform:uppercase;
  color:var(--muted);font-weight:500;
}
html[lang="th"] .field label{letter-spacing:.02em;text-transform:none;font-size:.85rem}
.field input,.field select,.field textarea{
  font-family:var(--sans);font-size:1rem;font-weight:300;color:var(--ink);
  background:var(--paper);border:1px solid var(--line);border-radius:var(--radius);
  padding:.8rem .9rem;width:100%;
  transition:border-color .2s var(--ease),box-shadow .2s var(--ease);
}
.field input:focus,.field select:focus,.field textarea:focus{
  outline:none;border-color:var(--indigo);box-shadow:0 0 0 3px rgba(44,74,94,.12);
}
.field textarea{min-height:120px;resize:vertical}
.field__error{color:#9B2C2C;font-size:.82rem}

/* ── Availability result ────────────────────────────────────── */
.avail{margin-top:1.5rem}
.avail__row{
  display:flex;flex-wrap:wrap;align-items:center;gap:1rem;justify-content:space-between;
  padding:1.15rem 0;border-bottom:1px solid var(--line);
}
/* ── Prices must use LINING figures ──────────────────────────────────────
   Found 19 Aug 2026 on the booking summary: "฿7,300" read as though the ฿ was
   printed on top of the 7.

   The first diagnosis was wrong and worth recording. It is NOT a missing
   glyph and NOT a font-fallback problem — a side-by-side test of six stacks
   showed every one of them drawing ฿ identically. Cormorant Garamond sets
   OLD-STYLE figures by default: 3, 7 and 9 hang below the baseline and 1 is
   x-height. Beside a full-height ฿ they interleave, and at 1.25rem that reads
   as an overlap.

   Old-style figures are lovely in running prose and wrong in a column of
   money, which is exactly what this element is. lining-nums also makes the
   totals align with each other.

   Applies to EVERY price on the site, not just the booking page — the search
   results use this class too. */
.avail__price{
  font-family:var(--serif);font-size:1.6rem;color:var(--ink);
  font-variant-numeric:lining-nums tabular-nums;
  font-feature-settings:'lnum' 1,'tnum' 1;   /* for engines that ignore the above */
}
html[lang="th"] .avail__price{font-family:var(--serif-th)}
.avail__note{font-size:.85rem;color:var(--muted)}
.avail--empty{color:var(--muted);padding:1.5rem 0}
.is-loading{opacity:.5;pointer-events:none}

/* ── Deposit callout ────────────────────────────────────────── */
.callout{
  border-left:2px solid var(--cedar);padding:1rem 0 1rem 1.25rem;
  background:none;color:var(--ink-soft);font-size:.95rem;
}

/* ── Testimonial ────────────────────────────────────────────── */
.quote{max-width:44ch;margin-inline:auto;text-align:center}
.quote blockquote{
  font-family:var(--serif);font-size:clamp(1.35rem,1.1rem + 1vw,1.9rem);
  line-height:1.45;margin:0 0 1.25rem;color:var(--ink);
}
html[lang="th"] .quote blockquote{font-family:var(--serif-th);line-height:1.7}
.quote cite{font-style:normal;font-size:.82rem;letter-spacing:.14em;text-transform:uppercase;color:var(--muted)}
/* On the dark band the base ink colour drops to ~1.4:1 — override explicitly. */
.section--ink .quote blockquote{color:#FBF8F3}
.section--ink .quote cite{color:#B8AEA3}
.stars{color:var(--cedar);letter-spacing:.3em;margin-bottom:1rem}

/* ── Gallery strip ──────────────────────────────────────────── */
/* A fixed repeat(4) left a hole whenever a strip did not hold exactly four
   images — which happened the moment house 2 lost the porch photograph and
   the café strip dropped to three. auto-fit divides whatever is there. */
.strip{display:grid;grid-template-columns:repeat(auto-fit,minmax(150px,1fr));gap:2px}
@media(min-width:820px){.strip{grid-template-columns:repeat(auto-fit,minmax(240px,1fr))}}
.strip img{aspect-ratio:1;object-fit:cover;width:100%}

/* ── Footer ─────────────────────────────────────────────────── */
.site-footer{background:var(--ink);color:#B8AEA3;padding-block:clamp(3rem,6vw,5rem) 2rem;font-size:.92rem}
.site-footer h4{color:var(--paper);font-size:.75rem;letter-spacing:.18em;text-transform:uppercase;margin-bottom:1rem}
html[lang="th"] .site-footer h4{letter-spacing:.04em;text-transform:none;font-size:.95rem}
.site-footer a{color:#B8AEA3}
.site-footer a:hover{color:var(--paper)}
.site-footer__grid{display:grid;gap:2.5rem}
@media(min-width:760px){.site-footer__grid{grid-template-columns:1.4fr 1fr 1fr 1fr}}
.site-footer ul{list-style:none;margin:0;padding:0;display:grid;gap:.6rem}
.site-footer__bottom{
  margin-top:3rem;padding-top:1.5rem;border-top:1px solid rgba(255,255,255,.1);
  display:flex;flex-wrap:wrap;gap:1rem;justify-content:space-between;
  font-size:.8rem;color:#8A8077;
}

/* ── Utilities ──────────────────────────────────────────────── */
.sr-only{
  position:absolute;width:1px;height:1px;padding:0;margin:-1px;
  overflow:hidden;clip:rect(0,0,0,0);white-space:nowrap;border:0;
}
[hidden]{display:none!important}

/* ── Mobile ─────────────────────────────────────────────────────
   90%+ of traffic is phones. Everything above this point was written
   desktop-first with only min-width:1040px overrides, which left the
   header laying out at ~427px inside a 375px viewport — the page
   scrolled sideways on every phone, which is what made it feel broken.
   Measured on a 390px viewport, 9 August 2026.
   ───────────────────────────────────────────────────────────── */

/* Belt and braces. A single stray element should degrade gracefully,
   not turn the whole document into a horizontal scroller.
   clip rather than hidden, so position:sticky still works. */
html{overflow-x:clip}

@media(max-width:1039px){
  /* The header row was the actual cause of the sideways scroll:
     brand (211px) + gap (24px) + actions (192px) = 427px in 375px. */
  .site-header__inner{gap:.75rem;min-height:64px}
  .brand{gap:.5rem;min-width:0}
  .brand img{height:38px}
  .brand__name{
    font-size:1.1rem;
    white-space:nowrap;overflow:hidden;text-overflow:ellipsis;
  }
  .header-actions{gap:.5rem;flex:0 0 auto}

  /* ── REVERSED 17 Aug 2026 ──────────────────────────────────────
     This used to be display:none, with the switch living only inside the
     hamburger menu. That was the right call for space and the wrong call
     for the audience: ~90% of visitors are Thai and most are on a phone,
     so the one control they are most likely to need was the one control
     hidden behind a menu icon.

     The space is bought back below — the wordmark now goes at 600px
     instead of 380px. On a Thai-first site the language control earns its
     place in the header ahead of the word "Aimei Bang Saray" next to a
     logo that already says it. */
  .header-actions .lang-switch{
    display:inline-flex;align-items:center;gap:.3rem;
    padding:.5rem .6rem;font-size:.78rem;letter-spacing:.04em;
  }
  /* Thai glyphs carry ascenders and descenders that clip at small sizes. */
  .header-actions .lang-switch span{line-height:1.5}

  /* Keep the booking CTA in the header. It is the whole point of the
     site, so it stays visible rather than being buried in the menu. */
  .header-actions .btn{padding:.65rem .85rem;font-size:.72rem;letter-spacing:.08em}

  .nav-mobile__lang{
    display:inline-block;margin-top:1.25rem;
    border:1px solid var(--line);border-radius:var(--radius);
    padding:.6rem 1rem;font-size:.85rem;letter-spacing:.1em;text-transform:uppercase;
    color:var(--muted);
  }
}

/* Very small phones (iPhone SE and similar): drop the wordmark, keep
   the mark. 44 + 12 + 80 + 8 + 44 = 188px of chrome, which leaves the
   CTA and the menu button room to breathe. */
/* Was max-width:379px. Widened to 600px on 17 Aug 2026 to make room for the
   language switch in the header — see the note above. The logo still carries
   the brand, so what is lost is a repetition, not the identity. */
@media(max-width:600px){
  .brand__name{display:none}
}

@media(max-width:600px){
  /* Two ragged part-width buttons read as a mistake on a narrow screen.
     Full width, stacked, equal — and each one is a comfortable target. */
  .hero__cta{gap:.65rem;margin-top:1.5rem}
  .hero__cta .btn{width:100%;justify-content:center}

  /* .22em tracking on a 29-character eyebrow wraps mid-phrase and looks
     like a bug. Tighten it so it holds one or two clean lines. */
  .eyebrow{letter-spacing:.14em;font-size:.68rem}

  /* The hero was reserving 82vh before a phone user saw anything below
     it. Still generous, but it no longer hides the whole page. */
  /* ── aspect-ratio:auto added 17 Aug 2026. Two bugs, one cause. ──────
     `aspect-ratio` only sizes whichever dimension is auto, and it makes the
     other one DEFINITE. Once min-height above pinned the height, the 4/5 ratio
     started driving the width — and the height stopped responding to content.

     1. WIDTH. The hero sized itself from the ratio rather than the viewport,
        then floored at the copy's min-content width: a hero locked at 446px on
        every phone. 126px too wide at 320, 56px too wide at 390.
        html{overflow-x:clip} meant this never showed up as a sideways scroll —
        the right-hand edge of the hero text was just quietly cut off, which is
        why it read as "doesn't fit" rather than as a scrolling bug.

     2. HEIGHT. A definite height cannot grow, so on the English homepage —
        three lines of headline, four of body, two stacked full-width buttons —
        the second CTA overflowed the hero and was covered by the booking card.
        Thai never showed it: the copy is shorter and fitted inside.

     Dropping the ratio on phones fixes both. min-height still guarantees a
     generous hero; content decides when it needs more. The portrait framing
     the ratio was there to protect is preserved anyway by
     object-fit:cover on the image. Desktop is untouched. */
  .hero{aspect-ratio:auto;min-height:clamp(440px,68vh,620px)}
  .hero p{font-size:1.05rem}

  /* Card padding at 1.75rem eats a third of a 375px screen. */
  .card__body{padding:1.25rem}

  /* Body copy at weight 300 is thin on a phone in daylight. */
  body{font-weight:400}
}

/* Minimum comfortable touch target. The language switch was 37px tall
   and the menu button was being squeezed to 34px wide by the overflow. */
@media(pointer:coarse){
  .nav-toggle{flex:0 0 44px;width:44px;height:44px}
  .lang-switch{min-height:44px;display:inline-flex;align-items:center}
  .nav-mobile a{padding-block:1rem}
}


/* ═══════════════════════════════════════════════════════════════
   Photography-led additions — 14 August 2026
   ───────────────────────────────────────────────────────────────
   Andrew's brief: "stylish, lavish, an experience", and a Japanese
   lifestyle feel the site did not have. The type system above was
   already right, so nothing here changes it. What was missing was
   somewhere to PUT photographs: the old page had one hero, one inline
   image and a 4-up decorative strip, and that was the whole of it.

   Three new components, in order of weight:
     .hero--rotating   three heroes on a slow crossfade
     .experience       full-bleed editorial row, image and text paired
     .lifestyle-band   tall portrait band, four abreast

   The hero comment further up says the library is portrait-first and a
   wide band therefore throws the picture away. That was true of the 43
   images we had on 10 August. It is no longer true — 31 of the 74
   photographs Andrew selected on 14 August are landscape, nine of them
   comfortably wide enough to carry a hero. Hence .hero--rotating sets
   its own aspect ratio rather than inheriting the 9/16 phone shape.
   ═══════════════════════════════════════════════════════════════ */

/* ── Rotating hero ──────────────────────────────────────────────
   Three photographs, eight seconds each, two-second crossfade. Only
   the first is fetched eagerly; the other two are lazy, so the LCP is
   unchanged from the single-image hero.

   The slides are siblings inside .hero__media rather than background
   images, so they keep their intrinsic dimensions.

   (This used to say "and the browser can still pick the webp". It cannot:
   there is no <picture> and no srcset anywhere on the site, so the .webp
   twins in assets/img are never requested. Corrected 20 Aug 2026 — the
   comment described an intention as though it were behaviour.)      */
.hero--rotating .hero__media{position:absolute;inset:0;z-index:-2;overflow:hidden}
.hero--rotating .hero__slide{
  position:absolute;inset:0;
  opacity:0;
  transition:opacity 2s var(--ease);
  will-change:opacity;
}
.hero--rotating .hero__slide.is-current{opacity:1}
.hero--rotating .hero__slide img{width:100%;height:100%;object-fit:cover;object-position:center}

/* Before the script runs — and if it never runs, because JS failed or
   is blocked — the first slide must still be visible. */
.hero--rotating .hero__slide:first-child{opacity:1}
.hero--rotating.is-live .hero__slide:first-child{opacity:0}
.hero--rotating.is-live .hero__slide.is-current{opacity:1}

/* All three chosen heroes are landscape, so the phone no longer needs
   the 9/16 shape that portrait photography forced. */
.hero--rotating{aspect-ratio:4/5}
@media(min-width:768px){
  .hero--rotating{aspect-ratio:auto}
  .hero--rotating .hero__slide img{object-position:center 45%}
}

/* The scrim.
   The old hero was aimei-onsen.jpg — an interior, shot in low light, so
   a light wash was enough to hold white type. All three of these are
   bright outdoor daylight, and worse, they are BUSY: the approach shot
   puts a pale grey building and a white wall directly behind the
   headline. A gentle gradient cannot hold white type over that, and a
   first attempt at .72 on the left still did not — Andrew could not read
   it, and he was right.

   So this is not a gradient trying to be subtle. It is a genuine dark
   panel under the words, fading out before it reaches the torii and the
   path on the right. Two layers:

     1  a left-to-right wash, .90 at the left edge, gone by 72%
     2  a global bottom-up wash, so the CTA row and the lower third of
        the frame never float on bare photograph either

   The right quarter of the picture is untouched, which is where the
   colour is. Anyone reducing these numbers should re-measure the
   contrast of .hero .eyebrow first — it is the smallest text and it
   fails long before the h1 does. */
.hero--rotating::after{
  background:
    linear-gradient(to top,
      rgba(14,11,10,.92) 0%,
      rgba(14,11,10,.72) 38%,
      rgba(14,11,10,.34) 70%,
      rgba(14,11,10,.18) 100%);
}
@media(min-width:768px){
  .hero--rotating::after{
    background:
      linear-gradient(to right,
        rgba(14,11,10,.90) 0%,
        rgba(14,11,10,.82) 30%,
        rgba(14,11,10,.55) 52%,
        rgba(14,11,10,0)   72%),
      linear-gradient(to top,
        rgba(14,11,10,.55) 0%,
        rgba(14,11,10,.28) 45%,
        rgba(14,11,10,.20) 100%);
  }
}

/* Belt and braces. The scrim above does the work; this stops a single
   bright highlight in a future photograph from eating a letterform. */
.hero--rotating h1,
.hero--rotating .hero__inner > p,
.hero--rotating .eyebrow{
  text-shadow:0 1px 12px rgba(10,8,7,.55);
}
/* The eyebrow was rgba(255,255,255,.75) — 25% down on white before the
   photograph is even considered. On a hero this size it is the first
   thing to become unreadable. */
.hero--rotating .eyebrow{color:rgba(255,255,255,.95)}
.hero--rotating p{color:rgba(255,255,255,.96)}

/* ── The same treatment for every other hero ──────────────────
   On 14 Aug Andrew could not read the white text on the homepage hero, and
   the fix — heavier scrim, text-shadow, brighter eyebrow — was applied only
   to .hero--rotating. The house, café, wellness and about heroes were left
   on the weaker base treatment.

   That surfaced on 16 Aug: house 2's new frontage photograph has white
   gravel and sunlit bamboo, so the brightest pixels directly behind the
   eyebrow measured 247/255. White on that is invisible no matter how good
   the average contrast looks. The average was 9.8:1; the worst case was
   1.07:1.

   Judge a hero by its brightest pixel behind the smallest text, not by the
   average. The eyebrow fails long before the h1 does.                     */
.hero h1,
.hero .hero__inner > p,
.hero .eyebrow{text-shadow:0 1px 12px rgba(10,8,7,.55)}
.hero .eyebrow{color:rgba(255,255,255,.95)}
.hero p{color:rgba(255,255,255,.96)}
@media (min-width:768px){
  .hero:not(.hero--rotating)::after{
    background:linear-gradient(to top,rgba(20,16,14,.80) 0%,rgba(20,16,14,.38) 50%,rgba(20,16,14,.15) 100%);
  }
}

/* Reduced motion: no crossfade, no rotation. The script checks the same
   query and simply never advances, so the first slide stands alone. */
@media (prefers-reduced-motion:reduce){
  .hero--rotating .hero__slide{transition:none}
}

/* ── Experience rows ────────────────────────────────────────────
   An alternating editorial row: one large photograph, one column of
   text. Every second row reverses, so the eye zig-zags down the page
   instead of running down a single column of left-aligned images.   */
.experience{
  display:grid;
  gap:clamp(1.75rem,4vw,3.5rem);
  align-items:center;
  padding-block:clamp(2.5rem,5vw,4.5rem);
}
@media(min-width:900px){
  .experience{grid-template-columns:1.05fr .95fr;gap:clamp(3rem,6vw,6rem)}
  /* Explicit, not :nth-child. The rows share a parent with the section
     heading, so an nth-child rule flips every row the day someone
     removes or adds anything above them. The markup states which side
     the photograph is on and the CSS does as it is told. */
  .experience--flip .experience__media{order:2}
}

.experience__media{position:relative}
.experience__media img{
  width:100%;
  aspect-ratio:4/5;
  object-fit:cover;
  border-radius:var(--radius);
}
@media(min-width:900px){
  /* Landscape sources get a landscape frame; portrait sources keep the
     tall crop. Set by the markup, not guessed here. */
  .experience__media--wide img{aspect-ratio:4/3}
}

.experience__body h3{margin-bottom:.5rem}
.experience__body p{color:var(--ink-soft)}
.experience__body p:last-child{margin-bottom:0}

/* The Japanese term, set small above the heading. Kana first, then the
   romaji — the same order a Japanese ryokan would use. */
.jp-term{
  font-family:var(--serif);
  font-size:1.35rem;
  letter-spacing:.06em;
  color:var(--cedar);
  margin:0 0 .35rem;
  line-height:1;
}
.jp-term span{
  display:block;
  font-family:var(--sans);
  font-size:.7rem;
  font-weight:500;
  letter-spacing:.22em;
  text-transform:uppercase;
  color:var(--muted);
  margin-top:.5rem;
}
html[lang="th"] .jp-term span{letter-spacing:.08em}

/* ── Lifestyle band ─────────────────────────────────────────────
   Full-bleed, edge to edge, four portraits abreast on a wide screen
   and two on a phone. Deliberately tall — this is the part of the page
   that is meant to feel like a magazine spread rather than a website.
   Decorative, so the markup marks it aria-hidden and the images carry
   empty alt text.                                                  */
.lifestyle-band{
  display:grid;
  grid-template-columns:repeat(2,1fr);
  gap:2px;
  background:var(--paper-2);
}
@media(min-width:820px){
  .lifestyle-band{grid-template-columns:repeat(4,1fr)}
}
.lifestyle-band img{
  width:100%;
  aspect-ratio:3/4;
  object-fit:cover;
  transition:transform .8s var(--ease),filter .8s var(--ease);
}
@media(min-width:820px){
  .lifestyle-band img{aspect-ratio:2/3}
}
/* A slow, small push on hover. Enough to feel alive, not enough to be
   a distraction on a page whose job is to get someone to book. */
@media(hover:hover){
  .lifestyle-band img:hover{transform:scale(1.03)}
}

/* ── House cards ────────────────────────────────────────────────
   The three house heroes are Andrew's own exterior choices and all
   three are PORTRAIT — dusk shots of each building. A 4:3 card with
   the default centre crop takes the middle band and throws away the
   roofline, which on a two-storey Japanese house is the whole point
   of the photograph.

   Lifting the crop to 38% keeps the roof, the upper storey and the
   lit windows, and loses paving off the bottom, which nobody misses.
   Applies wherever a house card appears — the houses page and the
   home page grid both use house_card(). */
.card--house .card__media img{object-position:center 38%}

/* ── Section heading with a rule ────────────────────────────────
   A hairline in cedar under the eyebrow. Cheap, and it does more for
   the "lavish" brief than any amount of extra ornament would.      */
.section-head{max-width:56ch;margin-inline:auto;text-align:center}
.section-head .eyebrow{margin-bottom:.9rem}
.section-head .eyebrow::after{
  content:'';display:block;width:2.5rem;height:1px;
  background:var(--cedar);margin:.9rem auto 0;
}
.section-head h2{margin-bottom:.5rem}
.section-head .lede{margin-inline:auto}


/* ── Guest ratings ──────────────────────────────────────────────
   Sits under the testimonial on the dark band. Each entry is a link to
   the platform it came from, because the entire value of a score over a
   quote is that the reader can go and check it.

   Deliberately restrained: this is evidence, not a badge. No logos, no
   coloured pills, no five-star graphics — the quote above already has
   the stars, and repeating them would read as decoration rather than
   fact. Numerals large, source small, both in the same cedar-to-cream
   register as the rest of the dark band.                            */
.ratings{
  list-style:none;
  margin:2.5rem 0 0;
  padding:0;
  display:flex;
  flex-wrap:wrap;
  gap:1rem 2.5rem;
  justify-content:center;
}
.ratings a{
  display:block;
  color:inherit;
  text-decoration:none;
  padding:.35rem .25rem;
  border-bottom:1px solid transparent;
  transition:border-color .25s var(--ease);
}
.ratings a:hover{border-bottom-color:var(--cedar);color:inherit}
.ratings a:focus-visible{outline:2px solid var(--cedar);outline-offset:3px}

.ratings__score{
  display:block;
  font-family:var(--serif);
  font-size:2.1rem;
  line-height:1;
  color:#FBF8F3;
}
html[lang="th"] .ratings__score{font-family:var(--serif-th)}
/* The denominator is context, not the number itself — 8.2 is the fact,
   "/10" only tells you how to read it. Same trick as a price tag. */
.ratings__scale{font-size:.55em;color:#B8AEA3;margin-left:.05em}

.ratings__meta{
  display:block;
  margin-top:.4rem;
  font-size:.72rem;
  letter-spacing:.14em;
  text-transform:uppercase;
  color:#B8AEA3;
}
html[lang="th"] .ratings__meta{letter-spacing:.04em;text-transform:none;font-size:.85rem}

@media(max-width:600px){
  .ratings{gap:1rem 1.75rem;margin-top:2rem}
  .ratings__score{font-size:1.8rem}
}


/* ── Chosen guest reviews ───────────────────────────────────────
   One quote reads as a pull-quote and keeps the old narrow measure.
   Two or three sit side by side, and the measure has to open up or
   each column ends up four words wide — .quote is capped at 44ch,
   which is right for one and wrong for three.                     */
/* .quote is max-width:44ch. That is correct for a single pull-quote and
   fatal for a grid — three columns inside 44ch just stack. index.php adds
   quote--wide when there is more than one review. */
.quote--wide{max-width:var(--wrap)}

.reviews--solo{max-width:44ch;margin-inline:auto}

.reviews--row{
  display:grid;
  gap:clamp(2.5rem,5vw,4rem);
  max-width:none;
  margin-inline:0;
}
@media(min-width:820px){
  /* auto-fit, so two reviews centre as two and three fall into three
     without the markup needing to know how many there are. */
  .reviews--row{grid-template-columns:repeat(auto-fit,minmax(16rem,1fr))}
}

.review{margin:0}
.review blockquote{
  font-family:var(--serif);
  font-size:clamp(1.35rem,1.1rem + 1vw,1.9rem);
  line-height:1.45;
  margin:0 0 1.25rem;
  color:#FBF8F3;
}
html[lang="th"] .review blockquote{font-family:var(--serif-th);line-height:1.7}
/* Shorter measure and a step down in size once they are side by side —
   a 1.9rem serif in a 16rem column is unreadable. */
.reviews--row .review blockquote{
  font-size:clamp(1.15rem,1rem + .5vw,1.4rem);
}
.review figcaption{margin:0}
.review cite{
  font-style:normal;
  font-size:.82rem;
  letter-spacing:.14em;
  text-transform:uppercase;
  color:#B8AEA3;
}
/* Thai names and platform words do not survive uppercase + wide tracking. */
html[lang="th"] .review cite{letter-spacing:.04em;text-transform:none;font-size:.9rem}
.review .stars{margin-bottom:1rem}


/* ── Availability calendar ──────────────────────────────────────
   17 August 2026. Andrew: "can we also show the availability in full
   calendar form" — one month grid per house, green available, red booked,
   no prices.

   NOT COLOUR-ONLY. Around 8% of men cannot separate red from green, and
   red/green is the worst possible pair for them. So the state is carried
   three ways over: fill, weight, and a struck-through numeral on a booked
   night. Turn the page greyscale and it still reads correctly — that is
   the test to apply before changing any of these colours.

   The greens and reds are desaturated into the washi palette on purpose.
   A pure #22C55E / #EF4444 pair is a dashboard, not a ryokan.            */
.cal{
  background:var(--white);
  border:1px solid var(--line);
  border-radius:var(--radius);
  padding:clamp(1rem,3vw,1.5rem);
  max-width:26rem;
}
.cal[data-loading]{opacity:.55;transition:opacity .15s var(--ease)}

.cal__head{
  display:flex;align-items:center;justify-content:space-between;gap:.5rem;
  margin-bottom:1rem;
}
.cal__month{
  margin:0;font-size:1.15rem;text-align:center;flex:1;
  /* The label is swapped by JS. Without a floor the whole card jumps a few
     pixels between "May 2027" and "September 2026". */
  min-height:1.4em;
}
.cal__nav{
  flex:0 0 auto;
  width:44px;height:44px;          /* full touch target — 90% of traffic is phones */
  display:inline-flex;align-items:center;justify-content:center;
  font-size:1.4rem;line-height:1;
  background:none;border:1px solid var(--line);border-radius:var(--radius);
  color:var(--ink);cursor:pointer;
  transition:border-color .2s var(--ease),color .2s var(--ease);
}
.cal__nav:hover:not([disabled]){border-color:var(--cedar);color:var(--cedar)}
.cal__nav[disabled]{opacity:.3;cursor:default}
.cal__nav:focus-visible{outline:2px solid var(--cedar);outline-offset:2px}

.cal__grid{
  width:100%;
  border-collapse:separate;
  border-spacing:3px;
  table-layout:fixed;              /* seven equal columns whatever the numerals */
}
.cal__grid th{
  font-family:var(--sans);
  font-size:.66rem;font-weight:500;
  letter-spacing:.1em;text-transform:uppercase;
  color:var(--muted);
  padding-bottom:.35rem;
}
/* Thai weekday abbreviations are already short and carry tone marks that
   uppercase tracking pulls apart. */
html[lang="th"] .cal__grid th{letter-spacing:0;text-transform:none;font-size:.8rem}

.cal__grid td{
  /* NOT aspect-ratio. It is ignored on display:table-cell, so the first cut of
     this shipped short letterbox cells that looked like a mistake. An explicit
     height is honoured, and against a ~48px column it lands near enough square
     at every width we care about. */
  height:2.6rem;
  text-align:center;
  vertical-align:middle;
  font-size:.9rem;
  font-variant-numeric:tabular-nums;
  border-radius:var(--radius);
  color:var(--ink-soft);
}
.cal__grid td.is-blank{background:none}

/* Free. Solid tint, full-contrast numeral, medium weight — the state you
   want a guest to notice first. */
.cal__grid td.is-free{
  background:#E4EDE2;
  color:#2F4A2C;
  font-weight:500;
}
/* Booked. Dimmer, lighter, and struck through, so it reads as "gone"
   without relying on the hue at all. */
.cal__grid td.is-booked{
  background:#F2E4E2;
  color:#8A4B45;
  text-decoration:line-through;
  text-decoration-thickness:1px;
}
/* Already gone, and nothing to say about it. */
.cal__grid td.is-past{color:#C6BFB6}
/* Beds24 returned nothing for this date. Say nothing rather than guess —
   a wrong green is a guest disappointed at the booking step. */
.cal__grid td.is-unknown{background:var(--paper-2);color:var(--muted)}

.cal__key{
  display:flex;flex-wrap:wrap;align-items:center;gap:.4rem 1.1rem;
  margin:1rem 0 .5rem;
  font-size:.78rem;color:var(--ink-soft);
  max-width:none;
}
.cal__swatch{
  display:inline-block;width:.85rem;height:.85rem;
  border-radius:var(--radius);margin-right:.35rem;
  vertical-align:-.1em;
}
.cal__swatch.is-free{background:#E4EDE2;border:1px solid #B9CDB4}
.cal__swatch.is-booked{background:#F2E4E2;border:1px solid #D9B5B0}

.cal__note{margin:0;font-size:.8rem;color:var(--muted);max-width:none}
.cal__error{margin:0;padding:1.5rem 0;color:var(--muted);font-size:.9rem}

/* On a phone the card is the full column width — a 26rem cap would leave
   an odd gutter on the right at 390px. */
@media(max-width:600px){
  .cal{max-width:none;padding:1rem .75rem}
  .cal__grid{border-spacing:2px}
  .cal__grid td{font-size:.85rem}
}

/* High contrast / forced colours: the tints are dropped by the OS, so the
   line-through and the aria-label are all that is left. Both survive. */
@media (forced-colors:active){
  .cal__grid td.is-free{border:1px solid CanvasText}
}

/* The block that holds the calendar on a house page. Separated from .cal
   itself so the grid can be dropped anywhere else later without dragging
   this spacing along with it. */
/* #availability is a real anchor — the header is sticky, so without this the
   heading lands underneath it and the guest sees a headless grid. */
.cal-wrap{margin-top:clamp(1.75rem,4vw,2.5rem);scroll-margin-top:90px}
.cal-wrap__title{
  font-size:clamp(1.3rem,1.15rem + .7vw,1.7rem);
  margin-bottom:1rem;
}

/* ── Booking extras ─────────────────────────────────────────────
   19 August 2026. The cot and pet prices were published in the policy text
   and nowhere else, so a guest could read that a cot costs money and find no
   way to ask for one. They either did not book, or arrived expecting a cot
   nobody knew about.

   A fieldset, because these rows genuinely are one group of related choices
   and a screen reader should say so. */
.extras{
  border:1px solid var(--line);
  border-radius:var(--radius);
  padding:1.25rem;
  margin:0;
  background:var(--white);
}
.extras legend{
  font-family:var(--sans);
  font-size:.72rem;font-weight:500;
  letter-spacing:.14em;text-transform:uppercase;
  color:var(--muted);
  padding:0 .5rem;
}
html[lang="th"] .extras legend{letter-spacing:.02em;text-transform:none;font-size:.85rem}

.extras__intro{font-size:.85rem;margin:0 0 1rem}

.extras__row{
  display:flex;align-items:center;justify-content:space-between;gap:1rem;
  padding:.7rem 0;
  border-top:1px solid var(--line);
}
.extras__row:first-of-type{border-top:0;padding-top:0}
.extras__row label{display:block;font-size:.98rem;color:var(--ink)}
.extras__price{display:block;font-size:.85rem;color:var(--muted)}

/* Wide enough for a comfortable tap, narrow enough that it reads as a
   quantity and not as the main control on the row. */
.extras__row select{
  flex:0 0 auto;width:6.5rem;min-height:44px;
  font-family:var(--sans);font-size:1rem;color:var(--ink);
  background:var(--paper);border:1px solid var(--line);border-radius:var(--radius);
  padding:.5rem .6rem;
}
.extras__row select:focus{
  outline:none;border-color:var(--indigo);box-shadow:0 0 0 3px rgba(44,74,94,.12);
}

.extras__note{font-size:.82rem;margin:1rem 0 0;padding-top:.9rem;border-top:1px solid var(--line)}

@media(max-width:420px){
  .extras{padding:1rem}
  .extras__row select{width:5.5rem}
}


/* ═══════════════════════════════════════════════════════════════
   AIMEI-DESIGN-2026-09-10 — dark fields, measured palette, booking panel
   ───────────────────────────────────────────────────────────────
   Andrew, 10 Sep: the site needed a stronger Japanese character, and the
   availability search "should be a feature to encourage them" rather than a
   plain form card.

   EVERY COLOUR BELOW WAS MEASURED FROM AIMEI'S OWN PHOTOGRAPHS OR ITS OWN
   LOGO FILE, not chosen. Scripts and full workings in _local-tests/
   (sample-torii.py, sample-indigo.py, sample-logo.py, contrast.py) and in the
   project doc Aimei-Palette-Measured.md.

     --field       #2A3140  the indigo in 65,353 px of the yukata photographs
     --gold        #E59C2D  the sun in aimei-logo.png            5.66:1 on field
     --shu         #A30312  the torii paint, 13,481 px           7.62:1 on paper
     --brand-brown #9A5936  the logo disc (matches what was already here)

   ⚠️ THE RULE THAT CAME OUT OF MEASURING, AND IT IS NOT OPTIONAL:

       vermilion on the indigo field  = 1.60:1   invisible
       gold       on washi paper      = 2.15:1   invisible
       brand brown on the indigo field= 2.39:1   invisible

   So: SHU IS A PAPER COLOUR. GOLD IS A DARK-FIELD COLOUR. They never swap,
   and the brown button cannot be used on a dark field — there is a
   .btn--paper below for that case. Check contrast.py before adding a fourth.
   ═══════════════════════════════════════════════════════════════ */

:root{
  --field:#2A3140;
  --field-deep:#1E2430;
  --gold:#E59C2D;
  --shu:#A30312;
  --brand-brown:#9A5936;
}

/* ── The dark field ─────────────────────────────────────────────
   The single highest-impact change identified in Aimei-Design-Direction.md
   (11 Aug): "They commit to darkness. We use it as a button colour." A
   photograph on a dark ground glows; the same photograph on cream sits there.
   This is that, finally applied.

   .section--ink already existed for the testimonial band on #1F3644. It is
   re-pointed at the measured indigo here so the page has ONE dark, not two
   that nearly match. */
.section--field,
.section--ink{background:var(--field);color:#EFE9E0}
.section--field h2,.section--field h3,.section--field h4,
.section--ink h2,.section--ink h3{color:#FBF8F3}
.section--field p,.section--ink p{color:rgba(251,248,243,.80)}
.section--field .eyebrow{color:var(--gold)}
.section--field a{color:var(--gold)}
.section--field a:hover{color:#F2C070}

/* A button that survives the dark field. Brown fails there at 2.39:1. */
.btn--paper{background:var(--paper);color:var(--ink);border-color:var(--paper)}
.btn--paper:hover{background:#fff;color:var(--ink)}

/* ── The brand mark ─────────────────────────────────────────────
   A rising sun over water, drawn from aimei-logo.png rather than invented —
   the logo already carries the most Japanese mark there is, and it is also
   literally Bang Saray on the Gulf. Inline SVG using currentColor, so the
   tone is set by whichever surface it lands on.

   USE IT ONCE PER SURFACE. Repeated down a list it stops reading as a mark
   and becomes a bullet glyph. */
.sunmark{display:block;flex:0 0 auto}
.sunmark--shu{color:var(--shu)}      /* paper only  */
.sunmark--gold{color:var(--gold)}    /* dark only   */

/* ── The booking panel ──────────────────────────────────────────
   Applied by passing $bb_panel = true to partials/booking-bar.php. Without
   that flag the bar renders exactly as it always has, so the house, café and
   wellness pages are untouched by any of this.

   It straddles the hero and the field below, so it sits physically in front
   of the photograph. No box-shadow: the old .booking-bar floated on one, and
   floating is the least Japanese thing a panel can do. Paper rests on wood. */
.booking-bar--panel{
  background:var(--paper);
  border:1px solid var(--line);
  border-top:0;
  border-radius:0;
  padding:0;
  box-shadow:none;
}
/* A renji lattice — the vertical-slat window of a machiya. Hairlines, not an
   image request. */
.booking-bar--panel .bookpanel__rail{
  height:14px;background:var(--field-deep);
  background-image:repeating-linear-gradient(90deg,
      transparent 0 13px,rgba(250,247,242,.20) 13px 14px);
}
.booking-bar--panel .bookpanel__in{padding:clamp(1.4rem,3vw,2.4rem)}
.bookpanel__head{display:flex;align-items:center;gap:1rem;margin-bottom:1.6rem}
.bookpanel__head h2{margin:0;font-size:clamp(1.35rem,1.15rem + .7vw,1.7rem)}
.bookpanel__head .eyebrow{margin:.35rem 0 0;color:var(--muted)}

/* The fields become hairline-divided cells rather than boxed inputs. The
   inputs are still real inputs — the form still GETs /book and still works
   with JavaScript off. Only the chrome changes. */
@media(min-width:900px){
  .booking-bar--panel .booking-bar__grid{
    grid-template-columns:repeat(4,1fr) auto;
    gap:0;align-items:stretch;
    border-top:1px solid var(--line);border-bottom:1px solid var(--line);
  }
  /* ⚠️ .field, NOT `> *`. The submit button is itself a direct child of this
     grid, so a blanket `> *` rule was overwriting .btn's own padding with the
     cell's — the label then filled the whole button and overflowed it. English
     nearly got away with it; Thai ("เซ็กห้องว่าง") ran clean off the brown.

     Found by rendering the Thai page. Two earlier attempts to fix it by
     reading the CSS both failed, because the selector looked right and the
     fault was in what it was matching. */
  .booking-bar--panel .booking-bar__grid > .field{
    padding:1.05rem 1.3rem;border-right:1px solid var(--line);min-width:0;
  }
  .booking-bar--panel .booking-bar__grid > .field:first-child{padding-left:0}
  .booking-bar--panel .booking-bar__grid > .field:last-of-type{border-right:0}
  .booking-bar--panel .booking-bar__grid > .btn{
    margin-left:1.5rem;align-self:center;min-width:max-content;
  }
}
.booking-bar--panel .field input,
.booking-bar--panel .field select{
  border:0;background:none;padding:.1rem 0;font-size:1.02rem;border-radius:0;
}
/* Removing the border removes the focus ring with it, so put a real one back.
   Never leave a focus state to the box-shadow that was just deleted. */
.booking-bar--panel .field input:focus,
.booking-bar--panel .field select:focus{
  outline:2px solid var(--brand-brown);outline-offset:3px;box-shadow:none;
}

/* Why book here rather than on an OTA. Live approved copy — book_why_1..3,
   signed off 17 Aug, see Direct-Rate-Decision.md. The fields alone are a
   search box; these three lines are the argument, and they are the half
   Andrew asked for when he said the panel should "encourage them". */
.bookpanel__why{
  display:grid;gap:1.4rem;margin-top:1.7rem;list-style:none;padding:0;
}
@media(min-width:820px){
  .bookpanel__why{grid-template-columns:repeat(3,1fr);gap:2rem}
}
.bookpanel__why li{
  border-top:2px solid var(--shu);padding-top:.9rem;
  font-size:.92rem;line-height:1.65;color:var(--ink-soft);max-width:none;
}
.booking-bar--panel .callout{
  border-left:0;border-top:1px solid var(--line);
  padding:1.2rem 0 0;margin-top:1.5rem;font-size:.86rem;color:var(--muted);
}

/* The panel's button is the LOGO BROWN, not the site's navy.
   #9A5936 is the disc colour measured out of aimei-logo.png, and it is
   5.05:1 on paper. The navy #2C4A5E it replaces here appears in no
   photograph and on no part of the logo — and it is within a few percent of
   Yunoyama's #21344A, so the two sites have been wearing the same colour.

   ⚠️ SCOPED TO THE PANEL ON PURPOSE. Repointing .btn--primary site-wide is
   the right end state and it is a one-line change, but it would repaint every
   button on every page, which is well outside "the homepage". Andrew's call —
   raised, not taken. */
.booking-bar--panel .btn--primary{background:var(--brand-brown);border-color:var(--brand-brown)}
.booking-bar--panel .btn--primary:hover{background:#84492b;border-color:#84492b}

/* Two lines, not three. The house standard caps a desktop hero headline at
   two, and .hero h1 was capped at 16ch, which broke a 39-character English
   headline across three. English only: Thai wraps on different rules and its
   headline is shorter anyway. */
@media(min-width:768px){
  html[lang="en"] .page-home .hero h1{max-width:23ch}
}

/* The hero's own "Check availability" button, at desktop widths only.
   ⚠️ DELIBERATELY NOT REMOVED FROM THE MARKUP. On a desktop it is a third
   booking CTA within one screen — header, hero, panel — and the panel now
   straddles up into the hero, so the button is redundant there. On a PHONE
   the panel does not straddle, so removing it outright would push the only
   booking action below the fold for ~90% of the traffic. Keep it where it
   still earns its place; hide it where it does not. */
@media(min-width:768px){
  .page-home .hero__cta .btn--primary{display:none}
}

/* The straddle. Phones do not get it: the hero is already only ~440px tall
   there and pulling a tall panel up into it would eat the photograph. */
.field-lift{background:var(--field)}
.field-lift__panel{position:relative;z-index:5}
@media(min-width:768px){
  .field-lift__panel{margin-top:clamp(-5rem,-5vw,-3rem)}
}

/* ── Editorial bleed ────────────────────────────────────────────
   Alternating text-left / image-right down a whole page is on the house
   reject list — it is what makes a page read as a template. Alternating the
   BLEED direction instead gives the same variety with none of the sameness:
   one row runs off the right edge, the next off the left.

   The negative margin is worked out from the section's own width. A negative
   margin inside .wrap only cancels .wrap's padding and stops at the centred
   1180 column, which looks like an ordinary two-column grid. */
@media(min-width:900px){
  /* Both terms are needed: the margin outside the centred 1180 column AND
     .wrap's own gutter. With only the first, the image stopped one gutter
     short of the edge and the whole thing read as an ordinary two-column
     grid — the bleed has to actually touch the edge to be a bleed. */
  .experience--bleed-right{
    margin-right:calc(-1 * (max(0px,(100vw - var(--wrap)) / 2) + var(--gutter)));
  }
  .experience--bleed-left{
    margin-left:calc(-1 * (max(0px,(100vw - var(--wrap)) / 2) + var(--gutter)));
  }
  .experience--bleed-right .experience__media img,
  .experience--bleed-left .experience__media img{border-radius:0}
}

/* On the dark field the cedar term colour drops to 3.49:1 — large text only,
   and .jp-term is not large. Gold instead, which measures 5.66:1. */
.section--field .jp-term{color:var(--gold)}
.section--field .jp-term span{color:rgba(251,248,243,.66)}

/* ── Thai ───────────────────────────────────────────────────────
   The site already reduces tracking for Thai in a dozen places — .eyebrow,
   .btn, .field label, .nav a, .lang-switch all do it, and the .lang-switch
   rule states the reason. These are the same treatment for the components
   added today, so nothing new is introduced, only kept consistent.

   Thai has no letter case, so text-transform is a silent no-op; the tracking
   is the part that actually does damage, pushing combining vowels and tone
   marks away from the consonants they belong to. */
html[lang="th"] .bookpanel__head .eyebrow{letter-spacing:.04em;font-size:.85rem}
html[lang="th"] .bookpanel__why li{font-size:.95rem;line-height:1.85}
html[lang="th"] .bookpanel__head h2{font-family:var(--serif-th);line-height:1.4}

/* ── Figures ────────────────────────────────────────────────────
   Cormorant sets OLD-STYLE figures by default — 3, 7 and 9 hang below the
   baseline. Lovely in prose, wrong for a standalone number. The stylesheet
   already learned this on .avail__price (19 Aug); any new display numeral
   needs the same treatment. */
.lining{font-variant-numeric:lining-nums tabular-nums;
        font-feature-settings:'lnum' 1,'tnum' 1}

/* ── Phone ──────────────────────────────────────────────────────
   90%+ of traffic is phones, so this is the case that matters most. The
   panel stacks two-up; the button goes full width. */
@media(max-width:899px){
  .booking-bar--panel .booking-bar__grid{
    grid-template-columns:1fr 1fr;gap:1.1rem 1rem;
  }
  /* min-width:0 so a grid column may shrink below its content. Without it a
     native date input refuses to go under its intrinsic width and pushes the
     whole grid wider than the panel. */
  .booking-bar--panel .booking-bar__grid > .field{min-width:0}
  .booking-bar--panel .booking-bar__grid > button{grid-column:1 / -1;width:100%}
  .booking-bar--panel .field input,
  .booking-bar--panel .field select{
    border:1px solid var(--line);background:var(--paper);
    padding:.7rem .8rem;border-radius:var(--radius);
  }
}
@media(max-width:600px){
  .bookpanel__head{gap:.75rem;margin-bottom:1.25rem}
  .bookpanel__head .sunmark{width:34px;height:21px}
  /* ONE column, not two. A native input[type=date] carries a calendar icon and
     will not render under roughly 165px; two of them plus the gap plus the
     panel's own padding needs ~370px of the 390 a phone actually has, so the
     right-hand column ran off the panel. Measured in a real 390px viewport —
     it does not show up when the browser window is merely narrowed, which is
     how it survived the first pass. */
  .booking-bar--panel .booking-bar__grid{grid-template-columns:1fr}
}


/* ═══════════════════════════════════════════════════════════════
   AIMEI-SEIGAIHA-2026-09-10 — the panel rail as a noren
   ───────────────────────────────────────────────────────────────
   Seigaiha 青海波, once, bounded, at strength — not washed over a section.
   See _local-tests/append-seigaiha.php for the full reasoning, including why
   the subtle-overlay version was built and then thrown away.

   The pattern is on Aimei's own bedding and yukata, and it means "blue sea
   waves" fifteen minutes from the beach. It is drawn, not stock.

   Taller than the renji rail it replaces (22px vs 14px) because a pattern
   needs room to read as one; at 14px it was a smudge.

   TO REVERT: delete this block. The renji lattice above takes over again.
   ═══════════════════════════════════════════════════════════════ */
.booking-bar--panel .bookpanel__rail{
  height:22px;
  background-color:var(--field-deep);
  background-image:url("data:image/svg+xml,%3Csvg%20xmlns%3D%27http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%27%20width%3D%2760%27%20height%3D%2730%27%20viewBox%3D%270%200%2060%2030%27%3E%3Cg%20fill%3D%27none%27%20stroke%3D%27%23FAF7F2%27%20stroke-width%3D%271.5%27%20stroke-opacity%3D%270.52%27%3E%3Cpath%20d%3D%27M-27%2030%20A27%2027%200%200%201%2027%2030%27%2F%3E%3Cpath%20d%3D%27M-16%2030%20A16%2016%200%200%201%2016%2030%27%2F%3E%3Cpath%20d%3D%27M3%2030%20A27%2027%200%200%201%2057%2030%27%2F%3E%3Cpath%20d%3D%27M14%2030%20A16%2016%200%200%201%2046%2030%27%2F%3E%3Cpath%20d%3D%27M33%2030%20A27%2027%200%200%201%2087%2030%27%2F%3E%3Cpath%20d%3D%27M44%2030%20A16%2016%200%200%201%2076%2030%27%2F%3E%3C%2Fg%3E%3C%2Fsvg%3E");
  background-size:48px 24px;
  background-position:center bottom;
  background-repeat:repeat-x;
}
@media(max-width:600px){
  /* Smaller tile on a phone, or three waves span the whole width and it stops
     reading as a repeat. */
  .booking-bar--panel .bookpanel__rail{height:18px;background-size:36px 18px}
}

/* ═══════════════════════════════════════════════════════════════
   AIMEI-BROWN-2026-09-10 — the navy retires as an action colour
   ───────────────────────────────────────────────────────────────
   #2C4A5E was in :root from the original build. It is in no photograph and on
   no part of the logo, and it sits within a few percent of Yunoyama's #21344A
   — so the two sites have been wearing the same blue.

   #9A5936 is Aimei's own: 135,140 px sampled off the disc in aimei-logo.png
   came back #9A5A36, one digit from the --brand already in this file.

   CONTRAST, measured not assumed:
     white on the brown        5.45:1   AA
     brown text on washi       5.05:1   AA
     brown on the dark field   2.39:1   FAILS — hence the guard rule at the end

   ⚠️ The dark indigo FIELD is untouched. Ground, not accent.
   ⚠️ --indigo stays defined but unused. Retired, not repointed: a token named
      --indigo holding a brown is a trap for the next person to read this.
   ═══════════════════════════════════════════════════════════════ */

.btn--primary{background:var(--brand-brown);border-color:var(--brand-brown)}
.btn--primary:hover{background:#84492B;border-color:#84492B}

/* Links. --cedar is already a brown, so the hover needs no change. */
a{color:var(--brand-brown)}

/* The light button sits on photographs; its hover fills white and the label
   has to come back in the brand colour rather than the retired navy. */
.btn--light:hover{background:#fff;color:var(--brand-brown)}

/* Focus rings. These were navy with a navy glow — easy to miss, and a focus
   state that does not match the button it belongs to reads as a bug. */
.field input:focus,.field select:focus,.field textarea:focus,
.extras__row select:focus{
  border-color:var(--brand-brown);
  box-shadow:0 0 0 3px rgba(154,89,54,.14);
}

/* The panel's own override is now the same as the global rule, so it is
   redundant — left in place rather than deleted, because deleting it would
   mean editing the block above and this costs nothing. */

/* ⚠️ GUARD. Brown measures 2.39:1 on the indigo field — invisible. Any primary
   button that ends up on a dark section gets the paper treatment instead, so a
   future section cannot fail silently. */
.section--field .btn--primary,
.section--ink .btn--primary{
  background:var(--paper);color:var(--ink);border-color:var(--paper);
}
.section--field .btn--primary:hover,
.section--ink .btn--primary:hover{background:#fff;color:var(--ink)}

/* ═══════════════════════════════════════════════════════════════
   AIMEI-FIELD-SAFETY-2026-09-10 — every component, made safe on the dark field
   ───────────────────────────────────────────────────────────────
   Promoting a section to section--field brings its components with it. These
   are the ones defined in ink or cedar, which are invisible or borderline on
   #2A3140. Measured, not assumed — see the table in
   _local-tests/append-field-safety.php.
   ═══════════════════════════════════════════════════════════════ */

/* Ledes and muted copy. */
.section--field .lede{color:rgba(251,248,243,.86)}
.section--field .muted{color:rgba(251,248,243,.62)}
.section--field h4{color:#FBF8F3}

/* Cards. White cards on indigo read as paper laid on cloth, which is right —
   but the border was drawn for a cream page and disappears here. */
.section--field .card{border-color:rgba(251,248,243,.16)}
.section--field .card__media{background:var(--field-deep)}

/* Feature lists. The cedar diamond marker is 3.49:1 here — large-text only, and
   a 7px diamond is not large text. */
.section--field .features li{color:rgba(251,248,243,.84)}
.section--field .features li::before{border-color:var(--gold)}

/* The deposit callout: cedar rule becomes gold, copy comes up to paper. */
.section--field .callout{border-left-color:var(--gold);color:rgba(251,248,243,.84)}

/* Ghost buttons are ink-on-transparent — invisible on the field. */
.section--field .btn--ghost{border-color:rgba(251,248,243,.55);color:#FBF8F3;background:none}
.section--field .btn--ghost:hover{background:var(--paper);color:var(--ink)}

/* Gallery strips sit on paper-2 by default, which flashes light between tiles. */
.section--field .strip{background:var(--field-deep)}

/* Section heads and the Japanese term, already handled for the homepage, made
   general here so a promoted section on any page inherits them. */
.section--field .eyebrow{color:var(--gold)}
.section--field .jp-term{color:var(--gold)}
.section--field .jp-term span{color:rgba(251,248,243,.66)}

/* Prices use the serif in ink — invisible on the field. */
.section--field .avail__price{color:#FBF8F3}
.section--field .avail__row{border-bottom-color:rgba(251,248,243,.16)}
.section--field .avail__note{color:rgba(251,248,243,.62)}

/* Form fields, if a booking bar ever lands on a dark section. */
.section--field .field label{color:rgba(251,248,243,.72)}
.section--field .field input,
.section--field .field select,
.section--field .field textarea{
  background:rgba(251,248,243,.06);border-color:rgba(251,248,243,.22);color:#FBF8F3;
}

/* ── Hero headline, every page ──────────────────────────────────
   The two-line cap was scoped to .page-home when the homepage was rebuilt.
   Six pages carry a hero; they all want it. English only — Thai wraps on
   different rules and its headlines are shorter anyway. */
@media(min-width:768px){
  html[lang="en"] .hero h1{max-width:23ch}
}

/* ═══════════════════════════════════════════════════════════════
   AIMEI-BIGGER-IMAGES-2026-09-10 — the photographs get the room
   ───────────────────────────────────────────────────────────────
   The property IS the product. Every rule here hands space back to the
   pictures and takes it from containers.
   ═══════════════════════════════════════════════════════════════ */

@media(min-width:900px){
  /* Give the width to whichever column the photograph is actually in.
     :has() rather than a blanket flip — see the note above. */
  .split--wide:has(> div:first-child img){grid-template-columns:1.28fr .92fr}
  .split--wide:has(> div:last-child  img){grid-template-columns:.92fr 1.28fr}
  .split:not(.split--wide):has(> div:first-child img){grid-template-columns:1.2fr 1fr}
  .split:not(.split--wide):has(> div:last-child  img){grid-template-columns:1fr 1.2fr}
}

/* Gallery strips. 42 of the 43 photographs in the original library were
   portrait or square; a 1:1 crop cuts the top and bottom off every one. 4:5
   keeps the shot and makes each tile substantially bigger. */
.strip img{aspect-ratio:4/5}
@media(min-width:820px){.strip img{aspect-ratio:4/5}}

/* House cards. A 4:3 window on a portrait photograph is the same problem in
   miniature, and these cards are the page that sells rooms. */
.card__media{aspect-ratio:4/5}

/* Big pictures should not look upholstered. A 2px radius reads as a container
   at thumbnail size and as a mistake at full width. */
.experience__media img,
.lifestyle-band img,
.strip img{border-radius:0}

/* Section photographs get a floor, so a short one still has presence. */
@media(min-width:900px){
  .split img,.split--wide img{min-height:340px;object-fit:cover;width:100%}
}

/* ── Full bleed on the widest screens ───────────────────────────
   Past the 1180 wrap there is dead margin either side. Above 1300px the
   photograph in a split runs out into it, toward its own edge only, so the
   text column keeps its measure. Uses the same maths proved on the homepage
   experience rows: the margin outside the centred column PLUS the gutter,
   or the picture stops one gutter short and reads as an ordinary grid. */
@media(min-width:1300px){
  .split--wide:has(> div:last-child img)  > div:last-child,
  .split:not(.split--wide):has(> div:last-child img) > div:last-child{
    margin-right:calc(-1 * (max(0px,(100vw - var(--wrap)) / 2) + var(--gutter)));
  }
  .split--wide:has(> div:first-child img) > div:first-child,
  .split:not(.split--wide):has(> div:first-child img) > div:first-child{
    margin-left:calc(-1 * (max(0px,(100vw - var(--wrap)) / 2) + var(--gutter)));
  }
}

/* ═══════════════════════════════════════════════════════════════════════
   AIMEI-PAGES-2026-09-10 — the café menu, and a top-aligned split
   ═══════════════════════════════════════════════════════════════════════ */

/* .split is align-items:center, which is right when one column is a
   photograph — the picture and the paragraph read as one object. It is wrong
   when BOTH columns are text of different lengths: on contact.php the form
   and the address block are unequal, so centring dropped "Message us" 125px
   below "Contact" and the page read as a mistake. This modifier lines the
   two headings up. Only for text-against-text. */
@media(min-width:900px){ .split--top{align-items:start} }

/* Prices are information, not an aside. Ink-soft (8.52:1 on the tint) and
   tabular figures, so 149 / 139 / 129 form a column rather than a ragged
   edge. white-space:nowrap keeps "฿1,000" off two lines in Thai. */
.menu-price{
  color:var(--ink-soft);
  font-variant-numeric:tabular-nums;
  font-feature-settings:'tnum' 1;
  white-space:nowrap;
}

/* ═══════════════════════════════════════════════════════════════════════
   AIMEI-PROPORTION-2026-09-11 — one height for every homepage photograph
   ═══════════════════════════════════════════════════════════════════════

   Andrew, 11 Sep 2026: "get the images on the homepage to similar sizes
   because each section is not in proportion."

   He was right, and the cause was two ratios multiplied by two widths.
   `.experience__media img` was 4:5, `--wide` was 4:3, and the bleeding rows
   are ~160px wider than the contained ones. At 1400px that produced roughly
   550 / 909 / 909 / 427 px of photograph down one section — the yukata was
   more than twice the height of the garden.

   THE FIX IS TO FIX THE HEIGHT, NOT THE RATIO. Every experience photograph
   is now the same height and fills whatever width its column gives it. The
   rows still alternate side and two still run off the edge, so the rhythm
   varies by WIDTH — which reads as composition — instead of by HEIGHT,
   which reads as a mistake.

   ⚠️ `height` alone is not enough: `aspect-ratio` wins over an auto height
   and would keep forcing the old box, so it is explicitly cleared here.
   The 4:5 / 4:3 rules above are left in place — they still govern below
   900px, where the rows stack and one column of equal-width images looks
   better in portrait. */
@media(min-width:900px){
  .experience__media img,
  .experience__media--wide img{
    aspect-ratio:auto;
    height:clamp(420px,36vw,540px);
    width:100%;
    object-fit:cover;
  }
}

/* The intro photograph above the field, and the lifestyle band, are set to
   the same height so the page keeps one measure from top to bottom rather
   than three. */
@media(min-width:900px){
  .page-home .split--wide img,
  .page-home .split img{
    height:clamp(420px,36vw,540px);
    min-height:0;
    object-fit:cover;
  }
}

/* Below 900px `.split` is not a grid at all — the grid rule and its gap live
   inside the 900px query — so its two children were plain blocks stacked with
   no space between them. On a phone that put the photograph hard against the
   bottom of the "About" button. `.experience` never had this because it is a
   grid at every width. */
@media(max-width:899px){
  .split > * + *{margin-top:clamp(1.75rem,6vw,2.5rem)}
}

/* ═══════════════════════════════════════════════════════════════
   AIMEI-CHAT-2026-09-11 — "message us" on LINE and WhatsApp
   ───────────────────────────────────────────────────────────────
   Andrew: "a sticky line messaging button to allow people to ask questions",
   then "aimei brown with Line and Whatsapp logos", "bar on desktop, floating
   bubble on mobile".

   ⚠️ TWO NODES, NOT ONE MOVED BY CSS. .site-header carries backdrop-filter,
   and a filter creates a containing block — position:fixed on a descendant
   resolves against the 76px bar, not the viewport. The dock is therefore a
   sibling of <header>, rendered separately. See partials/chat-buttons.php.

   ⚠️ NOT IN THE HEADER ROW BELOW 1040px. That row already overflowed a 375px
   phone once, and it scrolled the whole page sideways. The phone gets the
   bubble instead — which is also where a thumb actually is.

   CONTRAST, measured not assumed (values from AIMEI-BROWN-2026-09-10):
     brown glyph on washi   5.05:1   AA — the header buttons
     white glyph on brown   5.45:1   AA — the dock bubbles, and both on hover
   ═══════════════════════════════════════════════════════════════ */

.chat{display:flex;align-items:center;gap:.5rem}
.chat__btn{
  display:inline-flex;align-items:center;justify-content:center;
  flex:0 0 auto;text-decoration:none;
  transition:background .25s var(--ease),color .25s var(--ease),
             border-color .25s var(--ease),box-shadow .25s var(--ease);
}
.chat__icon{display:block}

/* ── The desktop bar ────────────────────────────────────────────
   Outlined, not filled. Three solid brown buttons in one 76px row would put
   "message us" at the same visual weight as BOOK NOW, and the booking is the
   commercial point of the site. Same colour, quieter voice; it fills on hover.
   One line to make them solid if Andrew wants them louder. */
.chat--header{display:none}
.chat--header .chat__btn{
  width:40px;height:40px;
  color:var(--brand-brown);
  background:none;
  border:1px solid rgba(154,89,54,.32);
  border-radius:var(--radius);
}
.chat--header .chat__btn:hover,
.chat--header .chat__btn:focus-visible{
  background:var(--brand-brown);
  border-color:var(--brand-brown);
  color:var(--paper);
}
/* ⚠️ 1120px, NOT the 1040px the nav itself uses, AND IT IS MEASURED.
   The Thai nav is the tight case — its labels are wider than the English ones
   and it is ~90% of the traffic. With BOTH buttons in the row, 1040px wraps
   บ้านพัก, ออนเซ็น, บางเสร่, เกี่ยวกับเรา and จองเลย onto two lines, which breaks
   the house rule that a desktop nav is one line. 1120px is the first width
   that holds — see _local-tests/header-widths.png.

   It is set for TWO buttons even though only LINE ships today. Tuned to the
   one-button row, this would have looked right until the morning Andrew sent
   the WhatsApp number, and then quietly broken the header on every Thai page.
   Below 1120px the dock takes over, which is why it is a `display` swap and
   not a second copy of the same control. */
@media(min-width:1120px){.chat--header{display:flex}}

/* ── The phone dock ─────────────────────────────────────────────
   Above the safe-area inset so it clears the iOS home bar. z-index 90 keeps
   it under the sticky header (100) rather than over it.

   ⚠️ BOTTOM LEFT, NOT BOTTOM RIGHT, AND THIS IS NOT A PREFERENCE.
   Bottom right is the convention and it was built that way first. At 390px it
   landed square on the check-in field's native date-picker button — see
   _local-tests/chat-phone.png, first run. Every native control on this site
   sits at the RIGHT edge of its field: the date pickers, the guests select,
   the house select. The booking panel straddles the homepage hero, and the
   same form is on contact, book and every house page, so that collision is
   most of the site, not one page. The left edge of a field carries its label
   and the start of its placeholder — nothing to tap.

   A floating button always covers something. This one now covers text. */
.chat--dock{
  position:fixed;
  left:clamp(.85rem,4vw,1.25rem);
  bottom:calc(clamp(.85rem,4vw,1.25rem) + env(safe-area-inset-bottom,0px));
  z-index:90;
  flex-direction:column;
  gap:.6rem;
}
.chat--dock .chat__btn{
  width:52px;height:52px;
  border-radius:50%;
  background:var(--brand-brown);
  border:1px solid var(--brand-brown);
  color:#fff;
  /* ⚠️ THE PAPER RING IS LOAD-BEARING, NOT DECORATION.
     A fixed button floats over whatever section happens to be behind it, so
     unlike every other component it cannot be given a .section--field
     override — the dark band scrolls under it. Brown measures 2.39:1 on the
     field (#2A3140) and the disc vanished into the café page's dark band
     completely. The ring is paper, which separates the control from a dark
     ground and from a photograph, and is all but invisible on the cream
     pages where the brown already passes at 4.86:1. */
  box-shadow:0 0 0 2px rgba(250,247,242,.92),
             0 2px 6px rgba(26,22,20,.16),
             0 10px 28px rgba(26,22,20,.22);
}
.chat--dock .chat__btn:hover,
.chat--dock .chat__btn:focus-visible{background:#84492B;border-color:#84492B}
/* Must match the .chat--header breakpoint exactly. A gap shows both; an
   overlap shows neither. */
@media(min-width:1120px){.chat--dock{display:none}}

/* A focus ring that is visible on both treatments. The outline is drawn
   outside the button so it survives the hover fill. */
.chat__btn:focus-visible{
  outline:2px solid var(--brand-brown);
  outline-offset:3px;
}