/* Float Therapy booking prototype — house design language */
@import url('https://fonts.googleapis.com/css2?family=Space+Grotesk:wght@400;500;600;700&family=Archivo:wght@400;500;600;700&display=swap');

:root {
  --navy: #071436;
  --navy-soft: #0e2050;
  --gold: #C9AB5F;
  --bronze: #72694F;
  --cream: #faf9f5;
  --card: #ffffff;
  --line: #e8e4d8;
  --ink: #1c2333;
  --muted: #6b7280;
  --ok: #2f7d4f;
  --font-heading: "Space Grotesk", sans-serif;
  --font-body: "Archivo", sans-serif;
  --radius: 14px;
  --shadow: 0 8px 30px rgba(7, 20, 54, .08);
}


* { margin: 0; padding: 0; box-sizing: border-box; }
html { -webkit-text-size-adjust: 100%; }
body { background: var(--cream); color: var(--ink); font-family: var(--font-body);
       font-size: var(--t-input); line-height: 1.55; min-height: 100vh; }
h1,h2,h3,h4 { font-family: var(--font-heading); color: var(--navy); line-height: 1.15; }
button { font-family: var(--font-body); cursor: pointer; border: none; background: none; }

.eyebrow { font-family: var(--font-heading); font-size: var(--t-label); font-weight: 600;
  letter-spacing: 2.5px; text-transform: uppercase; color: var(--bronze); }

/* ---------- shells ---------- */
.shell { max-width: 640px; margin: 0 auto; padding: 0 20px 120px; }
.wide  { max-width: 1200px; }

.topbar { position: sticky; top: 0; z-index: 40; background: var(--navy); color: #fff;
  display: flex; align-items: center; justify-content: space-between;
  padding: 14px 20px; }
.topbar .brand { font-family: var(--font-heading); font-weight: 600; letter-spacing: .5px;
  font-size: var(--t-lead); color: #fff; display: flex; align-items: center; gap: 10px; }
.topbar .brand small { color: var(--gold); font-weight: 500; letter-spacing: 2px;
  text-transform: uppercase; font-size: var(--t-nano); }
.topbar a { color: #cdd3e1; text-decoration: none; font-size: var(--t-caption); }

/* ---------- progress ---------- */
.steps { display: flex; gap: 6px; margin: 22px 0 6px; }
.steps span { flex: 1; height: 3px; border-radius: 2px; background: var(--line); transition: background .3s; }
.steps span.on { background: var(--gold); }
.stephead { display: flex; align-items: baseline; justify-content: space-between; margin: 14px 0 18px; }
.stephead h2 { font-size: var(--t-h2); }
.stephead .back { color: var(--bronze); font-size: var(--t-ui); font-weight: 600; }

/* ---------- cards ---------- */
.svc { background: var(--card); border: 1px solid var(--line); border-radius: var(--radius);
  padding: 18px; margin-bottom: 14px; display: block; width: 100%; text-align: left;
  transition: border-color .15s, transform .1s, box-shadow .2s; }
.svc:active { transform: scale(.99); }
.svc:hover { border-color: var(--gold); box-shadow: var(--shadow); }
.svc .row { display: flex; justify-content: space-between; align-items: baseline; gap: 12px; }
.svc h3 { font-size: var(--t-h4); }
.svc .price { font-family: var(--font-heading); font-size: var(--t-h4); color: var(--navy); font-weight: 600; white-space: nowrap; }
.svc .price small { color: var(--muted); font-weight: 400; font-size: var(--t-label); }
.svc p { color: var(--muted); font-size: var(--t-ui); margin-top: 5px; }
.svc .meta { font-size: var(--t-caption); color: var(--bronze); margin-top: 8px; font-weight: 600;
  letter-spacing: .3px; }

.chips { display: flex; flex-wrap: wrap; gap: 8px; margin-top: 12px; }
.chip { border: 1.5px solid var(--line); border-radius: 99px; padding: 8px 16px;
  font-size: var(--t-ui); font-weight: 600; color: var(--ink); background: #fff; transition: all .15s; }
.chip.on { background: var(--navy); border-color: var(--navy); color: #fff; }
.chip:hover:not(.on) { border-color: var(--gold); }
.chip .d { color: var(--muted); font-weight: 400; } .chip.on .d { color: var(--gold); }

.addon { background: linear-gradient(120deg, #fdf9ef, #fbf6e9); border: 1px solid #eadfc0;
  border-radius: var(--radius); padding: 14px 16px; margin: 4px 0 14px; display: flex;
  justify-content: space-between; align-items: center; gap: 10px; }
.addon b { font-size: var(--t-ui); color: var(--navy); display: block; }
.addon small { color: var(--bronze); font-size: var(--t-caption); }

/* ---------- date & time ---------- */
.datestrip { display: flex; gap: 8px; overflow-x: auto; padding: 4px 2px 12px; scrollbar-width: none; }
.datestrip::-webkit-scrollbar { display: none; }
.day { min-width: 64px; border: 1.5px solid var(--line); background: #fff; border-radius: 12px;
  padding: 10px 6px; text-align: center; transition: all .15s; }
.day small { display: block; font-size: var(--t-label); color: var(--muted); text-transform: uppercase; letter-spacing: 1px; }
.day b { font-family: var(--font-heading); font-size: var(--t-h4); color: var(--navy); }
.day.on { background: var(--navy); border-color: var(--navy); }
.day.on small { color: var(--gold); } .day.on b { color: #fff; }

.timegrid { display: grid; grid-template-columns: repeat(3, 1fr); gap: 10px; margin-top: 6px; }
.slot { border: 1.5px solid var(--line); background: #fff; border-radius: 12px; padding: 13px 6px;
  font-family: var(--font-heading); font-size: var(--t-input); font-weight: 600; color: var(--navy);
  text-align: center; transition: all .15s; }
.slot.on { background: var(--navy); color: #fff; border-color: var(--navy); }
.slot:hover:not(.on) { border-color: var(--gold); }
/* a start time that has already gone, in a picker rather than the diary — same meaning as
   .freeslot.gone and deliberately the same greys, so the move panel and the diary's ghosts do
   not disagree about what a past slot looks like. Still tappable: it asks first. */
.slot.gone { border-color: #dcd8cd; border-style: dotted; background: #f6f5f1; color: #9b978c; }
.slot.gone small { display: block; font-family: var(--font-body); font-size: var(--t-nano); font-weight: 500; color: #aba69a; }
.empty { text-align: center; color: var(--muted); padding: 26px 10px; font-size: var(--t-ui); }
.empty .chip { margin-top: 10px; }

/* ---------- forms ---------- */
.field { margin-bottom: 14px; }
.field label { display: block; font-size: var(--t-caption); font-weight: 600; color: var(--navy); margin-bottom: 6px; }
.field input { width: 100%; border: 1.5px solid var(--line); border-radius: 12px; padding: 13px 14px;
  font-size: var(--t-input); font-family: var(--font-body); background: #fff; outline: none; transition: border-color .15s; }
.field input:focus { border-color: var(--gold); }
.otp { display: flex; gap: 8px; justify-content: center; margin: 18px 0; }
.otp input { width: 46px; height: 54px; text-align: center; font-size: var(--t-h3); font-family: var(--font-heading); }
/* owner, 29 Aug: "wording like this, throughout the booking page, is really small — it needs to be
   bigger so people actually take note if needed". These carry real terms (card authorisation,
   gift-card validity, cancellation), so they were the quietest text on screen doing some of the
   most important work. 12.5 -> 14.5px, and see the contrast lift in site.css. */
.hint { font-size: var(--t-hint); line-height: 1.55; color: var(--muted); text-align: center; }
.hint b { color: var(--bronze); }

/* ---------- pay ---------- */
.summary { background: var(--card); border: 1px solid var(--line); border-radius: var(--radius); padding: 18px; margin-bottom: 16px; }
.summary .li { display: flex; justify-content: space-between; font-size: var(--t-ui); padding: 5px 0; }
.summary .li.total { border-top: 1px solid var(--line); margin-top: 8px; padding-top: 12px;
  font-family: var(--font-heading); font-weight: 700; font-size: var(--t-lead); color: var(--navy); }
.policy { display: flex; gap: 10px; align-items: flex-start; background: #f4f2ea; border-radius: 12px;
  padding: 12px 14px; font-size: var(--t-small); color: #4b5266; margin-bottom: 16px; }
.policy input { margin-top: 3px; accent-color: var(--navy); width: 17px; height: 17px; flex: none; }
/* 🔗 THE LINK WAS BROWSER-DEFAULT BLUE ON CREAM — owner, 11 Sept: "you can hardly read it on the
   background". Nothing had ever styled an anchor inside this box because nothing had ever put one
   there; the tick box only grew a link when the four sentences of small print collapsed into it.
   Bronze is the site's own accent and measures 5.4:1 on this cream, past AA for small text, and
   the underline is kept: colour alone is not a link to anyone who cannot see the difference. */
.policy a { color: var(--bronze); text-decoration: underline; text-underline-offset: 2px; }
.policy a:hover { color: var(--navy); }
.applepay { width: 100%; background: #000; color: #fff; border-radius: 12px; padding: 15px;
  font-size: var(--t-lead); font-weight: 600; display: flex; align-items: center; justify-content: center;
  /* owner, 29 Aug, asking whether two font languages were in use — there were THREE. This rule
     said `-apple-system` first, so every primary CTA (Buy gift card, Checkout, Pay & confirm)
     rendered in the OS font: San Francisco on iPhone, Segoe UI on Windows, Roboto on Android —
     a different typeface per device, on the most important buttons in the flow. It was a leftover
     from when this button imitated a native Apple Pay button; it is gold and branded now. Matched
     to .btn, which already uses the heading face. */
  gap: 6px; font-family: var(--font-heading); }
/* owner, 29 Aug (item 222): "add another card" must be the SAME SIZE as the buy button and sit
   ABOVE it, so it is obvious. Same geometry as .applepay, outlined instead of filled — equal
   presence, unambiguous hierarchy. */
.applepay.ghost { background: transparent; color: var(--navy); border: 1.5px solid var(--navy);
  padding: 13.5px; }   /* 13.5 + 1.5 border = the filled button's 15px, so both render identically */
.divider { text-align: center; color: var(--muted); font-size: var(--t-label); margin: 14px 0; text-transform: uppercase; letter-spacing: 1.5px; }
.giftfield { display: flex; gap: 8px; margin-top: 14px; }
.giftfield input { flex: 1; }
.giftfield button { border: 1.5px solid var(--navy); border-radius: 12px; padding: 0 18px;
  font-weight: 700; color: var(--navy); }

/* ---------- primary CTA ---------- */
.cta { position: fixed; left: 0; right: 0; bottom: 0; background: linear-gradient(transparent, var(--cream) 35%);
  padding: 26px 20px 20px; z-index: 30; }
.cta .inner { max-width: 640px; margin: 0 auto; }
.btn { width: 100%; background: var(--navy); color: #fff; font-family: var(--font-heading);
  font-size: var(--t-lead); font-weight: 600; letter-spacing: .3px; border-radius: 14px; padding: 16px;
  transition: opacity .15s, transform .1s; box-shadow: 0 10px 26px rgba(7,20,54,.25); }
.btn:disabled { opacity: .35; box-shadow: none; }
.btn:active { transform: scale(.985); }
.btn.gold { background: var(--gold); color: var(--navy); }
.runtotal { display: flex; justify-content: space-between; font-size: var(--t-ui); color: var(--muted);
  padding: 0 4px 10px; }
.runtotal b { font-family: var(--font-heading); color: var(--navy); font-size: var(--t-input); }

/* ---------- confirmation ---------- */
.confirm { text-align: center; padding: 44px 0 10px; }
.confirm .tick { width: 74px; height: 74px; border-radius: 50%; background: var(--navy); color: var(--gold);
  font-size: var(--t-h1); display: flex; align-items: center; justify-content: center; margin: 0 auto 22px;
  animation: pop .45s cubic-bezier(.2,1.6,.5,1); }
@keyframes pop { from { transform: scale(.4); opacity: 0; } }
.confirm h2 { font-size: var(--t-h2); margin-bottom: 8px; }
.confirm .ref { font-family: var(--font-heading); color: var(--bronze); letter-spacing: 2px; font-weight: 600; }
.confirm .actions { display: flex; gap: 10px; justify-content: center; margin-top: 26px; flex-wrap: wrap; }
.confirm .actions .chip { padding: 11px 18px; }

/* =================== STAFF DIARY =================== */
.diarywrap { display: flex; gap: 0; max-width: 1200px; margin: 0 auto; }
.diary { flex: 1; padding: 18px 20px 40px; overflow-x: auto; }
.dhead { display: flex; align-items: center; gap: 14px; margin-bottom: 16px; flex-wrap: wrap;
  justify-content: flex-end; }   /* overflow pills wrap RIGHT-aligned, so all buttons stay in one place */
/* owner, 28 Aug: the date owns its own line; its controls sit underneath, unsquashed */
.dhead h1 { font-size: var(--t-h2); flex: 1 0 100%; margin: 0 0 2px; }
.dhead .nav { flex: 1 0 auto; margin-right: auto; }
.dhead .nav { display: flex; gap: 6px; }
.dhead .nav button, .dhead .pill { border: 1.5px solid var(--line); background: #fff; border-radius: 10px;
  padding: 8px 14px; font-weight: 600; font-size: var(--t-ui); color: var(--navy); }
.dhead .pill.gold { background: var(--gold); border-color: var(--gold); }

.lanes { display: grid; grid-template-columns: 52px repeat(4, minmax(150px, 1fr)); gap: 8px; min-width: 700px; }
.lanehead { font-family: var(--font-heading); font-weight: 600; color: var(--navy); font-size: var(--t-ui);
  padding: 8px 6px; text-align: center; background: #fff; border: 1px solid var(--line); border-radius: 10px; }
.lanehead small { display: block; color: var(--bronze); font-size: var(--t-nano); letter-spacing: 1px; text-transform: uppercase; }
.lane { position: relative; background: #fff; border: 1px solid var(--line); border-radius: 10px; }
.timecol { position: relative; }
.timecol span { position: absolute; right: 6px; transform: translateY(-50%); font-size: var(--t-label); color: var(--muted); }
.gridline { position: absolute; left: 0; right: 0; height: 1px; background: #f0ede3; }
.apt { position: absolute; left: 5px; right: 5px; border-radius: 9px; padding: 7px 9px; font-size: var(--t-label);
  color: #fff; overflow: hidden; cursor: pointer; box-shadow: 0 2px 8px rgba(7,20,54,.14);
  border-left: 4px solid rgba(255,255,255,.45); transition: filter .15s; }
.apt:hover { filter: brightness(1.08); }
.apt.inblock { z-index: 5; outline: 2px solid #d9a423; outline-offset: 1px; }   /* stays visible above a block — needs rearranging */
/* two-column card (owner, 28 Aug): LEFT = name with the service · time ALWAYS on the
   second line, exactly as if no badge were there; RIGHT = the badges, right-aligned,
   wrapping downward in their own column — they can never push the time down or
   overlap the text, and no badges means no reserved space */
.apt .row1 { display: flex; align-items: flex-start; gap: 6px; }
.apt .aptmain { flex: 1 1 auto; min-width: 0; }
.apt b { display: block; font-size: var(--t-caption); line-height: 1.25; }
.apt .svc-l { display: block; opacity: .85; font-size: var(--t-label); }
.apt .ic { display: flex; flex-wrap: wrap; justify-content: flex-end; align-content: flex-start; gap: 2px; max-width: 58%; font-size: var(--t-label); }
.apt.confirmed { background: var(--navy-soft); }
.apt.arrived { background: #205a86; }
.apt.inservice { background: var(--bronze); }
.apt.done { background: #7a8194; }
.apt.hold { background: repeating-linear-gradient(45deg, #b9a15c, #b9a15c 8px, #ac9550 8px, #ac9550 16px); }
/* the timeline filters cancelled rows out — this is the belt to that braces: if one ever reaches
   the board it must be unmistakable rather than inheriting the live navy of the default class */
.apt.cancelled { background: #d8dae0; color: #6b7183; box-shadow: none; text-decoration: line-through; }
/* a status this build does not recognise — see statusClass(). Nothing produces one today; the
   point is that the day something does, it is read as a question rather than as a booking. */
.apt.unknown { background: #6b7183; box-shadow: none; outline: 2px dashed #d9a423; outline-offset: -3px; }
.turnover { position: absolute; left: 5px; right: 5px; border-radius: 7px;
  background: repeating-linear-gradient(45deg, #f2efe6, #f2efe6 7px, #ece8da 7px, #ece8da 14px);
  border: 1px dashed #d8d2bd; color: #a49a78; font-size: var(--t-nano); display: flex; align-items: center;
  justify-content: center; letter-spacing: 1px; text-transform: uppercase; }
.blocked.ghost { opacity: .75; pointer-events: none; border-color: var(--gold); color: var(--bronze); }
.blocked { position: absolute; left: 5px; right: 5px; border-radius: 7px; background: #eceef2;
  border: 1px dashed #c6cbd6; color: #8b93a5; font-size: var(--t-nano); display: flex; align-items: center;
  justify-content: center; letter-spacing: 1px; text-transform: uppercase; }

/* side panel */
/* owner, 28 Aug: widened from 400px so the tier line, the visit count, the card and the
   View full profile button all fit on one row (see .tierline). */
/* owner, 29 Aug: "if this button fits next to the button next to it it can fit on one line; if it
   doesn't, based on the browser window, it falls to the line below." It never fitted, because the
   panel was a fixed 460px whatever the browser was doing — "Text a secure update link" (192px) and
   "Key in new card — guest present" (242px) need 442px and had 420px of content width. On a wide
   screen the panel now has room for both; below that they wrap, which is what he asked for. */
.panel { position: fixed; top: 0; right: 0; bottom: 0; width: min(460px, 100vw); background: #fff;
  box-shadow: -18px 0 50px rgba(7,20,54,.18); z-index: 60; transform: translateX(105%);
  transition: transform .28s cubic-bezier(.3,1,.4,1); display: flex; flex-direction: column; }
.panel.open { transform: none; }
.panel .phead { background: var(--navy); color: #fff; padding: 18px 20px; }
.panel .phead h3 { color: #fff; font-size: var(--t-h4); }
.panel .phead .sub { color: var(--gold); font-size: var(--t-caption); margin-top: 3px; }
.panel .pbody { padding: 18px 20px; overflow-y: auto; flex: 1; }
/* 🔴 THE CLOSE BUTTON IS A RED SQUARE (owner, 10 Sept: the bare × "isn't visable enough").
   It was a pale #cdd3e1 glyph on a busy panel header — no box, no edge, nothing to aim at, and at
   a desk under pressure people were hunting for it.
   ⚠️ THIS IS ON ALL 23 PANELS, not only the customer profile. `.panel .close` is shared, and
   consistency is the point: a close button that moves or changes colour per screen is worse than
   one that is merely small.
   The red is #c0392b, the same one the sauna over-temperature warning uses — deliberately the
   house alert red rather than a new one, so the palette does not grow a second red. */
/* 🔴 ONE CLOSE BUTTON IN THE WHOLE PRODUCT (owner, 11 Sept: the waiver viewer's own control said
   "× Close" in a navy pill — "theres a box in the top right that says close, I dont like it. Can
   we have it the same as this box"). It is the same rule rather than a copy of it, deliberately:
   two selectors sharing one declaration cannot drift, and a close button that changes colour or
   shape depending on which thing you opened is exactly the inconsistency the note above is about.
   The document viewer sits on a cream page and the panels on navy; the red works on both, which
   is part of why it was chosen. */
.panel .close, .docmodal-close { position: absolute; top: 12px; right: 12px; width: 30px; height: 30px;
                display: flex; align-items: center; justify-content: center;
                background: #c0392b; color: #fff; border-radius: 7px;
                /* the glyph fills the box (owner, 10 Sept: the × "needs to be bigger"). It was
                   --t-body 15px inside a 30px square and swam in it. --t-h2 26px with line-height 1
                   and a hair of optical lift, because × sits high in the em box. */
                font-size: var(--t-h2); line-height: 1; font-weight: 400; padding-bottom: 2px; }
.panel .close:hover, .docmodal-close:hover { background: #a93226; }
@media (min-width: 1080px) { .panel { width: min(520px, 100vw); } }
/* and a pill never breaks its own label in half — the ROW wraps, the button doesn't */
.panel .chips > .chip { white-space: nowrap; }
.statusflow { display: flex; gap: 6px; margin: 6px 0 18px; flex-wrap: wrap; }
.statusflow .chip { font-size: var(--t-caption); padding: 7px 13px; }
/* Done before the day of the appointment: shown, greyed, and not clickable. Owner, 11 Sept —
   hiding it read as a fault; greyed with the reason beside it reads as the rule it is. */
.statusflow .chip[disabled] { opacity: .38; cursor: default; }
.statusflow .hint { font-size: var(--t-caption); }
.payopt { display: block; width: 100%; border: 1.5px solid var(--line); border-radius: 12px;
  padding: 14px; margin-bottom: 10px; text-align: left; font-size: var(--t-body); font-weight: 600;
  color: var(--navy); transition: all .15s; background: #fff; }
.payopt small { display: block; color: var(--muted); font-weight: 400; font-size: var(--t-caption); }
/* owner, 6 Sept: on the gift-card panel the "Open <name>'s profile" button sat flush against the
   card-details paragraph — measured 0px, the only zero gap in the panel; every other pair sits at
   6-18px. A .payopt following a plain paragraph gets the space. An EYEBROW is excluded on purpose:
   there the label belongs to the control under it, and 6px is what says so. */
.panel .pbody > p:not(.eyebrow) + .payopt { margin-top: 10px; }
.payopt:hover { border-color: var(--gold); }
/* 📝 "Send the form — text and email" (owner, 1 Oct 2026: "The button colour also cant be white, so its clear that it needs
   sending"). Gold, with the navy every other button's words use; shown only while a form is outstanding. */
.payopt.sendform { background: var(--gold); border-color: var(--gold); color: var(--navy); }
.payopt.sendform small { color: var(--navy); opacity: .8; }
.payopt.sendform:hover { background: #B99A4F; border-color: #B99A4F; }
/* go/no-go audit L9-07: a hidden message used to go see-through but stay on the page, above the
   Continue / Pay bar, catching taps — a long one wrapped into a box over the middle of the button,
   and a guest on a phone tapping there got nothing. Hidden means it lets taps through. */
.toast { position: fixed; bottom: 24px; left: 50%; transform: translateX(-50%) translateY(80px);
  background: var(--navy); color: #fff; border-radius: 99px; padding: 12px 22px; font-size: var(--t-ui);
  z-index: 90; opacity: 0; pointer-events: none; transition: all .3s; box-shadow: 0 12px 30px rgba(7,20,54,.35); }
.toast.show { transform: translateX(-50%); opacity: 1; pointer-events: auto; }
.scrim { position: fixed; inset: 0; background: rgba(7,20,54,.35); opacity: 0; pointer-events: none;
  transition: opacity .25s; z-index: 50; }
.scrim.show { opacity: 1; pointer-events: auto; }

/* =================== v3 — blueprint components =================== */

/* welcome-back / book-your-usual */
.usual { background: var(--navy); border-radius: var(--radius); padding: 16px 18px; margin-bottom: 16px;
  display: flex; justify-content: space-between; align-items: center; gap: 12px; }
.usual b { display: block; color: #fff; font-family: var(--font-heading); font-size: var(--t-body); margin-top: 4px; }
.usual small { color: #b9c1d5; font-size: var(--t-caption); }
.usual .chip.on { background: var(--gold); border-color: var(--gold); color: var(--navy); white-space: nowrap; }
.welcomeback { background: #f2efe6; border: 1px solid var(--line); border-radius: 12px; padding: 11px 14px; margin-bottom: 14px; }
.welcomeback b { color: var(--navy); font-size: var(--t-ui); display: block; }
.welcomeback small { color: var(--muted); font-size: var(--t-caption); }

/* checkout hold countdown */
.holdchip { font-family: var(--font-heading); font-size: var(--t-caption); font-weight: 600; color: var(--bronze);
  background: #fdf9ef; border: 1px solid #eadfc0; border-radius: 99px; padding: 7px 16px;
  display: inline-block; margin-bottom: 12px; min-height: 18px; }
.holdchip:empty { visibility: hidden; }

/* waitlist join form */
.wlform { display: flex; flex-direction: column; gap: 8px; max-width: 320px; margin: 14px auto 0; }
.wlform input { border: 1.5px solid var(--line); border-radius: 12px; padding: 11px 13px; font-size: var(--t-body); font-family: var(--font-body); }
.wlform .chip { align-self: center; }

/* wallet credit burn-down */
.burnbar { height: 8px; border-radius: 6px; background: var(--line); margin: 10px 0 6px; overflow: hidden; }
.burnbar i { display: block; height: 100%; border-radius: 6px; background: linear-gradient(90deg, var(--bronze), var(--gold)); }

/* staff KPI strip */
.kpis { display: flex; gap: 10px; max-width: 1200px; margin: 14px auto 0; padding: 0 20px; overflow-x: auto; scrollbar-width: none; }
.kpis::-webkit-scrollbar { display: none; }
.kpi { background: var(--navy); border-radius: 12px; padding: 10px 16px; min-width: 108px; flex: 1; }
.kpi small { display: block; color: var(--gold); font-size: var(--t-nano); letter-spacing: 1.5px; text-transform: uppercase; font-family: var(--font-heading); }
.kpi b { color: #fff; font-family: var(--font-heading); font-size: var(--t-h4); }
.kpi b span { color: #8f99b3; font-size: var(--t-label); font-weight: 500; }

/* view toggle */
.viewtoggle { display: flex; border: 1.5px solid var(--line); border-radius: 10px; overflow: hidden; background: #fff; }
.viewtoggle button { padding: 8px 14px; font-weight: 600; font-size: var(--t-ui); color: var(--muted); }
.viewtoggle button.on { background: var(--navy); color: #fff; }

/* front-desk kanban */
.kanban { display: grid; grid-template-columns: repeat(4, 1fr); gap: 10px; min-width: 700px; }
.kcol { background: #f4f2ea; border: 1px solid var(--line); border-radius: 12px; padding: 10px; min-height: 300px; }
.kcolhead { font-family: var(--font-heading); font-weight: 600; color: var(--navy); font-size: var(--t-body);
  display: flex; justify-content: space-between; align-items: baseline; padding: 2px 4px; }
.kcolhead small { color: var(--bronze); font-size: var(--t-caption); }
.kcolhint { font-size: var(--t-label); color: var(--muted); padding: 0 4px 8px; letter-spacing: .3px; }
.kcard { background: #fff; border: 1px solid var(--line); border-radius: 10px; padding: 10px 12px; margin-bottom: 8px;
  cursor: pointer; transition: border-color .15s, box-shadow .15s; }
.kcard:hover { border-color: var(--gold); box-shadow: var(--shadow); }
.kcard b { display: block; font-size: var(--t-small); color: var(--navy); font-family: var(--font-heading); }
.kcard span { display: block; font-size: var(--t-label); color: var(--muted); }
.kcard .krow { margin-top: 4px; display: flex; gap: 6px; align-items: center; flex-wrap: wrap; }
.kcard em, .wlrow em { font-style: normal; font-size: var(--t-label); font-weight: 600; border-radius: 99px; padding: 2px 9px; background: #eef0f4; color: #5a6377; }
.kcard em.warn, .wlrow em.warn { background: #faf0d8; color: #8a6d1f; }
.kcard em.okk { background: #e7f2ea; color: var(--ok); }
.kcard.owes { border-color: #e5c98a; }
.kempty { color: var(--muted); font-size: var(--t-caption); text-align: center; padding: 18px 0; }

/* in-page document pop-up (item 13) */
.docmodal { position: fixed; inset: 0; background: rgba(7,20,54,.5); z-index: 100;
  display: flex; align-items: center; justify-content: center; padding: 24px; }
.docmodal-inner { position: relative; width: min(960px, 96vw); height: 92vh; background: #F7F5EF;
  border-radius: 18px; overflow: hidden; box-shadow: 0 30px 80px rgba(7,20,54,.4); }
.docmodal-inner iframe { width: 100%; height: 100%; border: 0; }
/* only what the document viewer needs ON TOP of the shared rule above: it floats over an iframe,
   so it needs a stacking context the panels do not. */
.docmodal-close { z-index: 2; }

/* live transaction watch */
.txnwatch { border: 1.5px solid var(--gold); border-radius: 14px; padding: 16px; background: #fdf9ef; }
.txnwatch b { display: block; color: var(--navy); font-family: var(--font-heading); font-size: var(--t-body); }
.txnwatch span { display: block; color: var(--bronze); font-size: var(--t-small); margin-top: 4px; }
.txnwatch:not(.ok) b::after { content: " ⋯"; animation: pulse 1.2s infinite; }
@keyframes pulse { 50% { opacity: .3; } }
.txnwatch.ok { border-color: var(--ok); background: #eef6f0; }
.txnwatch.ok span { color: var(--ok); }

/* gift-card code display */
.giftcode-big { font-family: var(--font-heading); font-size: var(--t-h2); font-weight: 700; letter-spacing: 2px;
  color: var(--navy); background: #fdf9ef; border: 1.5px dashed #d8cba3; border-radius: 14px;
  padding: 16px 22px; display: inline-block; margin: 16px 0 10px; }

/* staff customer search */
.csearchwrap { position: relative; }
.csearch { border: 1.5px solid var(--line); border-radius: 10px; padding: 8px 14px; font-size: var(--t-ui);
  font-family: var(--font-body); background: #fff; width: 200px; }
.csearch:focus { outline: none; border-color: var(--gold); }
.cresults { position: absolute; top: 40px; left: 0; width: 280px; background: #fff; border: 1px solid var(--line);
  border-radius: 12px; box-shadow: var(--shadow); z-index: 70; overflow: hidden; }
.cresults button { display: block; width: 100%; text-align: left; padding: 10px 14px; font-size: var(--t-ui);
  border-bottom: 1px solid #f0ede3; }
.cresults button:hover { background: #faf8f2; }
.cresults b { color: var(--navy); display: block; }
.cresults small { color: var(--muted); }

/* forms dot */
.dot { width: 8px; height: 8px; border-radius: 50%; display: inline-block; }
.dot.amber { background: #d9a423; box-shadow: 0 0 0 2px rgba(217,164,35,.25); }

/* panel extras */
/* owner, 28 Aug: "make the profile box bigger, so the View full profile button always sits on
   the same line as the 10 visits line." The pill was an inline-block sized to its content, so
   once the tier, the visit count and the card were on it the button had nowhere to go and
   dropped to a second line. It is now a single row: the detail takes the space it needs and
   trims if it must, the button is pinned right and never wraps. */
.tierline { margin-top: 6px; font-size: var(--t-label); color: #cdd3e1; background: rgba(255,255,255,.08);
  border-radius: 99px; padding: 4px 6px 4px 12px; display: flex; align-items: center; gap: 8px; }
.tierline .tiertext { flex: 1 1 auto; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.tierline .chip { flex: 0 0 auto; margin-left: auto; }
/* the ban notice reuses the pill but has no button — keep it hugging its text */
.tierline.banline { display: inline-flex; padding-right: 12px; }
.settled { font-size: var(--t-small); color: var(--ok); background: #eef6f0; border-radius: 10px; padding: 10px 12px; margin-bottom: 10px; }
.repline { display: flex; justify-content: space-between; font-size: var(--t-ui); padding: 7px 2px; border-bottom: 1px solid #f0ede3; }
.repline b { font-family: var(--font-heading); color: var(--navy); }
/* owner, 29 Aug: "the '1 of 1' is split over two lines". The row is a flex pair and the long
   description was squeezing the figure until it wrapped mid-phrase. The figure never wraps; the
   description shrinks instead, which is the half that can afford to. */
.repline > span { min-width: 0; }
/* .payopt small is display:block; this one never was, so a reason under a date ran straight on —
   "25 Dec 2026That space is not free" (owner spotted it, 4 Sept). */
.repline small { display: block; color: var(--muted); font-weight: 400; font-size: var(--t-caption); margin-top: 2px; }
.repline > b { flex: 0 0 auto; white-space: nowrap; margin-left: 12px; align-self: baseline; }
.wlrow { border: 1px solid var(--line); border-radius: 10px; padding: 10px 12px; margin-bottom: 8px;
  display: flex; align-items: center; gap: 10px; flex-wrap: wrap; }
.wlrow b { color: var(--navy); font-size: var(--t-ui); }
/* the name is a link to the profile (owner, 29 Aug) — styled as the heading it replaces, with
   just enough affordance to read as tappable */
.wlrow b .wlname { font: inherit; color: var(--navy); background: none; border: 0; padding: 0;
  cursor: pointer; text-decoration: underline; text-decoration-color: rgba(11,23,54,.28);
  text-underline-offset: 3px; }
.wlrow b .wlname:hover { text-decoration-color: var(--gold); color: var(--gold); }
.wlrow span { color: var(--muted); font-size: var(--t-caption); margin-right: auto; }
.nbselect { width: 100%; border: 1.5px solid var(--line); border-radius: 12px; padding: 10px 12px;
  font-size: var(--t-small); line-height: 1.35; font-family: var(--font-body); background: #fff;
  color: var(--ink); white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }

@media (max-width: 700px) {
  .stephead h2 { font-size: var(--t-h3); }
  .timegrid { grid-template-columns: repeat(3, 1fr); }
  .lanes { grid-template-columns: 44px repeat(4, minmax(128px, 1fr)); }
  .kanban { grid-template-columns: repeat(2, 1fr); min-width: 0; }
  /* ═══ ITEM 217 (owner, 29 Aug) ═══ "the top boxes on mobile where it mentions takings etc,
     these need to be in one page, and not scrolling."
     They were a horizontal scroller, so TAKEN TODAY — the number an owner opens the page to see —
     was cut off the right edge. A 2×2 grid puts all four on screen with nothing to swipe. The
     scroller stays on desktop, where they fit in a row anyway. */
  .kpis { display: grid; grid-template-columns: 1fr 1fr; gap: 8px; overflow-x: visible; }
  .kpi { min-width: 0; padding: 10px 12px; }
  /* ⚠️ the tile COUNT is not fixed — an owner sees five (the two money tiles), Jade three,
     reception two. An odd last tile would sit alone against a gap, so it takes the full width
     instead. This is why the rule is written on :last-child rather than on a fixed 2×2. */
  .kpi:last-child:nth-child(odd) { grid-column: 1 / -1; }
  .kpi b { font-size: var(--t-input); }
  .kpi small { font-size: var(--t-nano); letter-spacing: 1.1px; }
  .kpi b span { display: block; font-size: var(--t-label); margin-top: 1px; }
}

/* item 20: readable badges */
.bdg{display:inline-block;font-size: var(--t-nano);font-weight:800;letter-spacing:.05em;padding:1.5px 6px;border-radius:99px;background:#e8ebf2;color:#3c4257;margin:0 3px 2px 0;vertical-align:1px}
.bdg.gold{background:#f3ead2;color:#7a6420}
.bdg.gift{background:#efe6f3;color:#5d3a6e}
.bdg.amber{background:#faeecb;color:#8a6d1f}
.bdg.red{background:#f6dede;color:#7a2f2f}
.bdg.due{background:#7a2f2f;color:#fff}
/* item 6: available time drawn explicitly */
.freeslot{position:absolute;left:3px;right:3px;border:1.5px dashed #b7cdb9;border-radius:8px;background:#f2f7f2;color:#4c6b4f;font-size: var(--t-nano);font-weight:700;padding:4px 7px;cursor:pointer;z-index:1;overflow:hidden}
.freeslot small{display:block;font-weight:500;font-size: var(--t-nano);color:#7c967e}
.freeslot:hover{background:#e8f1e8}
/* time that has already gone. Still tappable — the desk records visits that already happened —
   but it must not read as capacity: grey, not the green of something on sale. */
.freeslot.gone{border-color:#dcd8cd;border-style:dotted;background:#f6f5f1;color:#9b978c}
.freeslot.gone small{color:#aba69a}
.freeslot.gone:hover{background:#efeee8}
.apt{z-index:2}

/* item 64: the gift-card box is PROMINENT (owner) */
.giftfield { margin-top: 18px; padding: 14px; border: 2px solid #72694F; border-radius: 14px; background: #faf8f2; }
.giftfield::before { content: "Gift card / discount code"; display: block; font-weight: 800; font-size: var(--t-caption); letter-spacing: .03em; color: #071436; margin-bottom: 8px; }
.giftfield input { padding: 14px; font-size: var(--t-body); letter-spacing: .05em; border-radius: 12px; }
.giftfield button { padding: 14px 26px; font-size: var(--t-body); font-weight: 800; background: #72694F; color: #fff; border: none; border-radius: 12px; cursor: pointer; }

/* group micro-labels — declutter stacked chip groups (owner, 28 Aug: "VERY busy") */
.grouplabel { font-size: var(--t-label); font-weight: 600; letter-spacing: .16em; text-transform: uppercase;
  color: var(--muted); margin: 20px 0 8px; }
.grouplabel:first-of-type { margin-top: 14px; }

/* per-treatment description under the chip picker (item 99, copy verbatim from the site) */
.svcdesc { margin: 14px 4px 2px; font-size: var(--t-ui); line-height: 1.65; color: var(--ink);
  text-align: left; max-width: 62ch; }

/* topbar rework (28 Aug): shared markup, per-design dress. Design A = the simple bar. */
.topbar .brand { text-decoration: none; display: flex; align-items: center; gap: 10px; }
.brandlogo { display: none; }
.sitenav { display: none; }
.topactions { display: flex; gap: 16px; align-items: center; }
.topactions a { text-decoration: none; }
.mob-account { display: none; }
.soc, .chev { display: none; }   /* site-navbar furniture — design B only */
.realheader { display: none; }   /* the verbatim site header — design B only */
.hero-b { display: none; }       /* site-style hero — design B styles it into being */

/* item 100: expiry warnings on the staff profile */
.warnline { background: #b0801f; color: #fff; border-radius: 12px; padding: 9px 14px;
  font-size: var(--t-caption); font-weight: 600; margin: 8px 0 0; display: block; width: fit-content; max-width: 100%; line-height: 1.5; }
.warnline.red { background: #7a2f2f; }
.panel > .warnline { margin-left: 14px; margin-right: 14px; }
.dhead .pill.red { background: #7a2f2f; border-color: #7a2f2f; color: #fff; }
.wlform select { border: 1.5px solid var(--line); border-radius: 12px; padding: 10px 12px;
  font: inherit; font-size: var(--t-ui); background: #fff; color: var(--ink); }
.repline b.liab { text-align: right; white-space: nowrap; }
.repline b.liab small { display: block; font-family: var(--font-body); font-weight: 400;
  color: var(--muted); font-size: var(--t-label); margin-top: 1px; }
.daybanner { background: var(--navy); color: #fff; border-radius: 12px; padding: 10px 16px;
  font-weight: 600; font-size: var(--t-small); margin-bottom: 12px; }
.toast.warn { background: #7a2f2f; color: #fff; }
.toast.ok { background: #2f7d4f; color: #fff; }
.settled.owedue { background: #7a2f2f; color: #fff; font-weight: 700; font-size: var(--t-body);
  border-radius: 12px; padding: 12px 14px; }
.arrivenote { margin-top: 8px; background: #faf0d8; border-radius: 10px; padding: 10px; color: #5a4a1f; }
.arrivenote b { color: #5a4a1f; }

/* owner, 28 Aug: the state of the money is the first thing a front desk needs off a booking,
   so it is a banner, not a sentence — green when there is nothing to take, red when there is,
   with the itemised record underneath. */
/* owner, 28 Aug: a visit list should read down the page in columns, not as a run of sentences
   with prices bolted on the end. Date and time are quiet and aligned; the service is the thing
   you actually read. Imported visits say so, quietly, instead of wearing a badge. */
.apptlist { border: 1px solid #e6e1d5; border-radius: 12px; padding: 2px 12px; }
.apptrow { display: grid; grid-template-columns: 96px 44px 1fr; gap: 10px; align-items: baseline;
  padding: 8px 0; border-bottom: 1px solid #f4f1e9; font-size: var(--t-small); color: var(--navy); }
.apptrow:last-child { border-bottom: 0; }
.apptrow .d, .apptrow .t { color: var(--muted); font-size: var(--t-caption); white-space: nowrap; }
.apptrow .t { font-variant-numeric: tabular-nums; }
.apptrow .s { min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.apptrow.mig .s::after { content: " · from Zenoti"; color: var(--muted); font-size: var(--t-label); }
.apptrow.legacy { grid-template-columns: 1fr; }
.apptrow .cx { font-style: normal; color: #7a2f2f; font-size: var(--t-label); }
/* an unpaid visit in the history is the one thing on that list a desk must not miss, so it is the
   only thing on the row that is filled rather than written (owner, 30 Aug) */
.apptrow .owes { font-style: normal; font-weight: 800; font-size: var(--t-label); letter-spacing: .04em;
  background: #7a2f2f; color: #fff; border-radius: 6px; padding: 1px 6px; white-space: nowrap; }
/* a row that opens the appointment (owner, 29 Aug) — migrated history has none, so is left inert */
.apptrow.tap { cursor: pointer; border-radius: 8px; margin: 0 -6px; padding-left: 6px; padding-right: 6px; }
.apptrow.tap:hover { background: #f6f4ee; }
.apptrow .go { color: var(--bronze); font-weight: 700; }
/* the wallet borrows the appointment list's chrome, on its own three columns:
   what kind of thing it is · what it is · what's left of it (owner, 29 Aug) */
.apptrow.wal { grid-template-columns: 78px minmax(0, 1fr) auto; }
.apptrow.wal .d { text-transform: uppercase; letter-spacing: .06em; font-size: var(--t-label); }
.apptrow.wal .s { white-space: normal; overflow: visible; text-overflow: clip; }
.apptrow.wal .v { font-weight: 700; white-space: nowrap; font-variant-numeric: tabular-nums; }
/* owner, 29 Aug: "if it's paused, we need to bold with red text, so it's clear." A paused package
   and a frozen membership are the two states where a guest turns up expecting to use something
   they cannot — so they read as a warning, not as another grey detail on the line. */
.apptrow.wal .paused, .paused { font-style: normal; font-weight: 800; color: #a33;
  letter-spacing: .04em; }
@media (max-width: 520px) {
  .apptrow.wal { grid-template-columns: 1fr auto; }
  .apptrow.wal .d { grid-column: 1 / -1; }
}

.paystate { border-radius: 12px; padding: 12px 14px; margin-bottom: 10px; }
.paystate b { display: block; font-size: var(--t-lead); line-height: 1.25; }
.paystate span { display: block; font-size: var(--t-caption); margin-top: 2px; opacity: .85; }
.paystate.ok { background: #eef6f0; color: #2f7d4f; }
.paystate.owed { background: #7a2f2f; color: #fff; }
.payrows { border: 1px solid #e6e1d5; border-radius: 12px; padding: 4px 12px; margin-bottom: 12px; }
.payrow { display: flex; justify-content: space-between; align-items: baseline; gap: 12px;
  padding: 9px 0; border-bottom: 1px solid #f0ece2; font-size: var(--t-small); color: var(--navy); }
.payrow:last-child { border-bottom: 0; }
.payrow b { white-space: nowrap; font-weight: 700; }
.payrow.when { color: var(--muted); font-size: var(--t-caption); }

/* the step heading and its Back button: title left, Back hard right, wrapping safely
   on narrow windows rather than colliding (owner, 28 Aug) */
.stephead { flex-wrap: wrap; gap: 10px 16px; }
.stephead h2 { flex: 1 1 auto; min-width: 0; }
.stephead .back { flex: 0 0 auto; margin-left: auto; white-space: nowrap; }

/* THE DATE STRIP SCROLLS — at every width, with something you can actually press (owner, 29 Aug:
   "it can be made scrollable, but we need to make it obviously they need to scroll to view more
   dates"). It holds fourteen days; a grid wide enough for all of them either shrinks each day to
   an unreadable sliver or stacks them into two rows of numbers, which is what he was looking at. */
.datewrap { position: relative; padding: 0 34px; }
.datescroll { display: block; text-align: center; margin: 0; font-size: var(--t-caption); }
/* Today and the scroll hint share one centred line under the strip, and the row keeps its height
   whether or not Today is there, so nothing jumps when you move off today (owner, 29 Aug) */
.dateunder { display: flex; flex-direction: column; align-items: center; gap: 8px;
  margin: -4px 0 12px; min-height: 26px; }
/* when every date already fits, there is nothing to scroll — so the arrows, the fade and the hint
   all go, rather than pointing at movement that cannot happen.
   ⚠️ The HINT is hidden with visibility, not display, and the line keeps its height. The check runs
   after the screen is painted, so a display swap here changes the page height AFTER the "back"
   scroll position has been restored — measured: it put you back 23px from where you left. Reserve
   the line and the toggle costs nothing. */
.datewrap:not(.scrollable) .datenav,
.datewrap:not(.scrollable)::after { display: none; }
.datescroll { min-height: 1.5em; }
.datewrap:not(.scrollable) ~ .datescroll > span { visibility: hidden; }
.datewrap:not(.scrollable) { padding: 0; }
.datescroll .ds-touch { display: none; }
.datescroll .ds-desk  { display: inline; }
/* the fade says the row continues; the arrow says you may move it */
.datewrap::after { content: ""; position: absolute; top: 0; right: 34px; bottom: 12px; width: 46px;
  pointer-events: none; opacity: 1; transition: opacity .18s;
  background: linear-gradient(90deg, transparent, var(--cream) 78%); }
.datewrap.atend::after { opacity: 0; }
.datenav { position: absolute; top: calc(50% - 6px); transform: translateY(-50%);
  display: grid; place-items: center; width: 30px; height: 30px; border-radius: 50%;
  border: 0; background: var(--navy); color: #fff; font-size: var(--t-lead); line-height: 1;
  cursor: pointer; z-index: 3; transition: opacity .18s; }
.datenav.prev { left: 0; } .datenav.next { right: 0; }
.datenav:disabled { opacity: .22; cursor: default; }
@media (max-width: 899px) {
  .datescroll .ds-touch { display: inline; }
  .datescroll .ds-desk  { display: none; }
}
.wloffer { margin: 2px 0 16px; text-align: left; }
.kcard.held { border-style: dashed; border-color: #b9a15c; background: #fdf8ea; }
.kcard.held b { color: #8a6d1f; }

/* month calendar — reach any date without a long list (owner, 28 Aug) */
.calbar { display: flex; gap: 8px; flex-wrap: wrap; margin: 2px 0 14px; }
.calpanel { border: 1.5px solid var(--line); border-radius: 14px; padding: 14px; margin-bottom: 16px; background: #fff; }

/* ── THE STAFF DIARY'S DATE JUMP ──────────────────────────────────────────────────────────────
   The same month grid the customer sees, floated under the button that opens it. Sized properly
   on purpose: the control it replaces was the browser's own date input, which the owner found
   "generic" and "really small" (10 Sept). A desk taps this all day, so the day cells are finger
   sized rather than the compact ones the customer flow uses inside a narrow card. */
.dhead .nav { position: relative; }
.jumpcal { position: absolute; z-index: 60; top: calc(100% + 8px); left: 0;
           width: min(92vw, 340px); box-shadow: 0 18px 44px rgba(5,16,43,.22); border-radius: 14px; }
.jumpcal .calpanel { margin-bottom: 0; padding: 16px; }
.jumpcal .calgrid { gap: 5px; }
/* 🚨 THE NAV STEALS THESE BUTTONS. The popover sits inside .dhead .nav so it can be positioned
   against the button that opens it, and `.dhead .nav button` (0,2,1) then out-ranks `.calday.on`
   (0,2,0) — today's cell rendered white-on-white and the number simply vanished. Caught by
   screenshotting it, not by a test: every class was correct in the DOM.
   So the day cells are re-stated here at a specificity that wins, rather than the nav rule being
   loosened — that rule belongs to the ‹ Today › buttons and should keep working for them. */
/* ⚠️ THREE classes, not two, and it matters. `.dhead .nav button` is (0,2,1); `.jumpcal .calday`
   is (0,2,0) and LOSES to it. First time round I only re-stated the background and the border at
   a winning specificity, so the cells kept the nav's `padding: 8px 14px` — 49px cells inside 39px
   columns, and the Sunday column spilled out of the panel (owner spotted it in the screenshot).
   `.jumpcal .calpanel .calday` is (0,3,0) and beats it on class count. box-sizing is explicit for
   the same reason: a button's padding must not add to its column width. */
.jumpcal .calpanel .calday { box-sizing: border-box; width: 100%;
                             padding: 10px 0 8px; border: 1.5px solid var(--line); background: #fff;
                             border-radius: 10px; font-weight: 600; }
.jumpcal .calpanel .calday.on { background: var(--navy); border-color: var(--navy); }
.jumpcal .calpanel .calday.on b { color: #fff; }
.jumpcal .calpanel .calday.empty { border-color: transparent; background: none; }
.jumpcal .calpanel .calday b { font-size: var(--t-input); }
/* today, when it is not the day already selected: a ring rather than a fill, so it never competes
   with the navy `.on` that says which day the diary is actually showing */
.jumpcal .calpanel .calday.istoday:not(.on) { border-color: var(--navy); box-shadow: inset 0 0 0 1px var(--navy); }
.jumpcal .calpanel .calday.shut { background: var(--cream, #f4f1ea); }
.jumpcal .calpanel .calday.shut i { color: var(--muted); }
@media (max-width: 560px) { .jumpcal { left: auto; right: 0; } }
.calhead { display: flex; align-items: center; justify-content: space-between; margin-bottom: 10px; }
.calhead b { font-family: var(--font-heading); font-size: var(--t-input); color: var(--navy); }
.calhead .chip { padding: 6px 14px; }
.calhead .chip[disabled] { opacity: .35; cursor: default; }
.calgrid { display: grid; grid-template-columns: repeat(7, 1fr); gap: 6px; }
.caldow span { text-align: center; font-size: var(--t-label); font-weight: 700; letter-spacing: .08em;
  color: var(--muted); padding-bottom: 2px; }
.calday { border: 1.5px solid var(--line); border-radius: 10px; background: #fff; padding: 7px 2px 5px;
  text-align: center; display: block; }
.calday b { display: block; font-family: var(--font-heading); font-size: var(--t-body); color: var(--navy); font-weight: 600; }
.calday i { display: block; font-style: normal; font-size: var(--t-nano); color: var(--ok); font-weight: 600; }
.calday.on { background: var(--navy); border-color: var(--navy); }
.calday.on b { color: #fff; } .calday.on i { color: #cfe6d8; }
.calday.none { opacity: .5; border-style: dashed; }
.calday.none i { color: var(--muted); }
.calday.past, .calday.empty { border: none; opacity: .25; }
.calday.shut { opacity: .45; border-style: dashed; }
.calday.shut i { color: var(--muted); }
.calday.past i, .calday.empty b { display: none; }

/* the chosen card in a pick-one grid (gift delivery method) */
.svc.picked { border-color: #72694F; box-shadow: inset 0 0 0 1px #72694F; }

/* ---- gift cards waiting to post (owner, 28 Aug: "that box needs to be flashing red
   so its important"). The pill pulses the whole time anything is outstanding; the
   banner states who is waiting and for how long, and turns hard red once a card has
   been sitting overnight. Motion is switched off for anyone who asks for less of it. */
.dhead .pill.flashing { animation: postflash 1.15s ease-in-out infinite; }
@keyframes postflash {
  0%, 100% { background: #7a2f2f; border-color: #7a2f2f; color: #fff; box-shadow: 0 0 0 0 rgba(122,47,47,.55); }
  50%      { background: #c0392b; border-color: #c0392b; color: #fff; box-shadow: 0 0 0 6px rgba(192,57,43,0); }
}
/* bank holiday: a statement of fact, not an alarm — it never flashes */
.daybanner.bankhol { background: #5b5340; }
.daybanner.postalert { display: flex; align-items: center; gap: 12px; flex-wrap: wrap;
  background: #8a6d1f; animation: postbanner 1.6s ease-in-out infinite; }
.daybanner.postalert.overdue { background: #7a2f2f; animation-name: postbannerhot; }
@keyframes postbanner    { 50% { background: #b08a1f; } }
@keyframes postbannerhot { 50% { background: #c0392b; } }
.daybanner.postalert .pip { font-size: var(--t-lead); line-height: 1; }
.daybanner.postalert b { font-size: var(--t-ui); }
.daybanner.postalert .names { font-weight: 400; font-size: var(--t-caption); opacity: .95; }
.daybanner.postalert .names .wait { opacity: .8; font-style: italic; }
.daybanner.postalert .chip { margin-left: auto; background: #fff; color: var(--navy);
  border-color: #fff; font-weight: 600; }
@media (prefers-reduced-motion: reduce) {
  .dhead .pill.flashing, .daybanner.postalert { animation: none; }
  .dhead .pill.flashing { background: #c0392b; border-color: #c0392b; }
}

/* waitlist: place in the queue + when they joined (owner, 28 Aug) */
.wlrow .wlpos { display: inline-flex; align-items: center; justify-content: center;
  min-width: 20px; height: 20px; margin-right: 8px; padding: 0 5px; border-radius: 6px;
  background: var(--navy); color: #fff; font-family: var(--font-body); font-size: var(--t-label);
  font-weight: 700; vertical-align: 1px; }
.wlrow .wlwhen { display: block; margin-top: 3px; font-size: var(--t-label); color: var(--muted); }

/* owner, 29 Aug: "on mobile, the offer slot buttons are all over the place, it's not consistent."
   The row was one wrapping flex line, so where the button landed depended on how long that row's
   name / service / waiting text happened to be — three rows on one line, the next two on two.
   Fixed by giving every row the same four bands, so the button is ALWAYS bottom-right whatever
   the text does. */
.wlrow > b,
.wlrow > span:not(.wlwhen) { flex: 0 0 100%; margin-right: 0; }
.wlrow > .wlwhen { flex: 0 0 100%; }
.wlrow > em { flex: 0 1 auto; }
.wlrow > button { flex: 0 0 auto; margin-left: auto; }
.wlrow:has(> button) { padding-bottom: 12px; }

/* which rule this half of the list runs on (owner, 29 Aug) */
.wlgroup { display: flex; align-items: baseline; gap: 8px; flex-wrap: wrap;
  margin: 16px 2px 8px; padding-bottom: 6px; border-bottom: 1px solid var(--line); }
.wlgroup:first-child { margin-top: 2px; }
.wlgroup b { font-family: var(--font-heading); color: var(--navy); font-size: var(--t-small); }
/* the accepted section is a record, not a queue — set apart so the list above reads as the queue */
.wlgroup.done { margin-top: 22px; border-top: 1px solid var(--line); border-bottom: none;
  padding-top: 14px; padding-bottom: 0; }
.wlgroup.done b { color: #1f7a3d; }
.chip.wladd { width: 100%; justify-content: center; margin-bottom: 4px; border-style: dashed; }
.wlgroup.off { margin-top: 18px; border-top: 1px solid var(--line); border-bottom: none;
  padding-top: 14px; padding-bottom: 0; }
.wlgroup.off b { color: var(--muted); }
/* the 15 minutes are up but the time is still theirs — good news, so it does not read as a
   failure the way the other lapsed states do */
.wlrow em.wlstill { background: #e8f0fb; color: #23477e; font-weight: 600; }
.wlrow em.wloff { background: #f2f3f6; color: #6b7386; font-weight: 600; }
.wlrow .wlremovebtn { color: #a33; border-color: rgba(170,51,51,.32); }
.wlrow .wlremovebtn:hover { border-color: #a33; }
.wlgroup span { color: var(--muted); font-size: var(--t-label); }
.wlrow b .wlwhy { font-family: var(--font-body); font-size: var(--t-nano); font-weight: 700;
  letter-spacing: .04em; text-transform: uppercase; color: #8a6d1f; background: #faf0d8;
  border-radius: 99px; padding: 2px 7px; margin-left: 7px; vertical-align: 1px; }
.wlrow em.wlgood { background: #e4f3e8; color: #1f7a3d; font-weight: 700; }
/* the offer line on the panel — deliberately the SAME pill the waitlist row uses (owner, 29 Aug:
   "it can follow the same … as it currently shows on the live waitlist"), just given room to
   wrap, because on a panel it is a sentence rather than a chip at the end of a row */
.offerline { margin: 0 0 14px; }
.offerline em.warn { display: block; font-size: var(--t-caption); line-height: 1.5; padding: 8px 12px;
  border-radius: 10px; }
.offerline em.warn b.holdclock { font-variant-numeric: tabular-nums; }

/* taken off the list because staff booked them — settled, but not the same as a claimed offer */
.wlrow em.wldone { background: #edeff3; color: #3f4757; font-weight: 600; }

/* A WAITLIST RESERVATION in the diary (owner, 29 Aug). Deliberately distinct from the gold hatch
   of a checkout hold: this is a time we have taken off sale for somebody who has not replied yet,
   which is a different thing from somebody mid-payment. */
.apt.hold.offerhold { background: repeating-linear-gradient(135deg, #fdf3e0 0 8px, #f8e9cf 8px 16px);
  border: 1.5px dashed #c99a2e; color: #6d5312; }
.wlrow .wlwhy2 { flex: 0 0 100%; margin-top: 6px; font-size: var(--t-caption); line-height: 1.45;
  color: var(--muted); }
.chip.wlblocked { opacity: .45; cursor: not-allowed; text-decoration: line-through; }
.chip.wlout { border-style: dashed; opacity: .8; }

/* people waiting on OTHER days — the button reads 0 for today, this is where that is explained */
.wlelse { margin-top: 16px; padding: 12px; border: 1px dashed var(--line); border-radius: 10px;
  display: flex; align-items: center; gap: 8px; flex-wrap: wrap; }
.wlelse b { flex: 0 0 100%; font-family: var(--font-heading); color: var(--navy); font-size: var(--t-caption); }

/* credits at checkout — applied by default, removable (owner, 28 Aug) */
.creditrow { display: flex; align-items: center; gap: 12px; justify-content: space-between;
  border: 1.5px solid var(--line); border-radius: 12px; padding: 11px 14px; margin-bottom: 8px; max-width: 720px; }
.creditrow.on { border-color: var(--gold); background: #fbf8f1; }
.creditrow span { display: block; font-size: var(--t-ui); color: var(--navy); }
.creditrow small { display: block; color: var(--muted); font-size: var(--t-caption); margin-top: 2px; }

/* "Book another time as well" — the third of the stacked offers under the date strip, full width like the calendar and
   waitlist buttons above it, once a time is chosen (owner, 25 Sept 2026) */
.moretime { margin: 2px 0 16px; }
/* the basket of chosen times (owner, 28 Aug: book several at once) */
/* centred, not left-hugging (owner, 11 Sept) — everything else in the time picker is a centred
   column, and a box capped at 720px with margin-left 0 sat visibly off-axis from the buttons
   directly above it. */
.basket { border: 1.5px solid var(--gold); border-radius: 14px; padding: 14px 16px;
  margin: 4px auto 18px; max-width: 720px; background: #fbf8f1; }
.baskethead { display: flex; align-items: baseline; gap: 10px; margin-bottom: 8px; }
.baskethead b { font-family: var(--font-heading); font-size: var(--t-body); color: var(--navy); }
.baskethead small { color: var(--muted); font-size: var(--t-caption); }
.basketrow { display: flex; align-items: center; justify-content: space-between; gap: 12px;
  padding: 7px 0; border-top: 1px solid rgba(114,105,79,.2); font-size: var(--t-ui); color: var(--navy); }
.basketrow:first-of-type { border-top: none; }

/* the sauna starting temperature from the contrast waiver — the desk acts on this before
   the guest arrives, so it reads at a glance (owner, 28 Aug) */
.saunatemp { display: grid; grid-template-columns: auto 1fr; align-items: baseline; gap: 4px 14px;
  border: 2px solid var(--gold); border-radius: 14px; padding: 12px 16px; margin-bottom: 14px;
  background: #fbf6ea; }
.saunatemp span { grid-column: 1; font-family: var(--font-body); font-size: var(--t-label); font-weight: 700;
  letter-spacing: .08em; text-transform: uppercase; color: var(--bronze); }
.saunatemp b { grid-column: 2; grid-row: 1 / span 2; justify-self: end; align-self: center;
  font-family: var(--font-heading); font-size: var(--t-h1); line-height: 1; color: var(--navy); }
.saunatemp small { grid-column: 1; font-size: var(--t-caption); color: var(--muted); }
.saunatemp.none b { font-size: var(--t-lead); color: var(--muted); }
.saunatemp.over { border-color: #c0392b; background: #fbeeec; }
.saunatemp.over b, .saunatemp.over small { color: #a33; }

/* owner, 28 Aug: bank holidays are closed — the date reads as unavailable and cannot be picked */
.day.shut { opacity: .38; cursor: not-allowed; border-style: dashed; background: #f7f5ef; }
/* owner, 2 Oct 2026: a closed day on the booking page's strip says CLOSED under the date (site.css colours it for the dark skin) */
.day em { display: block; margin-top: 2px; font-style: normal; font-size: var(--t-label); text-transform: uppercase; letter-spacing: 1px; color: #8a2a2a; font-weight: 700; }

/* owner, 28 Aug: the waitlist is an offer of help, not a form — one small card, chips instead
   of dropdowns, and a single button for anyone we already know. */
.wlbox { border: 1.5px solid var(--line); border-radius: 14px; padding: 16px; margin: 6px 0 4px; text-align: left; }
.wlbox b { display: block; font-family: var(--font-heading); font-size: var(--t-input); color: var(--navy); }
.wlbox p { color: var(--muted); font-size: var(--t-small); margin: 4px 0 10px; }
.wlbox .chips { margin-top: 0; margin-bottom: 10px; }
.wlgo { width: 100%; background: var(--navy); color: #fff; border-radius: 12px; padding: 13px;
  font-family: var(--font-heading); font-weight: 600; font-size: var(--t-body); }
.wlwho { text-align: center; font-size: var(--t-caption); color: var(--muted); margin-top: 8px; }
.wlform { max-width: none; margin: 0 0 10px; }

/* editing a guest's details — plain labelled fields, nothing clever */
.editform { display: flex; flex-direction: column; gap: 10px; }
.editform label { display: block; font-size: var(--t-label); font-weight: 600; color: var(--muted);
  text-transform: uppercase; letter-spacing: .08em; }
.editform input { width: 100%; margin-top: 5px; border: 1.5px solid var(--line); border-radius: 10px;
  padding: 11px 12px; font-size: var(--t-body); font-family: var(--font-body); color: var(--navy);
  text-transform: none; letter-spacing: normal; }
.editform input:focus { outline: none; border-color: var(--gold); }

/* the whole date box is the button, so it should say so under the cursor */
input[type="date"] { cursor: pointer; }

/* the build stamp beside "prototype" — quieter than it, because it is a diagnostic, not a label.
   It exists so a stale copy of this page identifies itself on sight (see staff.html's .brand). */
.brand .build { opacity: .55; font-weight: 400; letter-spacing: .04em; margin-left: 2px; }

/* ============================================================================
   THE STAFF SHELL — blocker 19 (owner items 212, 214, 215), 30 Aug 2026.
   Everything here is scoped to .staffpage so the customer flow and /account,
   which share .topbar and .eyebrow, cannot be disturbed by it.

   staff.html loads ONLY this stylesheet — Design B never reaches the diary —
   and there was no @media rule for the header at all, so the topbar overflowed
   the viewport by 40px on a phone (measured, 390px).
   ========================================================================== */

/* ---- 214: the header stops overflowing and lines up ---- */
.staffpage .topbar { flex-wrap: wrap; gap: 10px 14px; }
.staffpage .topbar .brand { min-width: 0; flex-wrap: wrap; row-gap: 2px; }
/* the build stamp is load-bearing for debugging (a stale copy once cost an evening),
   so it wraps and shrinks rather than being hidden on small screens */
.staffpage .topbar .brand .build { font-size: var(--t-nano); letter-spacing: 1.5px; opacity: .75; }

/* 🔴 the Sign out in the top bar is a real button — red, so nobody mistakes it for a label (owner, 1 Oct 2026). `.pill` is
   styled only inside the day head, which is why it drew as bare black text up here. */
.staffpage .topbar #signedinas { color: inherit; opacity: .9; font-weight: 600; }
.staffpage .topbar .signout { background: #b3261e; color: #fff; border: 0; border-radius: 10px; padding: 8px 14px;
  font: inherit; font-size: var(--t-body); font-weight: 600; cursor: pointer; white-space: nowrap; }
.staffpage .topbar .signout:hover { background: #961f18; }
.staffpage .topbar .signout:focus-visible { outline: 2px solid #fff; outline-offset: 2px; }

@media (max-width: 720px) {
  .staffpage .topbar { padding: 10px 14px; }
  .staffpage .topbar .brand { width: 100%; font-size: var(--t-body); }
  .staffpage .topbar > div:last-child { width: 100%; justify-content: space-between; gap: 10px; }
  .staffpage .topbar #rolepick { flex: 1 1 auto; min-width: 0; }
  .staffpage .topbar a { white-space: nowrap; }
}

/* ---- 215: the action pills get one shape, and colour stops carrying two meanings ----
   Nine pills previously wrapped into ragged rows at four different widths. A shared
   min-width makes them wrap into tidy columns instead of a staircase. */
.staffpage .dhead .pill {
  min-width: 104px;
  justify-content: center;
  /* …and vertically (owner, 30 Sept 2026: "The text in the buttons is not centred. Some are higher than others"). On a
     phone the secondary pills and More are shown as inline-flex, which drops a button's own vertical centring; the rest
     stayed plain buttons and kept it — so half a row sat high. Centring the flex line puts every label on one height. */
  align-items: center;
  text-align: center;
  white-space: nowrap;
}

/* GOLD IS RESERVED FOR THE ONE PRIMARY DAY ACTION (Close day). The Live pill used to
   borrow it on every refresh, so gold meant both "this closes the day" and "the diary
   just updated" — two unrelated things in the same colour, four seconds apart. */
.staffpage .dhead .pill#livepill {
  min-width: 0;
  border-style: dashed;
  border-color: color-mix(in srgb, var(--line) 70%, transparent);
  background: transparent;
  color: color-mix(in srgb, currentColor 62%, transparent);
  font-variant-numeric: tabular-nums;
}
.staffpage .dhead .pill#livepill.flash {
  border-style: solid;
  border-color: var(--bronze);
  color: var(--bronze);
  background: color-mix(in srgb, var(--bronze) 8%, transparent);
}

/* ---- 212: panel section headings stop getting lost ----
   HEALTH FLAGS / MASSAGE BRIEF / FORMS / STATUS / CHECKOUT were doing the most
   structural work on the screen while being its weakest element: 12px, muted,
   and with nothing separating one section from the next. */
.staffpage #panel .eyebrow {
  font-size: var(--t-caption);
  letter-spacing: 1.6px;
  color: var(--navy);
  padding-top: 16px;
  margin-top: 4px;
  border-top: 1px solid color-mix(in srgb, var(--line) 85%, transparent);
}
/* the first heading in a panel needs no rule above it — it would sit under the title */
.staffpage #panel .pbody > .eyebrow:first-child {
  border-top: 0; padding-top: 0; margin-top: 0;
}

/* owner, 2 Sept: "Book appointment" reads as the day's primary action — green, white text. The
   studio's own green (--ok, the "nothing to take" / success colour), the same shape as .gold and
   .red. Listed with the .staffpage override so it wins whichever order the sheet loads in. */
.dhead .pill.green, .staffpage .dhead .pill.green { background: var(--ok); border-color: var(--ok); color: #fff; }
.dhead .pill.green:hover, .staffpage .dhead .pill.green:hover { background: #276a43; border-color: #276a43; color: #fff; }

/* ---- THE STAFF DIARY ON A PHONE (item 366 — owner, 30 Aug; built 2 Sept) ----
   Measured before: 846px of shell on an 844px screen — topbar 83, KPI cards 249, head with eight
   pills in four rows 315 — so a staff member scrolled a full screen before a single appointment.
   Now: one-row topbar, the KPI tiles in two short rows (still all on screen, item 217 — nothing to
   swipe), three primary pills plus Waitlist and a More toggle for the occasional ones, every tap
   target at least 44px tall, and a one-time hint that the rooms scroll sideways. */
.morepill, .swipehint { display: none; }
@media (max-width: 520px) {
  .staffpage .topbar { padding: 8px 12px; gap: 8px; flex-wrap: nowrap; align-items: center; }
  .staffpage .topbar .brand { width: auto; flex: 1 1 auto; font-size: var(--t-ui); }
  .staffpage .topbar .brand .build { display: none; }
  .staffpage .topbar > div:last-child { width: auto; flex: 0 1 auto; }
  .staffpage .topbar > div:last-child a { display: none; }
  .staffpage .topbar #rolepick { min-height: 44px; font-size: var(--t-ui); }
  /* KPI tiles: three occupancy tiles across one row, the two money tiles across the next — 5 tiles in
     2 rows, all on screen, none scrolling (item 217 stands) */
  .staffpage .kpis { grid-template-columns: repeat(6, 1fr); gap: 6px; margin-top: 8px; padding: 0 12px; }
  .staffpage .kpi { grid-column: span 2; padding: 5px 8px; border-radius: 10px; }
  .staffpage .kpi:nth-child(n+4) { grid-column: span 3; }
  .staffpage .kpi:last-child:nth-child(odd) { grid-column: span 3; }
  .staffpage .kpi:nth-child(3):last-child { grid-column: span 2; }   /* three tiles (desk, Jade): one row */
  .staffpage .kpi:nth-child(4):last-child { grid-column: span 6; }
  .staffpage .kpi b { font-size: var(--t-ui); line-height: 1.2; }
  .staffpage .kpi b span { display: inline; font-size: var(--t-nano); margin-left: 4px; }
  .staffpage .kpi small { font-size: var(--t-nano); letter-spacing: 1px; }
  /* the diary head */
  .staffpage .dhead { gap: 6px; margin-bottom: 8px; }
  .staffpage .dhead h1 { font-size: var(--t-price); margin: 0; }
  .staffpage .diary { padding-top: 8px; }
  /* the date nav and the view toggle share one row */
  .staffpage .dhead .nav { flex: 1 1 52%; margin-right: 0; gap: 4px; }
  .staffpage .dhead .nav button, .staffpage .dhead .nav input[type="date"] { min-height: 44px; flex: 1 1 auto; padding: 6px 8px; font-size: var(--t-caption); }
  .staffpage .dhead .nav input[type="date"] { flex: 1 1 40%; min-width: 0; }
  .staffpage .viewtoggle { flex: 1 1 40%; }
  .staffpage .viewtoggle button { min-height: 44px; flex: 1 1 50%; }
  .staffpage .csearchwrap { flex: 1 1 100%; }
  .staffpage .csearchwrap .csearch { min-height: 44px; width: 100%; }
  /* 🚨 `min-width: 0` is what cut the label in half. A flex item defaults to `min-width: auto`,
     which already refuses to shrink below its own content; setting it to 0 removes that floor and
     lets a third of the row win over the word inside it. At 390px a third is 111px and "Book
     appointment" needs 116, so it rendered as "Book appointmen" — and only below about 400px, which
     is why it looked fine on a larger phone and was missed. Measured 5 Sept: truncated at 360 and
     390, fine at 414.
     `fit-content` keeps the thirds as the TARGET and the label as the FLOOR, so the pills stay even
     when they fit and the row — which already wraps — wraps when they do not. Wrapping is the
     established behaviour here, not a new one: Waitlist and More are already on a second row. */
  .staffpage .dhead .pill { min-width: fit-content; min-height: 44px; flex: 1 1 calc(33.333% - 6px); padding: 8px 6px; font-size: var(--t-caption); }
  .staffpage .dhead .pill.secondary { display: none; }
  .staffpage .dhead.more .pill.secondary { display: inline-flex; }
  .staffpage .dhead .pill.morepill { display: inline-flex; }
  .staffpage .dhead .pill.morepill.red { background: #7a2f2f; border-color: #7a2f2f; color: #fff; }
  /* the rooms scroll sideways; say so once */
  .staffpage .swipehint { display: block; font-size: var(--t-label); color: var(--muted); text-align: right; margin: -4px 0 6px; }
  .staffpage .diarywrap { -webkit-overflow-scrolling: touch; scroll-snap-type: x proximity; }
  /* panels: chips and actions a thumb can hit */
  .staffpage #panel .chip { min-height: 40px; }
  .staffpage #panel .payopt { min-height: 48px; }
}

/* the picker's row labels: what this row of chips is asking (owner, 2 Sept) */
.staffpage #panel .picklabel { font-size: var(--t-label); font-weight: 600; color: var(--navy); margin: 12px 0 4px; letter-spacing: .04em; }

/* A document row with more than one action: the chips drop to their own line rather than
   squeezing the label into a one-word-per-line column. `gap` keeps them apart when they wrap. */
.repline--actions { flex-wrap: wrap; gap: 6px 10px; }
.repline--actions > span { flex: 1 1 100%; }
.repline--actions > b { flex: 1 1 100%; display: flex; flex-wrap: wrap; gap: 6px; justify-content: flex-start;
  /* .repline > b is nowrap so a money column never breaks mid-figure. The actions container holds
     SENTENCES as well as chips (the retention line), and they inherited it — the text ran off the
     side and had to be scrolled to (owner, 4 Sept). Only this container opts out. */
  white-space: normal; }
.repline--actions > b small { flex: 1 1 100%; white-space: normal; margin-left: 0; }
.repline--actions > b .chip { margin-left: 0 !important; }

/* A gap Jade can open: drawn over the raw hole between two appointments, i.e. INCLUDING the
   turnaround the ordinary free-window has already taken off. Deliberately not styled like a free
   slot — it is an offer to change the diary's rules, not a slot you can book as it stands. */
.gapopen { position: absolute; left: 3px; right: 3px; border: 1px dashed var(--brand-accent, #72694f);
  border-radius: 8px; background: rgba(245,243,238,.96); color: var(--brand-accent, #72694f);
  font-size: var(--t-nano); font-weight: 600; padding: 0 7px; cursor: pointer; overflow: hidden; z-index: 3;
  display: flex; align-items: center; gap: 6px; white-space: nowrap; line-height: 1.1; }
.gapopen:hover { background: rgba(114,105,79,.14); }
.gapopen small { font-weight: 400; opacity: .75; font-size: var(--t-nano); overflow: hidden; text-overflow: ellipsis; }
/* …and one that IS open: solid, because it is now a state of the diary rather than an offer */
.opengap { position: absolute; left: 3px; right: 3px; border: 1px solid var(--brand-accent, #72694f);
  border-radius: 8px; background: var(--brand-accent, #72694f); color: #fff;
  font-size: var(--t-nano); font-weight: 600; padding: 0 7px; cursor: pointer; overflow: hidden; z-index: 3;
  display: flex; align-items: center; gap: 6px; white-space: nowrap; line-height: 1.1; }
.opengap small { font-weight: 400; opacity: .85; font-size: var(--t-nano); overflow: hidden; text-overflow: ellipsis; }

/* ═══ iOS SAFARI ZOOM GUARD — the staff site ══════════════════════════════════════════════════
   Owner, 5 Sept 2026, after the customer-side fix: *"this is also an issue, regarding zoom, on the
   mobile version of the staff site as well."* He was right, and it is worse here: measured 33
   typable fields under the threshold, some at 12.5px, and at least three on EVERY screen because
   the date control and the guest search live in the topbar.

   Safari on iOS zooms the whole page when a field under 16px is tapped. On the customer side that
   is an annoyance during one form. On the diary it is worse: the desk taps the guest search dozens
   of times a shift, and each tap throws the board out of position mid-service.

   ⚠️ THE DIARY IS DESIGNED DENSE, at 13px, and this makes those fields taller. That is the trade
   and it is the right way round — a field two pixels larger costs a little space, a page that
   lurches on every tap costs the person using it. Only TYPABLE fields are lifted; the chips, pills,
   badges and lane labels that carry the density are untouched.

   ⚠️ Every width, not just a phone breakpoint: the trigger is the browser, and an iPhone in
   landscape is 844px wide and still zooms.
   ⚠️ Specificity is deliberate, not padding — see the same block in site.css. A bare `textarea`
   and `select` lose to any class and the rule half-works, which is exactly what happened first
   time on the customer side. */
input:not([type="checkbox"]):not([type="radio"]):not([type="range"]),
textarea:not(:disabled),
select:not(:disabled) { font-size: max(16px, 1em); }

/* ═══ ⌨️ WHERE THE KEYBOARD IS (owner, 19 Sept 2026: the accessibility pass) ══════════════════════════════════
   There was no focus style anywhere on either site, so anyone working by keyboard — and anyone using a screen
   reader, and anyone whose hands do not manage a phone screen — could not see what they were about to press.
   :focus-visible, so it shows for the keyboard and not on every mouse click, and it is drawn OUTSIDE the control
   (outline-offset) so a dark chip on a dark card still shows a ring. */
:focus-visible {
  outline: 3px solid #CFC7B6;
  outline-offset: 2px;
  border-radius: 6px;
}
/* on a cream surface the cream ring would vanish: the navy is the one that shows there */
.svc:focus-visible, .chip:focus-visible, button:focus-visible, a:focus-visible, input:focus-visible,
select:focus-visible, textarea:focus-visible, [tabindex]:focus-visible { outline-color: #CFC7B6; }
.cream :focus-visible, .lightcard :focus-visible { outline-color: #071436; }
/* a mouse press keeps its old look */
:focus:not(:focus-visible) { outline: none; }

/* ══ THE FIELDS INSIDE AN ASK BOX ════════════════════════════════════════════════════════════════
   🚨 THEY HAD NO STYLE AT ALL. The three details fields (new email, new mobile, the code) carried
   `darkinput`, which is defined only in the payment shell — a different document — so on the account
   page they rendered as raw browser boxes on a navy card. Seen on screen 20 Sept, not by any check.
   First fix put these in the card modal's own <style>, which only exists while that modal is open:
   they must live in a sheet the page always has. Shaped like the package-code field on the same page. */
#askbox .darkinput { display: block; width: 100%; box-sizing: border-box; padding: 10px 12px;
  border: 1.5px solid #d8d2c4; border-radius: 10px; font-size: var(--t-body); background: #FBFAF6;
  color: #2A3140; margin: 6px 0 0; font-family: inherit; }
#askbox .darkinput::placeholder { color: #8a8f9c; }
#askbox .darkinput:focus { outline: 0; border-color: #72694f; box-shadow: 0 0 0 2px rgba(114,105,79,.25); }
#askbox .fieldlabel { display: block; font-size: var(--t-caption); font-weight: 700; letter-spacing: .04em;
  text-transform: uppercase; color: var(--cream-dim, #CFC7B6); margin-bottom: 2px; }
/* 🚪 a closed day's pods and suite (owner, 30 Sept 2026) — unmistakably closed, not an empty lane */
.blocked.closedday { background: repeating-linear-gradient(135deg, #e6e2d8 0 10px, #eeeae1 10px 20px); border: 1.5px solid #cfc7b5;
  color: #5b5446; font-weight: 700; cursor: pointer; }
