:root{--ink:#19312d;--muted:#5b6a67;--line:#e7ebe7;--canvas:#f5f6f2;--card:#fff;--green:#173d35;--green2:#245b4d;--mint:#dcebe5;--gold:#bd9252;--gold-ink:#8a5f22;--gold-bg:#f6ecdc;--red:#b64949;--red-ink:#9c3535;--red-bg:#fae7e5;--amber:#8f5612;--amber-bg:#fbefd9;--shadow:0 12px 36px rgba(31,52,45,.08);--radius:18px}
.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}
*{box-sizing:border-box}html,body{min-height:100%}body{margin:0;background:var(--canvas);color:var(--ink);font-family:"Segoe UI",Arial,sans-serif}.app-shell{min-height:100vh;display:grid;grid-template-columns:248px minmax(0,1fr)}.sidebar{position:sticky;top:0;height:100vh;background:var(--green);color:#fff;padding:24px 16px 14px;display:flex;flex-direction:column;overflow:hidden;z-index:10}.brand{display:flex;align-items:center;gap:12px;padding:0 8px 20px;flex:none}.brand-mark{display:grid;place-items:center;width:38px;height:38px;border-radius:12px;background:var(--gold);font-family:Georgia,serif;font-size:22px}.brand strong{font-family:Georgia,serif;font-size:21px}.brand small,.property-switch small,.profile small,.system-mini small{display:block;color:#adc0ba;font-size:11px;margin-top:2px}.property-switch{display:flex;align-items:center;gap:10px;background:rgba(255,255,255,.08);border:1px solid rgba(255,255,255,.1);border-radius:14px;padding:11px;margin-bottom:10px;flex:none}.property-icon,.avatar{display:grid;place-items:center;width:35px;height:35px;flex:none;border-radius:10px;background:#315f53;font-size:11px;font-weight:700}.property-switch span:nth-child(2){min-width:0;flex:1}.property-switch strong{font-size:13px}.property-switch button,.profile button{color:white;border:0;background:none}nav{display:grid;gap:5px;overflow-y:auto;overscroll-behavior:contain;padding:4px 3px 18px 0;scrollbar-width:thin;scrollbar-color:#55786f transparent}
/* THE NAVIGATION SCROLLS, AND NOTHING SAID SO.
   Thirteen items do not fit beside the hero and the profile block: measured
   583px of room for 773px of list at 1440x900, and 451px at 1366x768 — the
   commonest laptop there is. The list was reachable by wheel the whole time,
   so nothing was ever lost; what was missing was any sign there was more. The
   nav ended at "Rates & property" with the profile directly beneath it and
   read as a finished list, so Health, Team and Control were undiscoverable.

   NOT SOLVED WITH A SCROLLBAR. `scrollbar-width` and ::-webkit-scrollbar are
   mutually exclusive in Chromium, and even with that resolved the bar is an
   OVERLAY on every platform this actually runs on — phones, tablets, Macs —
   painted only while the wheel moves and measuring zero pixels at rest.
   Measured: scrollbar width 0px with the pseudo-elements styled.

   So the affordance is a fade at the bottom edge, switched on by app.js only
   when the list really overflows and off again once it is scrolled to the end
   — CSS cannot ask "does this overflow", which is the whole reason this needs
   one line of script. The webkit rules stay as a bonus for the platforms that
   do reserve a bar; they are not what carries the signal. */
.sidebar nav::-webkit-scrollbar{width:8px}
.sidebar nav::-webkit-scrollbar-track{background:transparent}
.sidebar nav::-webkit-scrollbar-thumb{background:#55786f;border-radius:8px;
  border:2px solid transparent;background-clip:content-box}
.sidebar nav.is-scrollable{
  -webkit-mask-image:linear-gradient(to bottom,#000 calc(100% - 34px),transparent);
  mask-image:linear-gradient(to bottom,#000 calc(100% - 34px),transparent)}
.sidebar nav.is-scrollable.at-end{-webkit-mask-image:none;mask-image:none}.nav-label{color:#8fb0a6;font-size:10px;font-weight:700;letter-spacing:.12em;padding:16px 12px 5px}.nav-item{width:100%;display:flex;align-items:center;gap:12px;border:0;background:none;color:#bcd0ca;padding:10px 12px;border-radius:10px;text-align:left;font:inherit;font-size:13px;cursor:pointer;flex:none}.nav-item:hover,.nav-item.active{background:rgba(255,255,255,.1);color:white}.nav-item .nav-icon{width:20px;text-align:center;font-size:15px}.badge{margin-left:auto;background:#cf635c;color:white;border-radius:20px;padding:2px 7px;font-size:10px;font-weight:700}.sidebar-foot{margin-top:auto;flex:none;background:var(--green)}.system-mini,.profile{display:flex;align-items:center;gap:10px;padding:10px 8px;border-top:1px solid rgba(255,255,255,.1);font-size:12px}.system-mini strong,.profile strong{font-size:12px}.pulse{width:9px;height:9px;border-radius:50%;background:#76d5a8;box-shadow:0 0 0 4px rgba(118,213,168,.13)}.profile span:nth-child(2){flex:1}
.main{min-width:0;min-height:100vh}.topbar{display:flex;align-items:center;justify-content:space-between;gap:18px;padding:28px 34px 20px}.eyebrow{margin:0 0 5px;color:var(--gold-ink);font-size:10px;font-weight:700;letter-spacing:.12em}.topbar h1{margin:0;font-family:Georgia,serif;font-size:28px;font-weight:600}.top-actions{display:flex;gap:9px}.menu{display:none}.icon-button,.menu,.secondary,.primary,.quiet{border:1px solid var(--line);background:white;color:var(--ink);border-radius:10px;padding:9px 12px;font:inherit;cursor:pointer}.icon-button{width:40px;height:40px;font-size:18px;position:relative}.notification span{position:absolute;right:-3px;top:-5px;background:var(--red);color:#fff;font-size:9px;width:17px;height:17px;border-radius:50%;display:grid;place-items:center}.primary{background:var(--green);color:white;border-color:var(--green);font-weight:600;padding:10px 16px}.primary:hover{background:var(--green2)}.secondary{font-weight:600}.quiet{border:0;background:transparent;color:var(--green);font-weight:600;padding:6px}.content{padding:0 34px 72px;min-height:calc(100vh - 94px)}.notice{background:linear-gradient(100deg,#173d35,#24584c);color:white;border-radius:var(--radius);padding:18px 22px;display:flex;align-items:center;gap:15px;margin-bottom:18px;box-shadow:var(--shadow)}.notice-icon{font-size:23px}.notice strong{display:block;font-size:14px;margin-bottom:3px}.notice p{margin:0;color:#c8d9d4;font-size:12px}.notice button{margin-left:auto;background:white;color:var(--green);border:0;border-radius:9px;padding:9px 13px;font-weight:700}
.metrics{display:grid;grid-template-columns:repeat(5,1fr);gap:13px;margin-bottom:18px}.metric,.card{background:var(--card);border:1px solid var(--line);border-radius:var(--radius);box-shadow:0 3px 12px rgba(31,52,45,.025)}.metric{padding:17px}.metric-head{display:flex;justify-content:space-between;color:var(--muted);font-size:11px;font-weight:600}.metric-icon{width:29px;height:29px;border-radius:9px;display:grid;place-items:center;background:var(--mint);color:var(--green)}.metric-value{font-family:Georgia,serif;font-size:27px;margin:13px 0 5px}.metric-foot{font-size:10px;color:var(--muted)}.up{color:#27825d}.down{color:var(--red-ink)}.dashboard-grid{display:grid;grid-template-columns:minmax(0,1.55fr) minmax(280px,.8fr);gap:18px}.card{padding:19px}.card-head{display:flex;align-items:center;justify-content:space-between;margin-bottom:16px}.card-head h2{font-family:Georgia,serif;font-size:18px;margin:0}.card-head p{font-size:11px;color:var(--muted);margin:4px 0 0}.timeline{display:grid}.timeline-row{display:grid;grid-template-columns:65px 11px 1fr auto;gap:12px;align-items:start;min-height:66px}.timeline-row:has(.turnover-button){grid-template-columns:65px 11px 1fr auto auto}.timeline-time{font-size:11px;color:var(--muted);padding-top:2px}.timeline-marker{width:10px;height:10px;border-radius:50%;background:var(--green);box-shadow:0 0 0 4px var(--mint);position:relative;margin-top:3px}.timeline-marker:after{content:'';position:absolute;width:1px;height:52px;background:var(--line);left:4px;top:10px}.timeline-row:last-child .timeline-marker:after{display:none}.timeline-copy strong{font-size:12px;display:block}.timeline-copy span{font-size:11px;color:var(--muted)}.status{font-size:9px;font-weight:700;border-radius:20px;padding:5px 8px;text-transform:uppercase;letter-spacing:.04em}.status.green{background:var(--mint);color:var(--green2)}.status.amber{background:var(--amber-bg);color:var(--amber)}.status.red{background:var(--red-bg);color:var(--red-ink)}.attention-list{display:grid;gap:10px}.attention{display:flex;gap:11px;padding:12px;border:1px solid var(--line);border-radius:13px;cursor:pointer}.attention:hover{border-color:#b8cac4}
/* A handoff with no conversation behind it — an undeliverable send. It is a
   task, not a thread, so it must not look like something you can open. */
/* A document a guest sent. An <img> cannot render a PDF, so it gets a link —
   and the route serves it content-disposition: attachment, because a PDF
   opened inline is a scripting surface on the origin holding the session. */
.bubble-file{display:inline-block;margin-bottom:6px;padding:7px 10px;border-radius:9px;background:rgba(0,0,0,.06);color:inherit;font-size:12px;text-decoration:none}
.bubble-file:hover{background:rgba(0,0,0,.11);text-decoration:underline}
/* Delivery ticks, read the way WhatsApp's are. Grey for sent and delivered,
   blue for read, and words rather than a colour for a failure — a red tick is
   too easy to scroll past, and that is the one state needing an action. */
.bubble-tick{margin-left:5px;font-size:11px;letter-spacing:-1px;opacity:.75}
.bubble-tick.tick-read{color:#3aa3ff;opacity:1}
.bubble-tick.tick-failed{letter-spacing:0;font-size:10px;font-weight:700;color:var(--red-ink);background:var(--red-bg);border-radius:5px;padding:1px 5px;opacity:1}
.attention.attention-flat{cursor:default}
.attention.attention-flat:hover{border-color:var(--line)}
.attention-symbol{width:34px;height:34px;display:grid;place-items:center;border-radius:10px;background:var(--amber-bg)}.attention div:nth-child(2){flex:1}.attention strong{font-size:12px;display:block}.attention p{font-size:10px;color:var(--muted);margin:3px 0}.attention small{font-size:9px;color:var(--amber);font-weight:700}.occupancy{margin-top:18px}.progress-head{display:flex;justify-content:space-between;font-size:11px;margin-bottom:8px}.progress{height:8px;background:#edf0ed;border-radius:8px;overflow:hidden}.progress span{display:block;height:100%;width:82%;background:linear-gradient(90deg,var(--green),#4a8976);border-radius:8px}
.page-grid{display:grid;grid-template-columns:repeat(12,1fr);gap:18px}.span-12{grid-column:span 12}.span-8{grid-column:span 8}.span-7{grid-column:span 7}.span-5{grid-column:span 5}.span-4{grid-column:span 4}.toolbar{display:flex;align-items:center;gap:10px;margin-bottom:15px}.searchbox{flex:1;border:1px solid var(--line);border-radius:11px;padding:11px 13px;background:white;font:inherit}.filter{border:1px solid var(--line);background:#fff;border-radius:10px;padding:10px 12px;color:var(--ink)}table{width:100%;border-collapse:collapse;font-size:11px}th{text-align:left;color:var(--muted);font-weight:600;padding:10px 9px;border-bottom:1px solid var(--line)}td{padding:13px 9px;border-bottom:1px solid #eff1ef}tr:last-child td{border:0}.guest-cell{display:flex;align-items:center;gap:9px}.guest-cell .avatar{background:var(--mint);color:var(--green)}.ref{font-weight:700;color:var(--green)}.amount{font-weight:700}.inbox{display:grid;grid-template-columns:290px 1fr 280px;height:calc(100vh - 125px);padding:0;overflow:hidden}.conversation-list{border-right:1px solid var(--line);overflow:auto}.conversation-search{padding:15px;border-bottom:1px solid var(--line)}.conversation{padding:14px 15px;border-bottom:1px solid var(--line);cursor:pointer}.conversation.active{background:#f0f6f3;border-left:3px solid var(--green)}.conversation-top{display:flex;justify-content:space-between}.conversation strong{font-size:12px}.conversation time,.conversation p{font-size:10px;color:var(--muted)}.conversation p{white-space:nowrap;overflow:hidden;text-overflow:ellipsis;margin:5px 0}.chat{display:flex;flex-direction:column;min-width:0}.chat-head{display:flex;align-items:center;padding:13px 16px;border-bottom:1px solid var(--line);gap:10px}.chat-head .chat-who{flex:1;min-width:0}.chat-head strong{display:block;font-size:12px}.chat-head small{color:var(--muted);font-size:10px}.messages{padding:20px;overflow:auto;flex:1;background:#f8f9f6}.bubble{max-width:75%;border-radius:14px;padding:10px 12px;margin-bottom:10px;font-size:11px;line-height:1.5}.bubble.guest{background:#fff;border:1px solid var(--line)}.bubble.bot{background:var(--green);color:#fff;margin-left:auto}.bubble.staff{background:var(--gold-bg);margin-left:auto}.bubble small{display:block;opacity:.65;text-align:right;margin-top:4px;font-size:8px}.chat-compose{padding:12px;border-top:1px solid var(--line);display:flex;gap:8px}.chat-compose input{flex:1;border:1px solid var(--line);border-radius:10px;padding:11px;font:inherit}.guest-panel{border-left:1px solid var(--line);padding:17px;overflow:auto}.guest-panel h3{font-family:Georgia,serif;margin:0 0 15px}.detail-block{border-top:1px solid var(--line);padding:13px 0}.detail-row{display:flex;justify-content:space-between;font-size:10px;margin:7px 0}.detail-row span:first-child{color:var(--muted)}.action-stack{display:grid;gap:8px}.action-stack button{width:100%}.modal-backdrop{position:fixed;inset:0;background:rgba(12,30,26,.55);z-index:50;display:grid;place-items:center;padding:20px}.modal-backdrop[hidden]{display:none}.modal-card{background:white;border-radius:20px;width:min(520px,100%);padding:24px;box-shadow:0 30px 80px rgba(0,0,0,.25)}.modal-card h2{font-family:Georgia,serif;margin:0 0 8px}.modal-card p{color:var(--muted);font-size:12px}.form-grid{display:grid;grid-template-columns:1fr 1fr;gap:12px;margin:20px 0}.field{display:grid;gap:5px}.field.full{grid-column:1/-1}.field label{font-size:10px;font-weight:700}.field input,.field select{border:1px solid var(--line);border-radius:10px;padding:11px;font:inherit}.modal-actions{display:flex;justify-content:flex-end;gap:8px}.toast{position:fixed;right:24px;bottom:24px;background:var(--green);color:white;padding:12px 16px;border-radius:11px;box-shadow:var(--shadow);font-size:12px;transform:translateY(80px);opacity:0;transition:.25s;z-index:80}.toast.show{transform:none;opacity:1}.empty{padding:50px;text-align:center;color:var(--muted)}
.modal-kicker{margin-bottom:6px;color:var(--gold-ink);font-size:10px;font-weight:700;letter-spacing:.1em}.booking-modal{width:min(620px,100%)}.field small{color:var(--muted);font-size:10px;line-height:1.35}.date-control{display:grid;grid-template-columns:minmax(0,1fr) auto;min-width:0}.date-control input[type="date"]{min-width:0;border-radius:10px 0 0 10px;border-right:0;cursor:pointer}.date-picker-button{display:flex;align-items:center;gap:5px;min-height:44px;border:1px solid var(--line);border-radius:0 10px 10px 0;background:#f2f6f3;color:var(--green);padding:0 10px;font:700 10px/1 inherit;cursor:pointer;white-space:nowrap;transition:background .14s,border-color .14s}.date-picker-button:hover,.date-picker-button:focus-visible{background:var(--mint);border-color:#9bb8ae}.date-picker-button span:first-child{font-size:15px}.booking-review{display:grid;grid-template-columns:1fr 1fr;gap:9px;margin:18px 0}.booking-review>div{display:grid;gap:3px;padding:12px;border:1px solid var(--line);border-radius:11px;background:#fafcfb}.booking-review span{color:var(--muted);font-size:9px;font-weight:700;letter-spacing:.05em;text-transform:uppercase}.booking-review strong{font-size:12px}.booking-review small{color:var(--muted);font-size:10px;line-height:1.35}.booking-review .booking-total{background:var(--green);border-color:var(--green);color:#fff}.booking-review .booking-total span,.booking-review .booking-total small{color:#d6e8e1}.booking-review .booking-total strong{font-family:Georgia,serif;font-size:18px}.booking-review .booking-deposit{background:var(--gold-bg);border-color:#ead8ba}
.modal-topline{display:flex;align-items:flex-start;justify-content:space-between;gap:12px}.modal-close-x{font-size:22px;line-height:1;color:var(--muted);min-width:36px;min-height:36px}.modal-close-x:hover{color:var(--ink);background:#eef3f1}.guest-profile-card .attention{width:100%;background:#fff;text-align:left;color:inherit}.guest-profile-card .detail-block>strong{display:block;margin-bottom:10px;font-size:12px}
.access-banner{display:flex;align-items:center;gap:15px;padding:16px 18px;margin-bottom:18px;background:var(--gold-bg);border:1px solid #ead8ba;border-radius:16px}.access-banner>span{font-size:21px}.access-banner div{flex:1}.access-banner strong{display:block;font-size:13px}.access-banner p{margin:3px 0 0;color:#806742;font-size:11px}.role-preview{border:1px solid #d9c39e;background:#fffaf1;border-radius:10px;padding:9px 11px;color:var(--ink);font:inherit;font-size:11px}.team-layout{display:grid;grid-template-columns:300px minmax(0,1fr);gap:18px;align-items:start}.team-list{padding:0;overflow:hidden}.team-list-head{padding:18px;border-bottom:1px solid var(--line)}.team-list-head h2,.permission-head h2{font-family:Georgia,serif;font-size:18px;margin:0}.team-list-head p,.permission-head p{font-size:10px;color:var(--muted);margin:4px 0 0}.member{display:flex;align-items:center;gap:10px;width:100%;padding:13px 16px;border:0;border-bottom:1px solid var(--line);background:#fff;text-align:left;cursor:pointer;color:var(--ink)}.member:last-child{border-bottom:0}.member:hover,.member.active{background:#f0f6f3}.member.active{box-shadow:inset 3px 0 var(--green)}.member .avatar{background:var(--mint);color:var(--green)}.member-copy{min-width:0;flex:1}.member-copy strong{display:block;font-size:12px;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}.member-copy small{display:block;color:var(--muted);font-size:9px;margin-top:3px}.member-state{width:8px;height:8px;border-radius:50%;background:#65b58b}.member-state.invited{background:var(--amber)}.permission-card{padding:0;overflow:hidden}.permission-head{display:flex;align-items:center;gap:14px;padding:18px 20px;border-bottom:1px solid var(--line)}.permission-head .avatar{width:42px;height:42px}.permission-head>div{flex:1}.role-select{border:1px solid var(--line);border-radius:10px;padding:9px 11px;background:white;font:inherit;font-size:11px}.permission-summary{display:grid;grid-template-columns:repeat(3,1fr);gap:1px;background:var(--line);border-bottom:1px solid var(--line)}.permission-stat{background:#fafbf9;padding:12px 17px}.permission-stat strong{font-family:Georgia,serif;font-size:18px;display:block}.permission-stat small{font-size:9px;color:var(--muted)}.permission-groups{padding:5px 20px 18px}.permission-group{padding:16px 0 5px;border-bottom:1px solid var(--line)}.permission-group:last-child{border-bottom:0}.permission-group h3{font-size:10px;letter-spacing:.1em;color:var(--gold-ink);margin:0 0 5px}.permission-row{display:flex;align-items:center;gap:15px;padding:10px 0}.permission-copy{flex:1}.permission-copy strong{display:block;font-size:11px}.permission-copy small{display:block;color:var(--muted);font-size:9px;margin-top:3px;line-height:1.4}.toggle{position:relative;width:39px;height:22px;flex:none}.toggle input{position:absolute;opacity:0;pointer-events:none}.toggle span{position:absolute;inset:0;border-radius:30px;background:#cbd2cf;cursor:pointer;transition:.18s}.toggle span:after{content:'';position:absolute;width:16px;height:16px;left:3px;top:3px;border-radius:50%;background:white;box-shadow:0 1px 3px rgba(0,0,0,.25);transition:.18s}.toggle input:checked+span{background:var(--green)}.toggle input:checked+span:after{transform:translateX(17px)}.toggle input:disabled+span{opacity:.45;cursor:not-allowed}.locked{font-size:9px;color:var(--muted);background:#eef1ef;border-radius:20px;padding:4px 7px}.permission-actions{position:sticky;bottom:0;display:flex;align-items:center;justify-content:space-between;gap:10px;padding:13px 20px;background:rgba(255,255,255,.96);border-top:1px solid var(--line);backdrop-filter:blur(8px)}.permission-actions p{margin:0;font-size:10px;color:var(--muted)}.permission-actions div{display:flex;gap:8px}.danger-zone{margin-top:18px;border-color:#efcccc}.danger-zone .card-head{margin:0}.danger-zone p{max-width:650px}.danger-zone button{color:var(--red-ink);border-color:#e9bcbc}.unsaved-dot{display:inline-block;width:7px;height:7px;background:var(--amber);border-radius:50%;margin-right:5px}
@media(max-width:1100px){.metrics{grid-template-columns:repeat(3,1fr)}.inbox{grid-template-columns:250px 1fr}.guest-panel{display:none}.span-8,.span-7{grid-column:span 12}.span-4,.span-5{grid-column:span 6}.team-layout{grid-template-columns:250px minmax(0,1fr)}}
@media(max-width:760px){.app-shell{display:block}.sidebar{position:fixed;left:-270px;width:248px;transition:.25s}.sidebar.open{left:0;box-shadow:20px 0 60px rgba(0,0,0,.3)}.menu{display:block}.topbar{padding:18px 16px}.topbar h1{font-size:21px}.eyebrow{font-size:8px}.top-actions .icon-button,.top-actions .primary{display:none}.content{padding:0 14px 70px}.metrics{grid-template-columns:1fr 1fr}.dashboard-grid{grid-template-columns:1fr}.notice{align-items:flex-start}.notice button{display:none}.span-4,.span-5{grid-column:span 12}.card{padding:15px}.inbox{display:block;height:calc(100vh - 90px)}.conversation-list{height:100%}.chat{display:none}.inbox.chat-open .conversation-list{display:none}.inbox.chat-open .chat{display:flex;height:100%}.table-wrap{overflow:auto}.form-grid{grid-template-columns:1fr}.field.full{grid-column:auto}.access-banner{align-items:flex-start;flex-wrap:wrap}.role-preview{width:100%}.team-layout{grid-template-columns:1fr}.permission-card,.team-list{padding:0}.permission-head{flex-wrap:wrap}.permission-head .role-select{width:100%}.permission-summary{grid-template-columns:1fr 1fr 1fr}.permission-groups{padding:4px 15px 15px}.permission-actions{padding:12px 15px}.permission-actions p{display:none}}
.modal-card{max-height:calc(100vh - 40px);overflow:auto}.member-state.suspended{background:var(--red)}.team-modal{width:min(760px,100%)}.invite-permissions{display:grid;grid-template-columns:1fr 1fr;gap:10px;margin:16px 0}.invite-permission-group{border:1px solid var(--line);border-radius:13px;padding:12px}.invite-permission-group h3{font-size:9px;letter-spacing:.09em;color:var(--gold-ink);margin:0 0 6px}.invite-permission{display:flex;align-items:center;justify-content:space-between;gap:10px;padding:7px 0;font-size:10px}.invite-permission>.toggle{transform:scale(.88);transform-origin:right center}@media(max-width:760px){.invite-permissions{grid-template-columns:1fr}.team-modal{padding:18px}}

/* The sign-in screen. Here rather than injected by sign-in.js because the
   dashboard CSP is style-src 'self', and an inline <style> element is not
   self — it would have been dropped and the screen shown unstyled. */
.signin-veil{position:fixed;inset:0;z-index:9999;display:grid;place-items:center;
  background:#0d211d;color:#f3f7f5;font:16px/1.5 system-ui,-apple-system,"Segoe UI",sans-serif;padding:24px}
.signin-card{width:min(420px,100%);background:#12312a;border:1px solid #1f4a41;border-radius:14px;padding:28px}
.signin-card h1{margin:0 0 4px;font-size:20px}
.signin-help{margin:14px 0 0!important;font-size:12px!important;line-height:1.45;color:#a9c7bf}
/* The document shell is present for a fast authenticated boot, but it must
   never show sample operations data to a visitor behind the sign-in veil. */
body.auth-pending .app-shell,body.auth-pending .platform-shell{display:none}

/* Live notices and the property-scoped technical controls. */
.property-briefings .notice{align-items:flex-start}.property-briefings .notice small{margin-left:auto;color:#c8d9d4;font-size:11px;white-space:nowrap}.control-state{display:flex;align-items:center;justify-content:space-between;gap:18px;padding:16px 0;border-bottom:1px solid var(--line)}.control-state strong{display:block}.control-state p{margin:5px 0 0;color:var(--muted);font-size:13px;line-height:1.5}@media(max-width:760px){.control-state{align-items:flex-start;flex-direction:column}.control-state button{width:100%}.property-briefings .notice small{margin-left:0}}

/* The private platform console deliberately has a distinct visual shell: it
   reads as a portfolio tool, never as a hotel's own operations dashboard. */
.platform-body{background:#eef3f1}.platform-shell{max-width:1440px;margin:0 auto;padding:34px;min-height:100vh}.platform-header{display:flex;align-items:flex-start;justify-content:space-between;gap:24px;margin-bottom:25px}.platform-header h1{font-family:Georgia,serif;font-size:34px;margin:0 0 8px}.platform-header>div>p:last-child{max-width:680px;margin:0;color:var(--muted);font-size:14px;line-height:1.5}.platform-actions,.button-stack{display:flex;gap:8px;flex-wrap:wrap}.platform-metrics{display:grid;grid-template-columns:repeat(5,1fr);gap:13px;margin-bottom:20px}.platform-metric.red{border-color:#e8c1bf}.platform-grid{display:grid;grid-template-columns:minmax(0,1.7fr) minmax(300px,.7fr);gap:20px;align-items:start}.platform-card{padding:20px}.platform-property-list{display:grid;gap:12px}.platform-property{border:1px solid var(--line);border-radius:15px;padding:16px;background:#fbfcfb}.platform-property-head{display:flex;align-items:flex-start;justify-content:space-between;gap:15px}.platform-property h3{font-family:Georgia,serif;font-size:18px;margin:8px 0 4px}.platform-property p{margin:0;color:var(--muted);font-size:11px;line-height:1.45}.platform-switches{display:grid;grid-template-columns:1fr 1fr;gap:14px;margin-top:15px;padding-top:13px;border-top:1px solid var(--line)}.platform-switch{display:flex;align-items:center;justify-content:space-between;gap:12px}.platform-switch strong,.platform-switch small{display:block}.platform-switch strong{font-size:11px}.platform-switch small{font-size:10px;color:var(--muted);margin-top:3px}.platform-broadcast{position:sticky;top:22px}.platform-broadcast form{display:grid;gap:14px}.platform-broadcast label{display:grid;gap:6px;color:var(--muted);font-size:10px;font-weight:700;letter-spacing:.08em}.platform-broadcast select,.platform-broadcast textarea{width:100%;border:1px solid var(--line);border-radius:10px;background:#fff;padding:10px 11px;color:var(--ink);font:inherit;font-size:12px;letter-spacing:normal}.platform-broadcast textarea{resize:vertical;min-height:140px;line-height:1.45}.platform-form-note{margin:0;color:var(--muted);font-size:10px;line-height:1.45}.platform-loading,.platform-denied{min-height:70vh;display:grid;place-content:center;justify-items:center;text-align:center;gap:13px}.platform-loading{color:var(--muted);font-weight:600}.platform-loading .pulse{display:inline-block}.platform-denied{max-width:520px;margin:auto}.platform-denied h1{font-family:Georgia,serif;font-size:28px;margin:0}.platform-denied p:not(.eyebrow){margin:0;color:var(--muted);line-height:1.6}.platform-denied .secondary{text-decoration:none}
.pwa-dialog{width:min(430px,calc(100vw - 32px));border:0;border-radius:20px;padding:0;box-shadow:0 28px 80px rgba(8,33,28,.28);color:var(--ink);background:#fff}.pwa-dialog::backdrop{background:rgba(8,33,28,.5);backdrop-filter:blur(3px)}.pwa-dialog form{position:relative;display:grid;gap:12px;padding:26px}.pwa-dialog h2{font-family:Georgia,serif;font-size:26px;line-height:1.12;margin:0}.pwa-dialog p:not(.eyebrow){margin:0;color:var(--muted);line-height:1.55}.pwa-dialog .primary{width:100%;margin-top:5px}.pwa-close{position:absolute;right:12px;top:10px;width:36px;height:36px;border:0;border-radius:50%;background:transparent;color:var(--muted);font-size:26px;cursor:pointer}.pwa-close:hover{background:#eef3f1;color:var(--ink)}#pwa-install[hidden]{display:none!important}
@media(max-width:760px){.top-actions #pwa-install:not([hidden]),.top-actions #inbox-alert{display:grid}}
.chat-clear{flex:none;width:42px;height:42px;border:1px solid var(--line);border-radius:50%;background:#fff;color:var(--muted);font-size:23px;line-height:1;cursor:pointer}.chat-clear:hover{background:#f1f5f3;color:var(--ink)}
.conversation-list-head{display:flex;align-items:center;justify-content:space-between;gap:12px;padding:15px 16px 11px;background:#fff;border-bottom:1px solid var(--line)}.conversation-list-head strong{display:block;font-size:14px}.conversation-list-head small{display:block;margin-top:2px;color:var(--muted);font-size:11px}.inbox-list-actions{display:flex;align-items:center;gap:7px;flex:none}.inbox-unread-total,.conversation-new{display:grid;place-items:center;background:var(--green);color:#fff;border-radius:999px;font-size:10px;font-weight:700;line-height:1;flex:none}.inbox-unread-total{min-width:23px;height:23px;padding:0 7px}.inbox-archived-button{font-size:10px;padding:5px 7px;white-space:nowrap}.conversation.unread{background:#f3faf6}.conversation.unread .conversation-top strong{color:var(--green)}.conversation-new{min-width:34px;height:20px;padding:0 7px;text-transform:uppercase;letter-spacing:.04em}
@media(max-width:1050px){.platform-metrics{grid-template-columns:repeat(3,1fr)}.platform-grid{grid-template-columns:1fr}.platform-broadcast{position:static}}@media(max-width:700px){.platform-shell{padding:22px 15px}.platform-header{display:grid}.platform-header h1{font-size:28px}.platform-actions{width:100%}.platform-actions button{flex:1}.platform-metrics{grid-template-columns:1fr 1fr}.platform-property-head{display:grid}.platform-property-head .button-stack button{flex:1}.platform-switches{grid-template-columns:1fr}.platform-broadcast{padding:17px}}
.platform-sessions{margin-top:20px;padding:20px}.platform-session-list{display:grid}.platform-session{display:grid;grid-template-columns:minmax(180px,1.2fr) minmax(130px,.8fr) minmax(150px,.9fr) minmax(160px,.9fr) auto;gap:15px;align-items:center;padding:14px 0;border-top:1px solid var(--line)}.platform-session:first-child{border-top:0}.platform-session>div{min-width:0}.platform-session strong,.platform-session small{display:block;overflow-wrap:anywhere}.platform-session strong{font-size:11px}.platform-session small{margin-top:4px;color:var(--muted);font-size:10px}.platform-session-main .status{display:inline-block;margin-bottom:7px}.platform-session.revoked{opacity:.58}.platform-session .danger{color:#9d312a;border-color:#e5bab6}.platform-session button[disabled]{cursor:default;opacity:.55}@media(max-width:900px){.platform-session{grid-template-columns:1fr 1fr}.platform-session button{width:100%}}@media(max-width:600px){.platform-sessions{padding:16px}.platform-session{grid-template-columns:1fr}.platform-session:not(:last-child){padding-bottom:18px}.platform-session button{min-height:44px}}
.signin-card p{margin:0 0 20px;color:#a9c7bf;font-size:14px}
.signin-card label{display:block;font-size:12px;letter-spacing:.08em;color:#a9c7bf;margin-bottom:6px}
.signin-card input{width:100%;padding:11px 12px;border-radius:9px;border:1px solid #2b6156;
  background:#0d211d;color:inherit;font:inherit}
.signin-card input:focus{outline:2px solid #4ea58f;outline-offset:1px}
.password-field{position:relative}
.password-field input{padding-right:68px}
.signin-card .password-toggle{position:absolute;right:7px;top:50%;transform:translateY(-50%);width:auto;margin:0;padding:5px 7px;background:transparent;color:#a9c7bf;border:0;font-size:13px;text-decoration:underline}
.signin-card .password-toggle:hover{color:#f3f7f5}
.signin-route-note{display:grid;gap:6px;margin:0 0 18px;padding:12px;border:1px solid #2b6156;border-radius:10px;background:#0d211d;color:#d8ebe5;font-size:12px;line-height:1.45}.signin-route-note strong,.signin-route-note span{display:block}.signin-route-note a{color:#8fe3c8;font-weight:700;text-decoration:underline;text-underline-offset:2px}
.signin-card button{margin-top:16px;width:100%;padding:11px 12px;border:0;border-radius:9px;
  background:#4ea58f;color:#06201a;font:600 15px/1 inherit;cursor:pointer}
.signin-card button[disabled]{opacity:.6;cursor:progress}
.signin-note{margin-top:16px;font-size:13px;min-height:1.4em}
.signin-note[data-tone="bad"]{color:#ffb4a2}
.signin-note[data-tone="good"]{color:#8fe3c8}


/* ── Phone and POS ─────────────────────────────────────────────────────────
   Written after testing the deployed dashboard at 390px and at 1024×768, which
   is the shape of the POS the front desk will actually use. Everything here
   overrides an earlier rule, so it has to come last. */

/* iOS inflates text in landscape unless told not to, which breaks any layout
   tuned at a fixed width. */
html{-webkit-text-size-adjust:100%;text-size-adjust:100%}

/* A phone's address bar makes 100vh taller than the visible page, so a
   100vh panel hides its own last row behind the browser chrome. dvh follows
   the visible area; the vh line stays first as the fallback. */
.sidebar{height:100vh;height:100dvh}
.app-shell{min-height:100vh;min-height:100dvh}
.main{min-height:100vh;min-height:100dvh}

@media(max-width:1100px){
  /* The guest panel was hidden outright below 1100px, which on a 1024-wide POS
     means the front desk cannot see who they are talking to — the single thing
     they need most while a guest is standing in front of them. Fold it under
     the conversation instead of deleting it. */
  .inbox{grid-template-columns:250px minmax(0,1fr)}
  .guest-panel{display:block;grid-column:1/-1;border-left:0;border-top:1px solid var(--line)}
}

@media(max-width:760px){
  /* THE PRIMARY ACTION IS NOT OPTIONAL. The old rule hid the search, the
     notifications and "+ New booking" on any screen under 760px. A walk-in
     arriving at the desk is precisely when someone reaches for New booking,
     and on a phone it was the one control that had been removed. The topbar
     wraps to a second row instead. */
  .topbar{flex-wrap:wrap;gap:10px;padding:14px 14px 12px}
  .topbar>div:not(.top-actions){flex:1;min-width:0}
  .top-actions{order:3;width:100%;display:flex;gap:8px}
  .top-actions .primary{display:grid;place-items:center}
  .top-actions .icon-button{width:46px;height:46px;flex:none}
  .top-actions .primary{flex:1;min-height:46px;padding:10px 14px}

  /* Same reasoning for the banner's call to action. */
  .notice{align-items:flex-start;flex-wrap:wrap}
  .notice button{display:block;margin:12px 0 0;width:100%;min-height:44px}

  /* Touch targets. A 37px-tall row is a miss as often as a hit with a thumb,
     and the front desk will be tapping these while holding a phone one-handed. */
  .nav-item{min-height:46px;padding:11px 12px}
  .icon-button,.menu,.secondary,.primary,.quiet{min-height:44px}
  .menu{display:grid;place-items:center;width:46px;height:46px;flex:none}
  .conversation{padding:16px 15px}
  .member{padding:15px 16px}
  .attention{padding:14px}

  /* A drawer with no way back is a trap: the sidebar covered the page and the
     only way out was the menu button underneath it. */
  .app-shell.drawer-open::after{content:'';position:fixed;inset:0;background:rgba(12,30,26,.5);z-index:9}
  .sidebar{padding-top:max(24px,env(safe-area-inset-top))}

  /* Notched phones: the last row of a page and a bottom-anchored toast both
     land under the home indicator without this. */
  .content{padding:0 14px calc(70px + env(safe-area-inset-bottom))}
  .toast{right:14px;left:14px;bottom:calc(16px + env(safe-area-inset-bottom));text-align:center}

  /* Wide content scrolls inside its own box rather than pushing the page
     sideways — a horizontally scrolling BODY is what forces the pinch-zoom
     the owner would otherwise have to do. */
  .table-wrap{overflow-x:auto;-webkit-overflow-scrolling:touch}
  table{min-width:520px}

  /* 100vh again, for the panels that set their own height. */
  .inbox{height:calc(100vh - 90px);height:calc(100dvh - 90px)}
  .modal-backdrop{padding:14px;align-items:end}
  .modal-card{max-height:calc(100vh - 28px);max-height:calc(100dvh - 28px)}
}

.refresh-status{display:block;min-height:15px;margin-top:4px;color:var(--muted);font-size:11px}
.refresh-status[data-tone="busy"]{color:var(--gold-ink)}
.refresh-status[data-tone="bad"]{color:var(--red-ink);font-weight:700}
.refresh-status[data-tone="good"]{color:var(--muted)}
.mobile-page-back{display:none}
.mobile-nav{display:none}
@media(max-width:760px){
  .mobile-page-back:not([hidden]){display:grid;place-items:center;width:44px;height:44px;min-height:44px;padding:0;border:1px solid var(--line);border-radius:12px;background:#fff;color:var(--green);font-size:24px;cursor:pointer}
  .mobile-page-back[hidden]{display:none}
  /* THE HEIGHT OF THE BAR, WRITTEN ONCE.
     48px of button + 6px above + 6px below + a 1px border = 61px, and every
     rule that has to keep clear of it derives from this rather than carrying
     its own guess. Three places were reserving three different amounts —
     60px here, 72px in the installed-app block, and nothing at all on the
     conversation list — which is how the bug below survived. */
  :root{--mobile-nav-h:61px}
  .mobile-nav{position:fixed;z-index:30;right:0;bottom:0;left:0;display:grid;grid-template-columns:repeat(5,1fr);gap:2px;padding:6px 8px calc(6px + env(safe-area-inset-bottom));border-top:1px solid var(--line);background:rgba(255,255,255,.96);backdrop-filter:blur(10px)}
  .mobile-nav-item{position:relative;display:grid;place-items:center;gap:2px;min-height:48px;border:0;border-radius:10px;background:transparent;color:var(--muted);font-size:10px;font-weight:600;cursor:pointer}
  .mobile-nav-item span{font-size:19px;line-height:1}
  .mobile-nav-item.active{background:var(--mint);color:var(--green)}
  .mobile-nav-item b{position:absolute;top:1px;right:calc(50% - 20px);min-width:17px;height:17px;padding:0 4px;border-radius:20px;background:var(--red);color:#fff;font-size:9px;line-height:17px;text-align:center}
  .app-shell.inbox-thread-open ~ .mobile-nav,.app-shell.inbox-thread-open .mobile-nav{display:none}
  .app-shell.inbox-page .content.inbox-content{padding-bottom:calc(var(--mobile-nav-h) + env(safe-area-inset-bottom))}

  /* ── THE LAST CONVERSATION WAS UNREACHABLE ────────────────────────────────
     Measured with fourteen guests on a 390×844 screen, scrolled as far down as
     the list would go: the final row's bottom edge sat at 843px and the
     navigation bar's top edge at 783px. Sixty pixels of the last conversation
     were underneath the bar, and no amount of scrolling brought them out —
     the owner simply could not open her oldest thread.

     The bar is position:fixed, so it is out of flow and covers whatever is
     beneath it. The list scrolls inside .inbox, which is exactly viewport
     high, so nothing in the layout knew the bar was there. The scroll
     container has to reserve the space itself.

     On the list only. With a thread open both the list and the bar are
     hidden, and the composer does its own safe-area reservation. */
  .app-shell.inbox-page .conversation-list{
    padding-bottom:calc(var(--mobile-nav-h) + env(safe-area-inset-bottom))}
  .app-shell.inbox-thread-open .content.inbox-content{padding-bottom:0}
}

/* Platform Operator OS — the portfolio console is intentionally its own
   information architecture. All visible controls below are semantic buttons,
   links, inputs or labels; nothing is styled to look tappable without a role. */
.platform-body{margin:0;background:#eef2f0;color:#102a24;overflow-x:hidden}
.platform-body.auth-pending #platform-root{visibility:hidden}
.platform-body #platform-root{min-height:100vh}
.platform-body button,.platform-body input,.platform-body select,.platform-body textarea{font:inherit}
.platform-shell{min-height:100vh;display:grid;grid-template-columns:224px minmax(0,1fr);background:#eef2f0}
.platform-rail{position:sticky;top:0;height:100vh;padding:24px 15px 18px;background:#0c251f;color:#fff;display:flex;flex-direction:column;z-index:20}
.platform-brand{display:flex;align-items:center;gap:11px;padding:0 9px 25px;color:#fff;text-decoration:none}
.platform-brand>span{width:38px;height:38px;border-radius:12px;display:grid;place-items:center;background:linear-gradient(145deg,#d2a24b,#f4d494);color:#102a24;font:800 13px/1 Inter,Arial,sans-serif;box-shadow:0 10px 24px #0004}
.platform-brand strong{font:700 17px/1.1 Inter,Arial,sans-serif;letter-spacing:-.02em}.platform-brand small{display:block;margin-top:4px;color:#9bb3ac;font:600 9px/1 Inter,Arial,sans-serif;letter-spacing:.16em;text-transform:uppercase}
.platform-rail nav{display:flex;flex-direction:column;gap:5px}.platform-nav-item{width:100%;min-height:45px;border:0;border-radius:11px;padding:0 11px;display:grid;grid-template-columns:25px 1fr auto;align-items:center;gap:8px;background:transparent;color:#b9c9c5;text-align:left;font:600 12px Inter,Arial,sans-serif;cursor:pointer;transition:.18s ease}
.platform-nav-item:hover,.platform-nav-item:focus-visible{background:#ffffff0e;color:#fff;transform:translateX(2px)}.platform-nav-item.active{background:#f7f5ec;color:#102a24;box-shadow:0 12px 30px #0003}.platform-nav-item b{min-width:21px;height:21px;padding:0 5px;border-radius:20px;display:grid;place-items:center;background:#b84d48;color:#fff;font-size:9px}.platform-nav-icon{font:400 18px/1 Georgia,serif}
.platform-rail-foot{margin-top:auto;padding:14px 11px;border:1px solid #ffffff14;border-radius:12px;display:flex;align-items:center;gap:10px;background:#ffffff08}.platform-rail-foot strong,.platform-rail-foot small{display:block;font-family:Inter,Arial,sans-serif}.platform-rail-foot strong{font-size:11px}.platform-rail-foot small{color:#91aaa3;font-size:9px;margin-top:3px}
.live-dot{display:inline-block;width:7px;height:7px;border-radius:50%;background:#3ad68c;box-shadow:0 0 0 4px #3ad68c1b,0 0 14px #3ad68c}.platform-stage{min-width:0}.platform-topbar{min-height:102px;padding:21px clamp(22px,3vw,45px);display:flex;align-items:center;justify-content:space-between;gap:20px;border-bottom:1px solid #d8e0dd;background:#f8faf9e8;backdrop-filter:blur(16px);position:sticky;top:0;z-index:15}
.platform-topbar h1{margin:3px 0 1px;font-size:25px;letter-spacing:-.025em}.platform-topbar p:not(.eyebrow){margin:0;color:#6a7b76;font-size:11px}.platform-top-actions{display:flex;align-items:center;gap:9px}.platform-command{width:min(340px,32vw);height:42px;padding:0 12px;border:1px solid #cfdad6;border-radius:11px;background:#fff;color:#6d7d78;display:flex;align-items:center;gap:9px;text-align:left;cursor:pointer;box-shadow:0 4px 12px #183b3110}.platform-command span{font-size:20px;color:#143f34}.platform-command kbd{margin-left:auto;padding:4px 7px;border:1px solid #d9e0dd;border-radius:6px;background:#f2f5f3;color:#61726d;font:600 9px Inter,Arial,sans-serif}.icon-control,.operator-avatar{width:42px;height:42px;border:1px solid #cfdad6;border-radius:11px;background:#fff;color:#143f34;display:grid;place-items:center;cursor:pointer}.icon-control:hover,.operator-avatar:hover{border-color:#779b90;box-shadow:0 5px 14px #183b3117}.operator-avatar{border:0;background:#d4a64f;color:#102a24;font:800 11px Inter,Arial,sans-serif}.platform-mobile-nav{display:none}
.platform-workspace{padding:28px clamp(22px,3vw,45px) 70px;max-width:1600px;margin:0 auto}.platform-hero{min-height:166px;padding:30px 32px;border-radius:20px;display:flex;align-items:flex-end;justify-content:space-between;gap:22px;color:#fff;background:radial-gradient(circle at 80% 10%,#2c6b5b 0,transparent 35%),linear-gradient(135deg,#102c25,#16483b);box-shadow:0 16px 42px #10382c21;overflow:hidden;position:relative}.platform-hero:after{content:"";position:absolute;right:5%;top:-80px;width:250px;height:250px;border:1px solid #ffffff15;border-radius:50%;box-shadow:0 0 0 45px #ffffff06,0 0 0 90px #ffffff04}.platform-hero>div,.platform-hero>button{position:relative;z-index:1}.platform-hero .status{background:#3ad68c18;color:#8bf0bc;border:1px solid #61dfa24a}.platform-hero h2{margin:13px 0 7px;font-size:clamp(25px,3vw,38px);line-height:1.03;letter-spacing:-.035em}.platform-hero p{margin:0;color:#bad0c9;font-size:12px}.platform-hero .primary{background:#e2b864;color:#102a24;border-color:#e2b864}
.platform-kpis{display:grid;grid-template-columns:repeat(5,1fr);gap:11px;margin:16px 0}.platform-kpi{min-height:126px;padding:17px;border:1px solid #dbe2df;border-radius:15px;background:#fff;color:#102a24;text-align:left;display:flex;flex-direction:column;cursor:pointer;transition:.18s ease;box-shadow:0 5px 18px #0d30270a}.platform-kpi:hover,.platform-kpi:focus-visible{transform:translateY(-2px);border-color:#9bb6ae;box-shadow:0 12px 28px #0d302712}.platform-kpi>span{display:flex;justify-content:space-between;color:#6b7c77;font:700 9px Inter,Arial,sans-serif;letter-spacing:.09em;text-transform:uppercase}.platform-kpi>span i{font-size:12px}.platform-kpi strong{font:700 31px/1 Georgia,serif;margin:auto 0 5px}.platform-kpi small{color:#7b8b86;font-size:9px}.platform-kpi.danger{border-top:3px solid #c24f49}.platform-kpi.warn{border-top:3px solid #d09a37}
.platform-dashboard-grid{display:grid;grid-template-columns:repeat(12,1fr);gap:16px}.platform-dashboard-grid>.span-8{grid-column:span 8}.platform-dashboard-grid>.span-7{grid-column:span 7}.platform-dashboard-grid>.span-5{grid-column:span 5}.platform-dashboard-grid>.span-4{grid-column:span 4}.platform-panel{background:#fff;border:1px solid #dce3e0;border-radius:17px;padding:20px;box-shadow:0 5px 22px #0e352b0a}.platform-panel-head{display:flex;align-items:flex-start;justify-content:space-between;gap:15px;margin-bottom:15px}.platform-panel-head h2{font-size:19px;margin:3px 0 3px}.platform-panel-head p:not(.eyebrow){margin:0;color:#73827e;font-size:10px}.platform-panel-head .quiet{border:0;background:transparent;color:#174b3d;font-weight:700;cursor:pointer}
.fleet-table-head,.fleet-row{display:grid;grid-template-columns:minmax(180px,1.5fr) .8fr .8fr .8fr .9fr 20px;align-items:center;gap:10px}.fleet-table-head{padding:8px 10px;color:#84918d;font:700 8px Inter,Arial,sans-serif;letter-spacing:.08em;text-transform:uppercase;border-bottom:1px solid #e7ecea}.fleet-row{width:100%;min-height:56px;padding:8px 10px;border:0;border-bottom:1px solid #edf0ef;background:transparent;color:#28413a;text-align:left;font-size:10px;cursor:pointer}.fleet-row:hover{background:#f4f8f6;border-radius:10px}.fleet-row>span{display:flex;align-items:center;gap:8px}.fleet-row>span:first-child b{font-size:11px;color:#102a24}.property-glyph,.operator-mini{font-style:normal;width:30px;height:30px;border-radius:9px;background:#e1eee9;color:#164c3e;display:grid;place-items:center;font:bold 12px Georgia,serif}.property-glyph.large{width:42px;height:42px;border-radius:12px;font-size:16px}.signal{width:7px;height:7px;display:inline-block;border-radius:50%;background:#9cacaa}.signal.green{background:#28ac70;box-shadow:0 0 0 3px #28ac7016}.signal.amber{background:#d3972b}.signal.red{background:#c54f49}.signal.muted{background:#8e9b97}
.attention-stack{display:flex;flex-direction:column;gap:6px}.attention-command{width:100%;min-height:56px;padding:8px;border:0;border-radius:11px;background:#f7f9f8;color:#102a24;display:grid;grid-template-columns:31px 1fr 14px;align-items:center;gap:9px;text-align:left;cursor:pointer}.attention-command:hover{background:#eef5f2}.attention-command strong,.attention-command small{display:block}.attention-command strong{font-size:10px}.attention-command small{color:#788783;font-size:9px;margin-top:3px}.attention-mark,.incident-icon{width:27px;height:27px;border-radius:8px;display:grid;place-items:center;font-weight:800}.attention-mark.red,.incident-icon.red{background:#f8e3e1;color:#a9433e}.attention-mark.amber,.incident-icon.amber{background:#faefd8;color:#a46d15}
.workspace-tools{display:flex;align-items:center;gap:12px;margin-bottom:17px}.workspace-search{flex:1;max-width:420px;height:43px;border:1px solid #d3ddda;border-radius:11px;background:#fff;display:flex;align-items:center;padding-left:12px}.workspace-search input{flex:1;min-width:0;border:0;outline:0;padding:0 10px;background:transparent}.workspace-search button{height:100%;border:0;border-left:1px solid #e2e7e5;background:transparent;padding:0 14px;color:#174b3d;font-weight:700;cursor:pointer}.filter-chips{display:flex;gap:6px}.filter-chips button{height:36px;border:1px solid #d4ddda;border-radius:20px;background:#fff;padding:0 13px;color:#60716c;font-size:10px;cursor:pointer}.filter-chips button.active{background:#143f34;color:#fff;border-color:#143f34}.property-grid,.integration-grid{display:grid;grid-template-columns:repeat(3,minmax(0,1fr));gap:15px}.fleet-card,.integration-card{border:1px solid #dce3e0;border-radius:16px;background:#fff;padding:18px;box-shadow:0 5px 22px #0e352b0a}.fleet-card-open{width:100%;padding:0;border:0;background:transparent;text-align:left;color:#102a24;cursor:pointer}.fleet-card-open>div{display:flex;justify-content:space-between;align-items:center}.fleet-card-open h2{font-size:18px;margin:14px 0 5px}.fleet-card-open p{margin:0;color:#70807b;font-size:10px}.fleet-card-open p .signal{margin-right:6px}.fleet-card-metrics{display:grid;grid-template-columns:repeat(3,1fr);gap:5px;margin:16px 0;padding:12px 0;border-block:1px solid #e9edeb}.fleet-card-metrics span{color:#788783;font-size:8px}.fleet-card-metrics b{display:block;color:#163c32;font:700 15px Georgia,serif;margin-bottom:2px}.fleet-card-actions{display:grid;grid-template-columns:1fr auto;gap:7px}.fleet-card-actions button{min-height:37px}.compact-switches{margin-top:11px}.compact-switches label{display:flex;align-items:center;justify-content:space-between;padding:7px 1px;color:#536762;font-size:9px}.compact-switches input,.drawer-switch input{width:34px;height:19px;accent-color:#174b3d;cursor:pointer}
.platform-section-intro{min-height:102px;margin-bottom:17px;padding:0 2px;display:flex;align-items:flex-end;justify-content:space-between;gap:20px}.platform-section-intro h2{font-size:25px;margin:10px 0 4px}.platform-section-intro p{margin:0;color:#6e7f7a;font-size:11px}.incident-list{max-height:590px;overflow:auto}.incident-row{width:100%;padding:12px 8px;border:0;border-bottom:1px solid #e7ecea;background:transparent;color:#102a24;display:grid;grid-template-columns:31px 1fr auto;align-items:center;gap:10px;text-align:left;cursor:pointer}.incident-row:hover{background:#f6f9f8;border-radius:10px}.incident-row strong,.incident-row small{display:block}.incident-row strong{font-size:10px}.incident-row small{color:#768681;font-size:9px;line-height:1.4;margin-top:3px}.incident-row>span:last-child{color:#28594d;font-size:9px;font-weight:700}
.integration-card>button{width:100%;display:grid;grid-template-columns:31px 1fr auto;gap:10px;align-items:center;padding:0 0 14px;border:0;border-bottom:1px solid #e4e9e7;background:transparent;color:#102a24;text-align:left;cursor:pointer}.integration-card>button strong,.integration-card>button small{display:block}.integration-card>button small{color:#798783;font-size:9px;margin-top:3px}.integration-line{padding:12px 1px 0}.integration-line>span{display:flex;align-items:center;gap:8px}.integration-line strong{font-size:10px}.integration-line small{display:block;margin:4px 0 0 15px;color:#71817c;font-size:9px}
.session-table-head,.session-row{display:grid;grid-template-columns:1.4fr 1fr 1fr 1fr 100px;gap:14px;align-items:center}.session-table-head{padding:5px 9px 12px;color:#85928e;font:700 8px Inter,Arial,sans-serif;letter-spacing:.08em;text-transform:uppercase;border-bottom:1px solid #e2e8e5}.session-row{min-height:72px;padding:10px 9px;border-bottom:1px solid #e8ecea}.session-row.revoked{opacity:.5}.session-row>span:first-child{display:grid;grid-template-columns:34px 1fr}.session-row>span:first-child .operator-mini{grid-row:span 2}.session-row b,.session-row small{display:block}.session-row b{font-size:10px}.session-row small{color:#7c8986;font-size:8px;margin-top:3px}.session-row .secondary{min-height:35px;font-size:9px}.activity-stream article{min-height:55px;padding:8px 4px;display:grid;grid-template-columns:31px 1fr auto;gap:10px;align-items:center;border-bottom:1px solid #e8ecea}.activity-stream article>i{width:29px;height:29px;border-radius:8px;background:#e3efeb;display:grid;place-items:center;color:#174b3d;font:700 10px Inter,Arial,sans-serif}.activity-stream strong,.activity-stream small{display:block}.activity-stream strong{font-size:10px}.activity-stream small,.activity-stream time{color:#788783;font-size:8px;margin-top:3px}.broadcast-form{display:grid;gap:14px}.broadcast-form label{display:grid;gap:6px;color:#60716c;font:700 8px Inter,Arial,sans-serif;letter-spacing:.09em}.broadcast-form select,.broadcast-form textarea{width:100%;border:1px solid #d3ddda;border-radius:10px;background:#fff;padding:11px;color:#102a24}.broadcast-form textarea{min-height:150px;resize:vertical}.broadcast-form p{margin:0;color:#7a8985;font-size:9px}.broadcast-history{display:grid;gap:9px;max-height:580px;overflow:auto}.broadcast-history article{padding:13px;border:1px solid #e1e7e4;border-radius:11px;background:#fafbfa}.broadcast-history article>div{display:flex;align-items:center;justify-content:space-between}.broadcast-history time,.broadcast-history small{color:#7a8985;font-size:8px}.broadcast-history strong{display:block;font-size:10px;margin-top:9px}.broadcast-history p{font-size:10px;line-height:1.45;color:#455d57}
.platform-empty{min-height:130px;padding:24px;display:flex;flex-direction:column;align-items:center;justify-content:center;text-align:center;color:#72827d}.platform-empty>span{width:35px;height:35px;margin-bottom:9px;border-radius:50%;display:grid;place-items:center;background:#e5f1ed;color:#19845b}.platform-empty strong{color:#203d35;font-size:11px}.platform-empty p{margin:5px 0 0;font-size:9px}.platform-empty.wide{grid-column:1/-1}.platform-loading,.platform-denied{min-height:100vh;display:flex;flex-direction:column;align-items:center;justify-content:center;gap:10px;text-align:center}.platform-loading small{color:#71817c}.platform-denied{padding:30px}.platform-denied h1{font-size:35px}.platform-denied p{max-width:540px;color:#6c7d78}
.drawer-scrim,.command-scrim{position:fixed;inset:0;background:#0718149c;backdrop-filter:blur(5px);z-index:80}.property-drawer{position:fixed;right:0;top:0;bottom:0;width:min(500px,94vw);padding:25px;background:#f8faf9;z-index:81;overflow:auto;box-shadow:-25px 0 65px #061b1652;animation:drawerIn .22s ease}.property-drawer header{display:flex;justify-content:space-between;align-items:flex-start;gap:15px;padding-bottom:20px;border-bottom:1px solid #dce4e1}.property-drawer h2{font-size:26px;margin:10px 0 5px}.property-drawer header p{margin:0;color:#687a75;font-size:10px}.property-drawer header p .signal{margin-right:6px}.property-drawer>section{padding:20px 0;border-bottom:1px solid #dfe6e3}.drawer-stats{display:grid;grid-template-columns:repeat(2,1fr);gap:8px}.drawer-stats span{padding:13px;border-radius:11px;background:#fff;border:1px solid #e0e6e4;color:#74837f;font-size:8px}.drawer-stats b{display:block;color:#153f34;font:700 17px Georgia,serif;margin-bottom:4px}.drawer-actions{display:grid;grid-template-columns:repeat(2,1fr);gap:7px;margin-top:10px}.drawer-actions button{min-height:58px;padding:9px;border:1px solid #dce4e1;border-radius:11px;background:#fff;color:#102a24;display:grid;grid-template-columns:25px 1fr auto;gap:7px;align-items:center;text-align:left;cursor:pointer}.drawer-actions button:hover{border-color:#8aaba1;background:#f4f8f6}.drawer-actions button>i{font-style:normal;color:#1f5b4b}.drawer-actions strong,.drawer-actions small{display:block}.drawer-actions strong{font-size:9px}.drawer-actions small{color:#7b8985;font-size:7px;margin-top:3px}.drawer-control-head{display:flex;justify-content:space-between}.drawer-control-head>span{font-size:8px;color:#80908b}.drawer-switch{display:flex;align-items:center;justify-content:space-between;padding:13px 0}.drawer-switch strong,.drawer-switch small{display:block}.drawer-switch strong{font-size:10px}.drawer-switch small{color:#7a8985;font-size:8px;margin-top:3px}.wide-button{width:100%;min-height:42px;margin-top:10px}@keyframes drawerIn{from{transform:translateX(100%)}to{transform:none}}
.command-scrim{display:grid;place-items:start center;padding-top:min(13vh,110px);z-index:90}.command-palette{width:min(690px,92vw);max-height:78vh;border:1px solid #ffffff24;border-radius:17px;background:#f8faf9;box-shadow:0 30px 90px #0008;overflow:hidden}.command-palette form{height:62px;padding:0 18px;display:flex;align-items:center;gap:11px;border-bottom:1px solid #dce4e1;background:#fff}.command-palette form>span{font-size:24px}.command-palette input{flex:1;border:0;outline:0;background:transparent;font-size:15px}.command-palette kbd{padding:5px 8px;border:1px solid #d3ddda;border-radius:6px;color:#72817d;font:700 8px Inter,Arial,sans-serif}.command-results{max-height:calc(78vh - 62px);padding:17px;overflow:auto}.command-grid{display:grid;grid-template-columns:repeat(3,1fr);gap:6px;margin:8px 0 19px}.command-grid button{padding:10px;border:1px solid #dfe6e3;border-radius:9px;background:#fff;color:#173d33;text-align:left;cursor:pointer}.command-grid span,.command-grid small{display:block}.command-grid span{font-size:9px;font-weight:700}.command-grid small{color:#7a8985;font-size:7px;margin-top:3px}.command-property{width:100%;min-height:52px;padding:7px;border:0;border-bottom:1px solid #e3e8e6;background:transparent;color:#102a24;display:grid;grid-template-columns:31px 1fr auto;gap:10px;align-items:center;text-align:left;cursor:pointer}.command-property:hover{background:#eef4f1;border-radius:9px}.command-property strong,.command-property small{display:block}.command-property strong{font-size:10px}.command-property small{font-size:8px;color:#778681;margin-top:3px}.command-none{color:#798984;font-size:10px}
@media(max-width:1180px){.platform-kpis{grid-template-columns:repeat(3,1fr)}.platform-kpis .platform-kpi:nth-child(n+4){grid-column:span 1}.property-grid,.integration-grid{grid-template-columns:repeat(2,1fr)}.platform-dashboard-grid>.span-8,.platform-dashboard-grid>.span-4{grid-column:span 12}.fleet-table-head,.fleet-row{grid-template-columns:minmax(170px,1.4fr) .8fr .8fr .8fr 20px}.fleet-table-head>span:nth-child(5),.fleet-row>span:nth-child(5){display:none}}
@media(max-width:860px){.platform-shell{display:block}.platform-rail{display:none}.platform-topbar{min-height:86px;padding:16px 18px;position:relative}.platform-topbar h1{font-size:20px}.platform-topbar>div:first-child>p:not(.eyebrow){display:none}.platform-command{width:42px;font-size:0}.platform-command kbd{display:none}.platform-mobile-nav{display:flex;gap:5px;padding:8px 14px;border-bottom:1px solid #dbe2df;background:#fff;overflow:auto;position:sticky;top:0;z-index:14}.platform-mobile-nav button{height:34px;white-space:nowrap;border:0;border-radius:18px;padding:0 12px;background:transparent;color:#63756f;font-size:9px;font-weight:700}.platform-mobile-nav button.active{background:#143f34;color:#fff}.platform-workspace{padding:18px 14px 55px}.platform-hero{padding:22px;min-height:185px;align-items:flex-start;flex-direction:column}.platform-hero h2{font-size:27px}.platform-kpis{grid-template-columns:repeat(2,1fr)}.platform-kpi{min-height:112px}.platform-dashboard-grid>.span-7,.platform-dashboard-grid>.span-5{grid-column:span 12}.workspace-tools{align-items:stretch;flex-wrap:wrap}.workspace-search{max-width:none;width:100%}.filter-chips{overflow:auto}.property-grid,.integration-grid{grid-template-columns:1fr}.session-table-head{display:none}.session-row{grid-template-columns:1fr 1fr}.session-row>button{grid-column:1/-1}.platform-section-intro{align-items:flex-start;flex-direction:column}.platform-section-intro h2{font-size:22px}.command-grid{grid-template-columns:repeat(2,1fr)}}
@media(max-width:580px){.platform-topbar .eyebrow,.platform-topbar>div:first-child>p{display:none}.platform-topbar h1{font-size:18px}.platform-command{display:none}.platform-kpis{grid-template-columns:1fr 1fr;gap:8px}.platform-kpi{padding:14px;min-height:102px}.platform-kpi strong{font-size:25px}.platform-kpi:first-child{grid-column:1/-1}.fleet-table-head{display:none}.fleet-row{grid-template-columns:1fr auto;gap:4px;padding:10px}.fleet-row>span:nth-child(n+2):not(:last-child){display:none}.drawer-actions{grid-template-columns:1fr}.drawer-stats{grid-template-columns:1fr 1fr}.session-row{grid-template-columns:1fr}.session-row>span:first-child{grid-template-columns:34px 1fr}.command-grid{grid-template-columns:1fr 1fr}.platform-hero .primary{width:100%}}

/* Very narrow phones. Two metric columns become unreadable below this. */
@media(max-width:380px){
  .metrics{grid-template-columns:1fr}
  .permission-summary{grid-template-columns:1fr}
}

.metric-value.compact{font-size:21px}

/* The bookings table pushed the whole PAGE sideways at 390px — 461px of content
   in a 390px window — which is the pinch-and-zoom the owner should never have
   to do. Two causes, both needed: a `1fr` grid track is minmax(auto,1fr), so it
   grows to fit its widest child rather than clipping it, and a grid item with
   no min-width refuses to shrink below its content. The table still scrolls
   inside its own box; it just no longer drags the page with it. */
.page-grid{grid-template-columns:repeat(12,minmax(0,1fr))}
.card,.metric,.main,.content{min-width:0}
.table-wrap{max-width:100%}
@media(max-width:760px){
  .toolbar{flex-wrap:wrap}
  .toolbar .searchbox{flex:1 1 100%}
  .toolbar .filter{flex:1}
  .toolbar .primary{flex:1 1 100%}
}

/* The two sidebar controls were 18px tall — reliable with a mouse, a coin toss
   with a thumb or a POS stylus. They are the property switcher and the account
   menu, so a mis-tap lands on the nav item behind them. */
.property-switch button,.profile button{min-width:40px;min-height:40px;display:grid;place-items:center;border-radius:10px;cursor:pointer}
.property-switch button:hover,.profile button:hover{background:rgba(255,255,255,.12)}

/* Google sign-in on the veil. Styled here rather than inline because
   style-src 'self' drops a style attribute, and that policy is what keeps a
   third-party script away from the session token. */
.signin-or{display:flex;align-items:center;gap:10px;margin:16px 0 4px;color:#8fb0a6;font-size:12px}
.signin-or::before,.signin-or::after{content:'';flex:1;height:1px;background:#1f4a41}
.signin-card .signin-google{margin-top:12px;width:100%;min-height:46px;padding:11px 12px;border-radius:9px;
  background:#0d211d;color:#f3f7f5;border:1px solid #2b6156;font:600 15px/1 inherit;cursor:pointer}
.signin-card .signin-google:hover{background:#13302a}
.signin-card .signin-google[disabled]{opacity:.6;cursor:progress}

.signin-or[hidden],.signin-google[hidden]{display:none}

/* Anything that responds to a tap says so. Without this the metric cards and
   the arrival rows look identical to the ones that do nothing, which is the
   reason people stop trying to tap things on a dashboard. */
.attention{cursor:default}.attention.tappable{cursor:pointer}.button-stack{display:flex;align-items:center;gap:8px;flex-wrap:wrap}.tappable{cursor:pointer;transition:transform .12s,box-shadow .12s,border-color .12s}
.tappable:hover{border-color:#b8cac4;box-shadow:0 4px 16px rgba(31,52,45,.08)}
.tappable:active{transform:scale(.985)}
.tappable:focus-visible{outline:2px solid var(--green);outline-offset:2px}
.missing{color:var(--amber);font-style:normal;font-weight:600}

/* The availability board keeps every date visible in a familiar month grid.
   Its navigator is a small command palette rather than a browser-specific
   month field, so a receptionist can jump to any future month quickly. */
.calendar-actions{display:grid;grid-template-columns:1fr auto;align-items:center;gap:10px;width:min(100%,820px)}
.calendar-actions-main,.calendar-actions-extra{display:flex;align-items:center;gap:7px;flex-wrap:wrap}
.calendar-actions-extra{justify-content:flex-end}
.calendar-month-picker{position:relative}
.calendar-month-trigger{display:flex;align-items:center;gap:7px;min-height:42px;padding:7px 10px;border:1px solid var(--line);border-radius:11px;background:#fff;color:var(--ink);font:inherit;text-align:left;cursor:pointer;box-shadow:0 1px 1px rgba(31,52,45,.03);transition:border-color .16s,box-shadow .16s,transform .16s}
.calendar-month-trigger:hover,.calendar-month-trigger[aria-expanded="true"]{border-color:#9bb8ae;box-shadow:0 5px 16px rgba(31,52,45,.09)}
.calendar-month-trigger:active{transform:translateY(1px)}
.calendar-month-trigger span{font-size:9px;font-weight:700;letter-spacing:.06em;text-transform:uppercase;color:var(--muted)}
.calendar-month-trigger strong{font-family:Georgia,serif;font-size:14px;font-weight:600;white-space:nowrap}
.calendar-month-trigger b{font-size:16px;color:var(--green);line-height:1;transition:transform .16s}
.calendar-month-trigger[aria-expanded="true"] b{transform:rotate(180deg)}
.calendar-month-popover{position:absolute;top:calc(100% + 8px);left:50%;z-index:12;width:292px;padding:10px;border:1px solid #d7e1dc;border-radius:14px;background:#fff;box-shadow:0 18px 42px rgba(31,52,45,.18);transform:translateX(-50%);animation:calendar-picker-in .16s ease-out}
.calendar-year-row{display:grid;grid-template-columns:34px 1fr 34px;align-items:center;gap:4px;margin-bottom:9px}
.calendar-year-row select{width:100%;min-height:34px;border:0;border-radius:8px;background:#f2f6f3;color:var(--ink);font:600 13px Georgia,serif;text-align:center;cursor:pointer}
.calendar-year-step{display:grid;place-items:center;min-width:34px;min-height:34px;padding:0!important;border-radius:8px!important;font-size:16px}
.calendar-month-grid{display:grid;grid-template-columns:repeat(3,1fr);gap:5px}
.calendar-month-option{min-height:38px;border:1px solid transparent;border-radius:8px;background:#f8faf8;color:var(--ink);font:600 11px/1 inherit;cursor:pointer;transition:background .14s,border-color .14s,transform .14s}
.calendar-month-option:hover{border-color:#b8d0c6;background:#eef6f1}.calendar-month-option:active{transform:scale(.97)}
.calendar-month-option.active{border-color:var(--green);background:var(--green);color:#fff;box-shadow:0 3px 8px rgba(23,61,53,.2)}
.calendar-month-option.current:not(.active){border-color:#c9dcd4;background:#eef6f1;color:var(--green)}
@keyframes calendar-picker-in{from{opacity:0;transform:translate(-50%,-4px)}to{opacity:1;transform:translate(-50%,0)}}
.calendar-board{border:1px solid var(--line);border-radius:13px;overflow:hidden;background:#fff}
.calendar-weekdays,.calendar-days{display:grid;grid-template-columns:repeat(7,minmax(0,1fr))}
.calendar-weekdays{background:#fafbf9;border-bottom:1px solid var(--line)}
.calendar-weekdays span{padding:8px 5px;text-align:center;color:var(--muted);font-size:10px;font-weight:700;letter-spacing:.05em;text-transform:uppercase}
.calendar-days{grid-auto-rows:minmax(136px,auto)}
.calendar-day{min-width:0;padding:8px;border-right:1px solid var(--line);border-bottom:1px solid var(--line);background:#fff}
.calendar-day:nth-child(7n){border-right:0}
.calendar-day:nth-last-child(-n+7){border-bottom:0}
.calendar-empty{background:#fafbf9}
.calendar-day-date{display:flex;align-items:baseline;justify-content:space-between;gap:5px;margin-bottom:7px;color:var(--muted)}
.calendar-day-date span{font-size:9px;font-weight:700;letter-spacing:.04em;text-transform:uppercase}
.calendar-day-date strong{font-family:Georgia,serif;color:var(--ink);font-size:16px}
.calendar-day-rooms{display:grid;gap:4px}
.calendar-day-room{display:flex;align-items:center;justify-content:space-between;gap:4px;min-width:0;border-radius:5px;padding:4px 5px;font-size:9px}
.calendar-day-room span{min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
.calendar-day-room b{flex:none;font-size:10px}
.calendar-day.cal-today{box-shadow:inset 0 0 0 2px var(--green);position:relative;z-index:1}
.calendar-hint{display:flex;align-items:center;justify-content:space-between;gap:14px;margin:0 0 12px;padding:10px 12px;border:1px solid var(--line);border-radius:12px;background:#fafcfb}
.calendar-hint p{margin:0;color:var(--muted);font-size:11px;line-height:1.35}
.calendar-legend{display:flex;align-items:center;gap:10px;flex-wrap:wrap;font-size:11px;font-weight:700;color:var(--ink)}
.calendar-legend span{display:inline-flex;align-items:center;gap:5px;white-space:nowrap}
.calendar-dot{width:12px;height:12px;border-radius:4px;border:1px solid var(--line)}
.calendar-dot.free{background:#eef6f1;border-color:#bdd9ca}.calendar-dot.part{background:var(--amber-bg);border-color:#e8c783}.calendar-dot.full{background:var(--red-bg);border-color:#e3aaaa}
@media(prefers-reduced-motion:no-preference){.calendar-board{animation:calendar-board-in .18s ease-out}@keyframes calendar-board-in{from{opacity:.55;transform:translateY(3px)}to{opacity:1;transform:translateY(0)}}}
@media(max-width:900px){.calendar-days{grid-auto-rows:minmax(118px,auto)}.calendar-day{padding:6px}.calendar-day-room{padding:4px}.calendar-day-room span{display:none}.calendar-day-room{justify-content:center}.calendar-day-room b{font-size:9px}}
@media(max-width:560px){.calendar-days{grid-auto-rows:minmax(94px,auto)}.calendar-weekdays span{font-size:8px;padding:7px 1px}.calendar-day{padding:4px}.calendar-day-date{margin-bottom:4px}.calendar-day-date span{display:none}.calendar-day-date strong{font-size:14px}.calendar-day-rooms{gap:3px}.calendar-day-room{padding:3px 1px}.calendar-day-room b{font-size:8px}}

/* ── The reservation timeline ───────────────────────────────────────────────
   The second view of the same month. The seven-day grid above answers "how
   many are free on the 14th"; this answers "who is in which room, and where is
   the gap" — the question a week is actually planned around.

   Rooms down the left and dates across the top both stay put while the board
   scrolls under them, because a room name that scrolls away turns every bar
   into an unlabelled rectangle. */
.calendar-view-switch{display:flex;gap:2px;padding:2px;border:1px solid var(--line);border-radius:11px;background:#f2f6f3}
.calendar-view-option{display:flex;align-items:center;gap:5px;min-height:38px;padding:6px 11px;border:0;border-radius:9px;background:none;color:var(--muted);font:600 12px inherit;cursor:pointer;transition:background .14s,color .14s,box-shadow .14s}
.calendar-view-option span:first-child{font-size:13px;line-height:1}
.calendar-view-option:hover{color:var(--ink)}
.calendar-view-option.active{background:#fff;color:var(--green);box-shadow:0 1px 3px rgba(31,52,45,.12)}
.calendar-view-option:focus-visible{outline:2px solid var(--green);outline-offset:2px}

/* --tl-days is written onto .nightgrid as an inline style by the view, because
   only the view knows how many nights this month has. Everything below sizes
   itself from it.

   This replaces the lane-based stayboard. That board drew a Gantt bar per
   booking and stacked overlapping stays into numbered "lanes" — which read as
   room numbers and were not, since this system never assigns individual units.
   The owner's verdict was "terrible UI and UX", and the diagnosis is that it
   answered which-bar-is-above-which while making "is the 2-Bedroom free on
   Thursday?" a visual scan. One cell per room-night answers that by looking. */
.nightgrid{--tl-cell:44px;--tl-label:150px;border:1px solid var(--line);border-radius:13px;background:#fff;overflow:auto;max-height:min(68vh,620px);overscroll-behavior:contain}
/* A plain block, deliberately: the sticky header row is a child of this, and
   sticky positioning on a grid item behaves differently from sticky on a block
   child. min-width is what makes the whole grid scroll rather than squash. */
.nightgrid-grid{min-width:calc(var(--tl-label) + var(--tl-days) * var(--tl-cell))}
.nightgrid-row{display:grid;grid-template-columns:var(--tl-label) 1fr;border-bottom:1px solid #f3f6f3}
.nightgrid-row:last-child{border-bottom:0}
.nightgrid-header{position:sticky;top:0;z-index:3;border-bottom-color:var(--line)}
/* The room name stays put while the month scrolls sideways. Without this the
   thing that tells you WHICH row you are reading is the first thing to leave
   the screen. */
.nightgrid-room{position:sticky;left:0;z-index:2;display:flex;flex-direction:column;justify-content:center;gap:2px;padding:8px 11px;background:#fff;border-right:1px solid var(--line);min-width:0}
.nightgrid-header .nightgrid-room{z-index:4;background:#fafbf9}
.nightgrid-header .nightgrid-cells{background:#fafbf9}
.nightgrid-room strong{font-family:Georgia,serif;font-size:12px;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}
.nightgrid-room small{color:var(--muted);font-size:10px;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}
.nightgrid-corner small{font-size:9px;font-weight:700;letter-spacing:.1em;text-transform:uppercase;color:var(--muted)}
.nightgrid-cells{display:grid;grid-template-columns:repeat(var(--tl-days),var(--tl-cell))}
.nightgrid-head{display:flex;flex-direction:column;align-items:center;justify-content:center;gap:1px;padding:7px 0;border-left:1px solid #eef2ef;font-size:10px;color:var(--muted)}
.nightgrid-head b{font-size:12px;color:var(--ink);font-weight:700}
.nightgrid-head.weekend{background:#f7faf8}
/* Today is the column a person looks for first, on both the header and every
   cell beneath it, so it is marked down the whole column rather than once. */
.nightgrid-head.is-today{background:var(--mint);color:var(--green2)}
.nightgrid-head.is-today b{color:var(--green2)}

/* THE CELL IS THE WHOLE INTERFACE. Height matches the header so the grid reads
   as squares, and the count sits centred where the eye already is. */
.nightgrid-cell{position:relative;min-height:46px;display:grid;place-items:center;border-left:1px solid #eef2ef;font-size:11px;font-weight:700}
.nightgrid-cell.weekend{background:#f7faf8}
.nightgrid-cell.is-today{box-shadow:inset 2px 0 0 var(--green)}

/* Free is blank on purpose. A month of identical zeroes is noise a reader has
   to look past to find the nights that matter; absence of colour is the
   fastest possible "nothing to see here". */
.nightgrid-cell.fill-free{background:#eef6f1}
.nightgrid-cell.fill-part{background:var(--amber-bg);color:var(--amber)}
.nightgrid-cell.fill-full{background:var(--red-bg);color:var(--red-ink)}
.nightgrid-cell.fill-unknown{background:repeating-linear-gradient(45deg,#f4f6f4,#f4f6f4 4px,#ecefec 4px,#ecefec 8px)}

/* Only a night that can actually be sold invites a tap. A full night looking
   clickable and then refusing is worse than one that never offered. */
.nightgrid-cell.bookable{cursor:pointer}
.nightgrid-cell.bookable:hover{box-shadow:inset 0 0 0 2px var(--green);border-radius:6px}
.nightgrid-cell.bookable:focus-visible{outline:2px solid var(--green);outline-offset:-2px}
/* A taken night opens the stay in it, so it has to read as tappable too — the
   same ring, so the board has one idea of "this cell does something" rather
   than two. See the holder branch in calendarNightGrid. */
.nightgrid-cell.has-stay{cursor:pointer}
.nightgrid-cell.has-stay:hover{box-shadow:inset 0 0 0 2px var(--green);border-radius:6px}
.nightgrid-cell.has-stay:focus-visible{outline:2px solid var(--green);outline-offset:-2px}
.nightgrid-cell.bookable.selecting{background:var(--mint)}


/* THE CELL DOES NOT SHRINK, THE BOARD SCROLLS.
   These narrowed the night to 38px and then 34px to fit more of the month on
   screen, which put every night of a booking board under the 44px this file
   enforces everywhere else — measured at 34x40 on a phone. The room label
   gives up its width instead, and the month scrolls sideways, which it was
   already built to do: it opens centred on today rather than on the 1st. */
@media(max-width:900px){.nightgrid{--tl-label:114px}}
@media(max-width:560px){.nightgrid{--tl-label:94px;max-height:62vh}.nightgrid-room{padding:5px 7px}.nightgrid-room strong{font-size:11px}.nightgrid-room small{font-size:9px}.nightgrid-cell{min-height:44px;font-size:10px}.calendar-view-option span:first-child{display:none}.calendar-view-option{padding:6px 12px}}
.calendar-table{font-size:11px;min-width:1420px}
.calendar-table th,.calendar-table td{text-align:center;padding:8px 5px}
.calendar-table th{white-space:nowrap}
.calendar-table th span{display:block;font-size:9px;color:var(--muted);font-weight:600;text-transform:uppercase;letter-spacing:.04em}
.calendar-table .cal-room{text-align:left;white-space:nowrap;font-weight:600}
.calendar-table .cal-room small{display:block;color:var(--muted);font-weight:400}
.cal-cell{font-weight:700;min-width:42px}
.cal-cell strong{font-size:12px}
.cal-cell small{font-size:9px;font-weight:500;opacity:.76}
.cal-today{box-shadow:inset 0 0 0 2px var(--green)}
.cal-free{background:#eef6f1}
.cal-part{background:var(--amber-bg);color:var(--amber)}
.cal-full{background:var(--red-bg);color:var(--red-ink)}
/* THE HEADING AND THE CONTROLS STOP FIGHTING FOR THE SAME ROW.
   .card-head is a space-between flex row with no wrap, so on a phone the
   calendar title was squeezed into a ~140px column and broke across four
   lines beside the view switch. Stacked, the title reads as a title and the
   controls get the full width they need. */
@media(max-width:760px){.card-head:has(.calendar-actions){align-items:flex-start;flex-direction:column;gap:12px}
.card-head:has(.calendar-actions) .calendar-actions{width:100%;grid-template-columns:1fr}
.calendar-actions-main,.calendar-actions-extra{width:100%;justify-content:space-between}.calendar-actions .quiet span{display:none}.calendar-actions{gap:8px}.calendar-month-picker{flex:1;min-width:148px}.calendar-month-trigger{width:100%;justify-content:center}.calendar-month-popover{left:0;right:auto;width:min(292px,calc(100vw - 58px));transform:none}.calendar-hint{align-items:flex-start;flex-direction:column;gap:8px}@keyframes calendar-picker-in{from{opacity:0;transform:translateY(-4px)}to{opacity:1;transform:translateY(0)}}}
.calendar-sync-guide ol{margin:8px 0 16px;padding-left:21px;font-size:13px;line-height:1.65;color:var(--ink)}
.calendar-sync-guide li+li{margin-top:5px}
.calendar-feed-status{display:grid;gap:7px}
.calendar-feed-status>div{display:flex;justify-content:space-between;align-items:center;gap:12px;flex-wrap:wrap;border-top:1px solid var(--line);padding-top:9px;font-size:13px}
.calendar-feed-status strong small{display:block;color:var(--muted);font-size:10px;font-weight:400;margin-top:2px}

/* Payment history is loaded only when this page opens. These compact rows keep
   year-on-year comparison readable on a phone without hiding exact amounts. */
.payment-years{display:grid;grid-template-columns:repeat(auto-fit,minmax(150px,1fr));gap:10px}
.payment-years>div{display:grid;gap:3px;padding:14px;border:1px solid var(--line);border-radius:12px;background:#fafcfb}
.payment-years strong{font-size:12px;color:var(--muted)}
.payment-years span{font-family:Georgia,serif;font-size:20px;color:var(--green)}
.payment-years small{font-size:10px;color:var(--muted)}

/* ── The guest inbox, rebuilt ───────────────────────────────────────────────
   Modelled on WhatsApp deliberately. The staff using this already know
   WhatsApp, and a screen they can use without being taught is worth more than
   a screen that is denser. Everything below serves that: readable at arm's
   length on a POS, newest message in view, composer above the keyboard. */

.inbox{grid-template-columns:320px minmax(0,1fr) 260px}

/* Conversation list — a row is a button now, not a div with a click handler,
   so it is reachable by keyboard and reads correctly to a screen reader. */
.conversation{display:flex;align-items:center;gap:12px;width:100%;text-align:left;
  padding:14px 16px;border:0;border-bottom:1px solid var(--line);background:#fff;
  cursor:pointer;color:var(--ink);font:inherit}
.conversation:hover{background:#f6faf8}
.conversation.active{background:#eef6f2;box-shadow:inset 3px 0 var(--green)}
.conversation[hidden]{display:none}
.conversation-avatar{display:grid;place-items:center;width:44px;height:44px;flex:none;
  border-radius:50%;background:var(--mint);color:var(--green);font-weight:700;font-size:15px}
.conversation-body{min-width:0;flex:1;display:block}
.conversation-top{display:flex;justify-content:space-between;align-items:baseline;gap:8px}
.conversation-top strong{font-size:15px;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}
.conversation-top time{font-size:12px;color:var(--muted);flex:none}
.conversation-preview{display:block;font-size:13px;color:var(--muted);margin-top:3px;
  white-space:nowrap;overflow:hidden;text-overflow:ellipsis}
.conversation-flag{font-size:10px;font-weight:700;letter-spacing:.06em;text-transform:uppercase;
  background:var(--gold-bg);color:#806742;border-radius:20px;padding:3px 8px;flex:none}

/* The thread. 16px, because 11px on a phone held at arm's length across a
   reception desk is not a size anybody reads twice. */
.messages{padding:18px 16px 22px;background:#f3f5f1;display:flex;flex-direction:column;gap:2px;
  scroll-behavior:smooth;overscroll-behavior:contain}
.bubble{max-width:min(78%,520px);border-radius:16px;padding:10px 13px;margin-bottom:8px;
  font-size:16px;line-height:1.45;box-shadow:0 1px 1px rgba(31,52,45,.08);word-break:break-word}
.bubble.guest{background:#fff;border:0;align-self:flex-start;border-top-left-radius:5px}
.bubble.bot{background:#dff3e6;color:var(--ink);align-self:flex-end;border-top-right-radius:5px;margin-left:0}
.bubble.staff{background:var(--green);color:#fff;align-self:flex-end;border-top-right-radius:5px;margin-left:0}
.bubble-text{display:block;white-space:pre-wrap}
.bubble-meta{display:flex;justify-content:flex-end;align-items:center;gap:6px;
  margin-top:5px;font-size:11px;opacity:.72}
.bubble-meta em{font-style:normal;font-weight:600}
.bubble.guest .bubble-meta{color:var(--muted)}

/* In flight, and failed. A message that vanishes when the network hiccups
   costs the person their words and tells them in a banner that fades. */
.bubble.sending{opacity:.6}
.bubble.failed{background:var(--red);color:#fff}
.retry{background:rgba(255,255,255,.22);border:0;color:#fff;font:inherit;font-size:11px;
  font-weight:700;border-radius:20px;padding:3px 10px;cursor:pointer}
.retry:hover{background:rgba(255,255,255,.34)}

.day-break{align-self:center;margin:12px 0 10px}
.day-break span{background:rgba(31,52,45,.08);color:#4b6158;font-size:11px;font-weight:600;
  border-radius:20px;padding:4px 12px}

.chat-head{gap:12px;padding:12px 14px}
.chat-head .avatar{width:42px;height:42px;border-radius:50%;background:var(--mint);color:var(--green);font-size:14px}
.chat-head strong{font-size:16px}
.chat-head small{font-size:12px}
.chat-back{display:none;background:none;border:0;color:var(--green);font-size:24px;
  line-height:1;padding:6px 10px;cursor:pointer}
.chat-release{flex:none;font-size:13px}

.chat-compose{padding:12px;gap:10px;background:#fff;border-top:1px solid var(--line)}
.chat-compose input{flex:1;border:1px solid var(--line);border-radius:24px;padding:13px 16px;
  font:inherit;font-size:16px;background:#f7f9f6}
.chat-compose input:focus{outline:2px solid var(--green);outline-offset:-1px;background:#fff}
.send-button{flex:none;width:48px;height:48px;border-radius:50%;border:0;background:var(--green);
  color:#fff;font-size:19px;cursor:pointer;display:grid;place-items:center}
.send-button:hover{background:var(--green2)}
.chat-closed{padding:14px 16px;background:var(--amber-bg);color:#806742;font-size:14px;
   line-height:1.5;border-top:1px solid var(--line)}
.app-shell.inbox-page .chat{min-height:0}
.app-shell.inbox-page .messages{min-height:0}
.app-shell.inbox-page .chat-compose{flex:none}
.chat-compose-disabled{opacity:.72}
.chat-compose-disabled input{color:var(--muted);cursor:not-allowed}
.chat-compose-disabled .send-button{cursor:not-allowed;background:#879791}

@media(max-width:1100px){
  .inbox{grid-template-columns:280px minmax(0,1fr)}
}
@media(max-width:760px){
  .chat-back{display:block}
  .messages{padding:14px 12px 18px}
  .bubble{max-width:86%;font-size:16px}
  /* The composer must sit ABOVE the keyboard, not behind it. dvh follows the
     visible area as the keyboard opens; vh does not, which is what buries an
     input on a phone. */
  .inbox{height:calc(100vh - 84px);height:calc(100dvh - 84px)}
  .chat-compose{padding:10px 10px calc(10px + env(safe-area-inset-bottom))}
}

/* ── Legible sizes ─────────────────────────────────────────────────────────
   The layout was drawn at 9-11px, which is fine on a designer's monitor and
   unreadable on a phone at arm's length or a POS across a desk. Raised where
   it costs nothing, left alone where a larger size would reflow the page.
   Nothing here changes structure — only what a person can actually read. */
.metric-head{font-size:12px}
.metric-value{font-size:30px}
.metric-foot{font-size:12px}
.card-head h2{font-size:19px}
.card-head p{font-size:12.5px}
.notice strong{font-size:15px}
.notice p{font-size:13px}
.nav-item{font-size:14px}
.nav-label{font-size:11px}
.badge{font-size:11px;padding:2px 8px}
.brand strong{font-size:22px}
.brand small,.property-switch small,.profile small,.system-mini small{font-size:12px}
.property-switch strong{font-size:14px}
.system-mini strong,.profile strong{font-size:13px}
table{font-size:13px}
th{font-size:12px}
td{padding:14px 9px}
.status{font-size:10.5px;padding:5px 9px}
.timeline-time{font-size:12px}
.timeline-copy strong{font-size:13.5px}
.timeline-copy span{font-size:12.5px}
.attention strong{font-size:14px}
.attention p{font-size:12.5px}
.attention small{font-size:11px}
.detail-row{font-size:13px;margin:9px 0}
.member-copy strong{font-size:13.5px}
.member-copy small{font-size:11.5px}
.permission-copy strong{font-size:13px}
.permission-copy small{font-size:11.5px}
.permission-group h3{font-size:11px}
.permission-stat strong{font-size:20px}
.permission-stat small{font-size:11px}
.access-banner strong{font-size:14px}
.access-banner p{font-size:12.5px}
.field label{font-size:11px}
.field input,.field select{font-size:15px}
.empty{font-size:14px;line-height:1.6}
.toast{font-size:14px}
.searchbox,.filter{font-size:15px}

/* 16px on any control a phone can focus. Anything smaller and iOS zooms the
   whole page in on tap, which is the single most common way a mobile layout
   is broken by accident. */
@media(max-width:760px){
  input,select,textarea{font-size:16px}
}

/* First-run walkthrough, anchored to what it is describing.
   The dimming is done with one enormous box-shadow on a transparent box rather
   than four overlay rectangles or an SVG mask: it is a single element, it
   animates smoothly as it travels between steps, and it leaves the highlighted
   control genuinely visible instead of merely lighter. */
.tour-veil{position:fixed;inset:0;z-index:9998;pointer-events:auto}
.tour-veil::before{content:'';position:fixed;inset:0;background:rgba(12,30,26,.62)}
.tour-veil:has(.tour-spot)::before{display:none}
.tour-spot{position:fixed;
  border-radius:14px;box-shadow:0 0 0 9999px rgba(12,30,26,.68);pointer-events:none;
  transition:left .32s cubic-bezier(.22,1,.36,1),top .32s cubic-bezier(.22,1,.36,1),
    width .32s cubic-bezier(.22,1,.36,1),height .32s cubic-bezier(.22,1,.36,1);
  outline:2px solid rgba(255,255,255,.85);outline-offset:2px}
.tour-card{width:min(360px,calc(100vw - 24px));background:#fff;border-radius:18px;
  padding:22px 22px 16px;box-shadow:0 24px 60px rgba(0,0,0,.32);text-align:center}
.tour-card.anchored{position:fixed;transition:top .32s cubic-bezier(.22,1,.36,1),left .32s cubic-bezier(.22,1,.36,1)}
.tour-card.centred{position:fixed;top:50%;left:50%;transform:translate(-50%,-50%);width:min(420px,calc(100vw - 32px));padding:28px 26px 22px}
.app-shell.tour-running{pointer-events:none}
.tour-veil,.tour-veil *{pointer-events:auto}
.tour-icon{font-size:30px;line-height:1;margin-bottom:8px}
.tour-card h2{font-family:Georgia,serif;font-size:20px;margin:0 0 8px}
.tour-card p{font-size:14.5px;line-height:1.6;color:#4b6158;margin:0 0 16px}
.tour-dots{display:flex;justify-content:center;gap:7px;margin-bottom:18px}
.tour-dots span{width:7px;height:7px;border-radius:50%;background:#d3ddd8}
.tour-dots span.on{background:var(--green);width:20px;border-radius:20px}
.tour-actions{display:flex;justify-content:space-between;align-items:center;gap:10px}
.tour-actions .primary{flex:1;min-height:44px}
@media(prefers-reduced-motion:reduce){.tour-spot,.tour-card.anchored{transition:none}}

/* ── The inbox has to fill the screen exactly ──────────────────────────────
   It was sized with calc(100dvh - 84px), a guess at the header height, and on
   a phone the header wraps to two rows — so the composer sat just below the
   fold and the send button was half cut off. Measuring it in CSS is fragile
   and measuring it in JS is worse; letting flexbox do the arithmetic means the
   chat fills whatever is left over, whatever the header does. */
.app-shell.inbox-page .main{display:flex;flex-direction:column;height:100vh;height:100dvh;overflow:hidden}
.app-shell.inbox-page .content.inbox-content{flex:1;min-height:0;display:flex;padding-bottom:0}
.app-shell.inbox-page .content.inbox-content .inbox{flex:1;min-height:0;height:auto;margin:0}
@media(max-width:760px){
  /* THE INBOX RECLAIMS THE TOPBAR, BUT NOT THE HEADING.

     display:none on the whole bar took 146px back on an 844px screen, which
     was the point and is worth keeping. It also took the page’s only h1 out
     of the accessibility tree, so somebody navigating by headings arrived at
     a screen that never says what it is. The audit caught it as "no h1 — the
     page never states its own subject".

     So the bar goes and the heading stays, clipped to a pixel: no space, no
     visual change, still announced. */
  .app-shell.inbox-page .topbar{
    position:absolute;width:1px;height:1px;padding:0;margin:-1px;
    overflow:hidden;clip-path:inset(50%);white-space:nowrap
  }
  .app-shell.inbox-page .topbar .eyebrow,
  .app-shell.inbox-page .topbar .top-actions{display:none}
  .app-shell.inbox-page .main{height:100dvh;min-height:100dvh}
  .app-shell.inbox-page .content.inbox-content{height:100%;min-height:0}
  .app-shell.inbox-page .content.inbox-content .inbox{height:100%;min-height:0}
  .app-shell.inbox-page .content.inbox-content .inbox{padding:0}
  .app-shell.inbox-page .content.inbox-content{padding:0}
  .app-shell.inbox-page .content.inbox-content .inbox{border-radius:0;border-left:0;border-right:0}
}

/* ── The welcome ───────────────────────────────────────────────────────────
   Four seconds, once per account, before the walkthrough. Pure keyframes:
   nothing else could be loaded anyway, because the content security policy
   allows no third-party script — and that policy is what keeps anything else
   away from the session token. */
.welcome-stage{position:fixed;inset:0;z-index:10000;display:grid;place-items:center;
  background:radial-gradient(120% 90% at 50% 40%,#1c4a40 0%,#12312a 45%,#07120f 100%);
  color:#f3f7f5;overflow:hidden;opacity:1;transition:opacity .6s ease}
.welcome-stage.leaving{opacity:0}

/* A slow bloom behind everything, so the screen feels lit rather than filled. */
.welcome-glow{position:absolute;width:min(120vmin,900px);aspect-ratio:1;border-radius:50%;
  background:radial-gradient(circle,rgba(120,215,180,.30) 0%,rgba(120,215,180,0) 62%);
  animation:welcome-bloom 4.2s cubic-bezier(.22,1,.36,1) forwards}
@keyframes welcome-bloom{
  0%{transform:scale(.35);opacity:0}
  35%{opacity:1}
  100%{transform:scale(1.25);opacity:.55}
}

/* Three rings expanding outward, staggered — the arrival. */
.welcome-rings{position:absolute;display:grid;place-items:center}
.welcome-rings span{position:absolute;width:150px;height:150px;border-radius:50%;
  border:1px solid rgba(143,227,200,.42);opacity:0;
  animation:welcome-ring 3.1s cubic-bezier(.22,1,.36,1) infinite}
.welcome-rings span:nth-child(2){animation-delay:.55s}
.welcome-rings span:nth-child(3){animation-delay:1.1s}
@keyframes welcome-ring{
  0%{transform:scale(.5);opacity:0}
  22%{opacity:.85}
  100%{transform:scale(3.4);opacity:0}
}

/* The mark lands first, then everything else follows it. */
.welcome-mark{position:relative;width:96px;height:96px;border-radius:28px;display:grid;
  place-items:center;background:linear-gradient(145deg,#c9a227,#8f7016);color:#0d211d;
  font-family:Georgia,serif;font-size:52px;font-weight:700;
  box-shadow:0 24px 60px rgba(0,0,0,.45);transform:translateY(-56px) scale(.6);opacity:0;
  animation:welcome-mark 1s cubic-bezier(.16,1.06,.3,1) .15s forwards}
@keyframes welcome-mark{
  0%{transform:translateY(-90px) scale(.55) rotate(-14deg);opacity:0}
  60%{opacity:1}
  100%{transform:translateY(-56px) scale(1) rotate(0);opacity:1}
}

.welcome-words{position:relative;text-align:center;padding:0 24px;transform:translateY(28px)}
.welcome-eyebrow{margin:0 0 8px;font-size:13px;letter-spacing:.22em;text-transform:uppercase;
  color:#8fe3c8;opacity:0;animation:welcome-rise .9s cubic-bezier(.22,1,.36,1) .85s forwards}
.welcome-words h1{margin:0;font-family:Georgia,serif;font-size:clamp(28px,7vw,44px);
  line-height:1.15;opacity:0;animation:welcome-rise 1s cubic-bezier(.22,1,.36,1) 1.05s forwards}
.welcome-sub{margin:12px 0 0;font-size:15px;color:#a9c7bf;opacity:0;
  animation:welcome-rise 1s cubic-bezier(.22,1,.36,1) 1.45s forwards}
@keyframes welcome-rise{from{opacity:0;transform:translateY(16px)}to{opacity:1;transform:none}}

/* A hairline that draws itself across, last. */
.welcome-line{position:absolute;bottom:22%;height:1px;width:0;
  background:linear-gradient(90deg,transparent,rgba(143,227,200,.75),transparent);
  animation:welcome-draw 1.6s cubic-bezier(.22,1,.36,1) 1.7s forwards}
@keyframes welcome-draw{to{width:min(70vw,520px)}}

/* Motion sickness is a real response to this kind of thing, and a welcome
   screen is a bad place to find that out. Same words, no movement. */
.welcome-stage.still .welcome-glow,
.welcome-stage.still .welcome-rings,
.welcome-stage.still .welcome-line{display:none}
.welcome-stage.still .welcome-mark,
.welcome-stage.still .welcome-eyebrow,
.welcome-stage.still .welcome-words h1,
.welcome-stage.still .welcome-sub{animation:none;opacity:1;transform:none}
.welcome-stage.still{transition:opacity .2s ease}
@media(prefers-reduced-motion:reduce){
  .welcome-glow,.welcome-rings,.welcome-line{display:none}
  .welcome-mark,.welcome-eyebrow,.welcome-words h1,.welcome-sub{animation:none;opacity:1;transform:none}
}

/* ── The property comes first ──────────────────────────────────────────────
   The sidebar opened with "Staybot / Operations" and put the property below
   it as a switcher. That is the right order for a platform and the wrong one
   for an owner: she is not logging in to Staybot, she is logging in to her
   apartments. Her name and her building are the header now, and Staybot is a
   line at the bottom, which is where the plumbing belongs. */
.property-hero{display:flex;align-items:center;gap:12px;padding:4px 6px 18px;flex:none}
.property-photo{width:56px;height:56px;flex:none;border-radius:16px;display:grid;place-items:center;
  background:linear-gradient(145deg,#c9a227,#8f7016);color:#0d211d;font-family:Georgia,serif;
  font-size:19px;font-weight:700;background-size:cover;background-position:center;
  box-shadow:0 6px 18px rgba(0,0,0,.28)}
.property-photo.has-photo span{display:none}
.property-titles{min-width:0;flex:1}
.property-titles strong{display:block;font-family:Georgia,serif;font-size:19px;line-height:1.2;color:#fff}
.property-titles small{display:block;color:#a9c7bf;font-size:11.5px;margin-top:3px}
.property-change{color:#fff;border:0;background:none;min-width:36px;min-height:36px;
  display:grid;place-items:center;border-radius:9px;cursor:pointer;flex:none}
.property-change:hover{background:rgba(255,255,255,.12)}

.brand-foot{display:flex;align-items:center;gap:8px;padding:11px 8px 2px;
  border-top:1px solid rgba(255,255,255,.1);color:#8fb0a6;font-size:11px;letter-spacing:.04em}
.brand-foot-mark{display:grid;place-items:center;width:18px;height:18px;border-radius:6px;
  background:rgba(201,162,39,.85);color:#0d211d;font-family:Georgia,serif;font-size:11px;font-weight:700}

/* The welcome opens on HER building, not our logo. An owner seeing her own
   property is the entire point of the screen; the Staybot mark belongs in the
   footer with the rest of the plumbing. Falls back to a monogram when no
   photograph has been set yet. */
.welcome-mark.has-photo{background-size:cover;background-position:center;color:transparent;
  border:1px solid rgba(255,255,255,.28)}

/* The PIN pad. Wide-spaced digits, so four characters read as four. */
.pin-card{text-align:center;max-width:380px}
.pin-icon{font-size:32px;margin-bottom:8px}
.pin-card h2{font-family:Georgia,serif;font-size:21px;margin:0 0 8px}
.pin-card p{font-size:14px;line-height:1.6;color:#4b6158;margin:0 0 16px}
.pin-label{display:block;font-size:11px;font-weight:700;letter-spacing:.08em;color:var(--muted);
  text-align:left;margin:12px 0 6px}
.pin-input{width:100%;border:1px solid var(--line);border-radius:12px;padding:13px;
  font:inherit;font-size:26px;letter-spacing:.5em;text-align:center;text-indent:.5em;
  background:#f7f9f6;-webkit-text-security:disc}
.pin-input:focus{outline:2px solid var(--green);outline-offset:-1px;background:#fff}
.pin-note{min-height:1.4em;margin:14px 0 0;font-size:13px}
.pin-note[data-tone="bad"]{color:var(--red-ink)}
.pin-card .modal-actions{margin-top:14px}
.pin-card .modal-actions .primary{flex:1;min-height:46px}

/* PIN sign-in, on the veil. */
.signin-card.pin-signin .pin-input{background:#0d211d;border-color:#2b6156;color:#f3f7f5}
.signin-other{margin-top:14px;width:100%;background:none;border:0;color:#a9c7bf;
  font:inherit;font-size:13px;text-decoration:underline;cursor:pointer}
.signin-other:hover{color:#f3f7f5}

/* Choosing who is signing in, on a shared front desk. */
.who-list{display:grid;gap:10px;margin:6px 0 4px}
.who{display:flex;align-items:center;gap:12px;width:100%;padding:12px 14px;border-radius:12px;
  border:1px solid #2b6156;background:#0d211d;color:#f3f7f5;font:inherit;font-size:15px;
  text-align:left;cursor:pointer;min-height:56px}
.who:hover{background:#13302a}
.who-avatar{display:grid;place-items:center;width:38px;height:38px;flex:none;border-radius:50%;
  background:#2b6156;color:#f3f7f5;font-weight:700;font-size:14px}

/* Quoting a specific message, the way swiping does on WhatsApp. */
.bubble.quotable{cursor:pointer}
.bubble.quotable:active{transform:scale(.99)}
.bubble-quote{display:block;margin:-2px 0 6px;padding:6px 9px;border-left:3px solid currentColor;
  border-radius:6px;background:rgba(0,0,0,.07);font-size:13px;line-height:1.35;opacity:.85;
  overflow:hidden;text-overflow:ellipsis}
.bubble.staff .bubble-quote{background:rgba(255,255,255,.16)}
.compose-quote{display:flex;align-items:center;gap:10px;padding:10px 14px;background:#eef6f2;
  border-top:1px solid var(--line);font-size:13px;color:#3c5a51}
.compose-quote span{flex:1;min-width:0;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}
.compose-quote strong{color:var(--green)}
.quote-clear{border:0;background:none;font-size:22px;line-height:1;color:var(--muted);
  cursor:pointer;min-width:36px;min-height:36px}

/* ── Editing rates and property facts ──────────────────────────────────────
   A save bar that follows you down the page, because the fields worth editing
   are longer than a screen and a Save button at the bottom is a Save button
   nobody finds. */
.edit-bar{position:sticky;top:0;z-index:20;display:flex;align-items:center;justify-content:space-between;
  gap:12px;margin-bottom:16px;padding:12px 16px;border-radius:14px;background:var(--green);color:#fff;
  box-shadow:0 8px 24px rgba(31,52,45,.22);font-size:14px;font-weight:600}
.edit-bar[hidden]{display:none}
.edit-bar div{display:flex;gap:8px}
.edit-bar .quiet{color:#cfe6dd}
.edit-bar .quiet:hover{color:#fff}
.edit-bar .primary{background:#fff;color:var(--green);border-color:#fff}
.edit-bar .primary:hover{background:#eef6f2}

.edit-card .card-head{margin-bottom:14px}
.edit-grid{display:grid;grid-template-columns:repeat(auto-fit,minmax(220px,1fr));gap:14px}
.edit-field{display:grid;gap:6px;min-width:0}
.edit-field.full{grid-column:1/-1}
.edit-field label{font-size:11px;font-weight:700;letter-spacing:.06em;color:var(--muted);text-transform:uppercase}
.edit-field input,.edit-field textarea{width:100%;border:1px solid var(--line);border-radius:10px;
  padding:11px 12px;font:inherit;font-size:15px;background:#fff;color:var(--ink)}
.edit-field textarea{resize:vertical;line-height:1.5}
.edit-field input:focus,.edit-field textarea:focus{outline:2px solid var(--green);outline-offset:-1px}
/* A changed field says so, so it is obvious what the Save button is about to
   write — and what it is not. */
.edit-field.changed input,.edit-field.changed textarea{border-color:var(--gold-ink);background:#fffdf6}
.edit-hint{font-size:11px;color:var(--muted)}

.edit-toggle{display:flex;align-items:center;justify-content:space-between;gap:12px;
  padding:11px 12px;border:1px solid var(--line);border-radius:10px;background:#fff;font-size:14px;
  min-height:48px;cursor:pointer}
.edit-toggle.changed{border-color:var(--gold-ink);background:#fffdf6}
.room-rate{font-family:Georgia,serif;font-size:20px;color:var(--green);white-space:nowrap}

@media(max-width:760px){
  .edit-grid{grid-template-columns:1fr}
  .edit-bar{flex-wrap:wrap;position:sticky;top:0}
  .edit-bar div{width:100%}
  .edit-bar .primary{flex:1;min-height:46px}
}

/* ── The sidebar health line, which used to be three static words ──────────
   It read "All systems healthy" unconditionally, including while every request
   was failing. Now it carries a tone set from the last actual read, and it is a
   button, because the detail behind it is a real screen. */
.system-mini{width:100%;border:0;background:none;color:inherit;font:inherit;text-align:left;
  cursor:pointer;border-radius:10px;min-height:44px}
.system-mini:hover{background:rgba(255,255,255,.08)}
.pulse[data-tone="green"]{background:#76d5a8;box-shadow:0 0 0 4px rgba(118,213,168,.13)}
.pulse[data-tone="amber"]{background:var(--gold);box-shadow:0 0 0 4px rgba(200,160,70,.16)}
.pulse[data-tone="red"]{background:#e2837c;box-shadow:0 0 0 4px rgba(226,131,124,.16)}

/* A fact and its verdict on one row, so a WhatsApp number and an "OK" do not
   have to be squeezed into a single classified string. */
.health-value{display:inline-flex;align-items:center;gap:8px;font-weight:600}

/* ── Keyboard focus, everywhere ───────────────────────────────────────────
   Only .tappable, inputs and the sign-in card carried a focus ring, so a
   person tabbing through the sidebar or the team list on a keyboard had to
   guess where they were. */
button:focus-visible,input:focus-visible,select:focus-visible,textarea:focus-visible,
[tabindex]:focus-visible{outline:2px solid var(--green);outline-offset:2px}
.sidebar button:focus-visible,.nav-item:focus-visible{outline:2px solid var(--gold);outline-offset:-2px}

/* The two sidebar controls that were under the 44px thumb target on a phone. */
@media(max-width:760px){
  .property-change,.profile button{min-width:44px;min-height:44px}
}

/* ── The last controls under a thumb on a POS ─────────────────────────────
   Measured at 390px: the thread back-button was 36px tall, the bookings
   filter 40px, the role select 36px and the search box a fraction under.
   These are all controls a front desk hits at speed on a phone, and a
   mis-tap on the back button drops you into the wrong guest's thread. */
@media(max-width:760px){
  .chat-back{min-width:44px;min-height:44px}
  .searchbox,.filter,.role-select,select{min-height:46px}
  .toolbar .filter{padding-block:11px}
}

/* ── Service operations ───────────────────────────────────────────────────
   Tickets are action rows, not another dense report.  The next real action is
   the visible button, so a receptionist can accept, progress and close work
   without opening a second screen. */
.request-ticket-list{display:grid;gap:10px}
.request-ticket{display:flex;align-items:center;justify-content:space-between;gap:14px;
  padding:13px;border:1px solid var(--line);border-radius:13px;background:#fff}
.request-ticket-main{display:flex;align-items:flex-start;gap:10px;min-width:0}
.request-ticket-main>div{min-width:0}
.request-ticket strong{display:block;font-size:13px}
.request-ticket p{margin:4px 0;color:var(--muted);font-size:11px;line-height:1.45}
.request-ticket small{color:var(--muted);font-size:10px}
.request-ticket-actions{display:flex;align-items:center;justify-content:flex-end;gap:8px;flex-wrap:wrap;flex:none}
.service-setup{width:min(680px,100%);max-height:calc(100vh - 40px);overflow:auto}
.service-setup-section{padding:15px 0;border-top:1px solid var(--line)}
.service-setup-section .card-head{gap:12px;margin-bottom:10px}
.service-setup-section h3{font-family:Georgia,serif;font-size:16px;margin:0}
.service-config-list{display:grid;gap:8px}
.service-config-row{display:flex;align-items:center;justify-content:space-between;gap:12px;width:100%;
  border:1px solid var(--line);border-radius:11px;background:#fff;padding:11px 12px;text-align:left;
  color:var(--ink);font:inherit;cursor:pointer}
.service-config-row:hover{border-color:#a9c7bf;background:#f8fbf9}
.service-config-row strong,.service-config-row small{display:block}
.service-config-row strong{font-size:12px}
.service-config-row small{margin-top:3px;color:var(--muted);font-size:10px;line-height:1.35}
.choice-grid{display:flex;flex-wrap:wrap;gap:8px}
.choice{display:flex;align-items:center;gap:7px;padding:9px 10px;border:1px solid var(--line);
  border-radius:9px;background:#fff;font-size:12px;cursor:pointer}
.choice input{width:16px;height:16px;margin:0;accent-color:var(--green)}
.form-grid .field textarea{width:100%;min-height:88px;border:1px solid var(--line);border-radius:10px;
  padding:11px;font:inherit;line-height:1.45;resize:vertical;color:var(--ink);background:#fff}
@media(max-width:760px){
  .request-ticket{align-items:flex-start;flex-direction:column}
  .request-ticket-actions{width:100%;justify-content:space-between}
  .service-setup{padding:18px}
  .service-setup-section .card-head{align-items:flex-start;flex-direction:column}
  .booking-review{grid-template-columns:1fr}
  .date-picker-button span:last-child{display:none}
  .date-picker-button{min-width:46px;justify-content:center;padding:0}
}
.operator-menu{position:absolute;right:clamp(22px,3vw,45px);top:78px;width:235px;padding:13px;border:1px solid #d7e0dd;border-radius:12px;background:#fff;box-shadow:0 18px 45px #0b2b2330;z-index:25}.operator-menu p{margin:0!important;color:#153c32!important;font-weight:800}.operator-menu small{display:block;margin:4px 0 10px;color:#75847f;font-size:8px}.operator-menu button{width:100%;padding:9px;border:0;border-radius:8px;background:#f2f6f4;color:#173f34;text-align:left;font-size:9px;font-weight:700;cursor:pointer}.operator-menu button+button{margin-top:5px}.operator-menu button.danger{color:#a23d38;background:#faecea}

/* Operator OS: render one workspace at a time and keep large operational
   collections bounded. This lowers DOM and paint work without hiding data. */
.platform-workspace{position:relative;min-height:calc(100vh - 102px);scroll-margin-top:105px}
.platform-view{animation:operator-view-in .16s cubic-bezier(.2,.8,.2,1)}
@keyframes operator-view-in{from{opacity:.35;transform:translateY(5px)}to{opacity:1;transform:none}}
.platform-panel,.fleet-card,.integration-card{content-visibility:auto;contain-intrinsic-size:auto 320px}
.platform-section-intro.compact{min-height:78px}.platform-section-intro .filter-chips{flex:none}
.platform-rail-foot{width:100%;color:#fff;text-align:left;font:inherit;cursor:pointer}.platform-rail-foot:hover{border-color:#ffffff2e;background:#ffffff0d}.live-dot.paused{background:#d2a24b;box-shadow:0 0 0 4px #d2a24b1b}
.data-count{margin:14px 2px 0;color:#75857f;font-size:10px;text-align:right}
.data-pagination{min-height:54px;margin:16px -20px -20px;padding:8px 12px 8px 17px;border-top:1px solid #dfe6e3;display:flex;align-items:center;gap:15px;background:#f7f9f8;border-radius:0 0 17px 17px;color:#687a74}
.data-pagination p{margin:0;flex:1;font-size:10px}.data-pagination p strong{color:#173f34;font-variant-numeric:tabular-nums}
.data-pagination label{display:flex;align-items:center;gap:7px;font-size:9px;font-weight:700;text-transform:uppercase;letter-spacing:.06em}.data-pagination select{height:34px;border:1px solid #d1dcd7;border-radius:8px;background:#fff;color:#173f34;padding:0 24px 0 8px;font-size:10px;cursor:pointer}
.data-pagination>span{display:flex;align-items:center;gap:7px}.data-pagination>span b{min-width:78px;text-align:center;color:#294a41;font-size:9px;font-variant-numeric:tabular-nums}
.data-pagination button{width:34px;height:34px;border:1px solid #d1dcd7;border-radius:9px;background:#fff;color:#174b3d;font-size:14px;cursor:pointer}.data-pagination button:hover:not(:disabled){border-color:#7b9e93;background:#eef5f2}.data-pagination button:disabled{opacity:.38;cursor:default}
.property-grid+.data-pagination,.integration-grid+.data-pagination{margin:16px 0 0;border:1px solid #dfe6e3;border-radius:12px}
.platform-body[data-density="compact"] .platform-workspace{font-size:93%}.platform-body[data-density="compact"] .platform-kpi{min-height:104px;padding:13px}.platform-body[data-density="compact"] .fleet-row{min-height:45px;padding-block:7px}.platform-body[data-density="compact"] .fleet-card,.platform-body[data-density="compact"] .integration-card,.platform-body[data-density="compact"] .platform-panel{padding:15px}.platform-body[data-density="compact"] .property-grid,.platform-body[data-density="compact"] .integration-grid{gap:10px}.platform-body[data-density="compact"] .session-row,.platform-body[data-density="compact"] .activity-stream article{padding-block:8px}
@media(prefers-reduced-motion:reduce){.platform-view{animation:none}.tappable{transition:none}}
@media(max-width:700px){.data-pagination{margin:14px -15px -15px;padding:10px 12px;flex-wrap:wrap}.data-pagination p{flex:1 1 100%}.data-pagination label{margin-right:auto}.data-pagination>span b{min-width:70px}.platform-section-intro .filter-chips{width:100%;overflow:auto}}

/* ── Authentication portals ───────────────────────────────────────────────
   Hotel staff get the familiar, low-friction account pattern they expect.
   The fleet operator entrance is a separate visual and credential flow; it is
   never linked from this screen and is also gated by the server allow-list. */
.signin-veil{overflow:auto;padding:clamp(18px,4vw,48px);font:15px/1.5 system-ui,-apple-system,"Segoe UI",sans-serif}
.property-signin-veil{background:
  radial-gradient(circle at 15% 15%,rgba(189,146,82,.15),transparent 32%),
  radial-gradient(circle at 85% 85%,rgba(36,91,77,.12),transparent 34%),#f2f5f1;color:var(--ink)}
.signin-card{width:min(440px,100%);padding:clamp(26px,4vw,38px);border:1px solid #dfe6e1;border-radius:22px;
  background:rgba(255,255,255,.98);color:var(--ink);box-shadow:0 28px 70px rgba(25,49,45,.14)}
.auth-brand{display:flex;align-items:center;gap:11px;margin-bottom:30px}
.auth-brand-mark{display:grid;place-items:center;width:40px;height:40px;border-radius:12px;background:var(--green);color:#fff;
  font:700 21px/1 Georgia,serif;box-shadow:0 7px 18px rgba(23,61,53,.2)}
.auth-brand>span:last-child{display:grid;line-height:1.15}.auth-brand strong{font:600 18px/1.2 Georgia,serif}.auth-brand small{margin-top:3px;color:var(--muted);font-size:10px;letter-spacing:.08em;text-transform:uppercase}
.auth-mode-switch{display:grid;grid-template-columns:1fr 1fr;gap:4px;margin:-10px 0 25px;padding:4px;border:1px solid #dce4e0;border-radius:12px;background:#f1f4f2}
.signin-card .auth-mode-switch button{min-height:39px;width:100%;margin:0;padding:8px;border:0;border-radius:9px;background:transparent;color:#6a7a75;font:700 12px/1 inherit;cursor:pointer;box-shadow:none}
.signin-card .auth-mode-switch button:hover{background:#fff;color:var(--green)}.signin-card .auth-mode-switch button.active{background:#fff;color:var(--green);box-shadow:0 3px 10px rgba(25,49,45,.09)}
.signin-heading{margin-bottom:25px}.signin-card .signin-heading h1{margin:0 0 7px;color:var(--ink);font:600 clamp(27px,5vw,32px)/1.12 Georgia,serif;letter-spacing:-.02em}
.signin-card .signin-heading p{margin:0;color:var(--muted);font-size:14px}
.signin-fields{display:grid;gap:8px}.signin-fields label+input,.signin-fields label+.password-field{margin-bottom:8px}
.signin-card label{margin:0;color:#42534f;font-size:12px;font-weight:700;letter-spacing:.01em;text-transform:none}
.signin-card input,.signin-card.pin-signin .pin-input{width:100%;min-height:48px;padding:11px 13px;border:1px solid #ccd8d2;border-radius:11px;background:#fbfcfb;color:var(--ink);font:inherit;transition:border-color .15s,box-shadow .15s,background .15s}
.signin-card input::placeholder{color:#9aa7a3}.signin-card input:focus,.signin-card.pin-signin .pin-input:focus{outline:0;border-color:#508b7b;background:#fff;box-shadow:0 0 0 4px rgba(36,91,77,.12)}
.signin-card .password-toggle{right:7px;min-width:52px;min-height:36px;margin:0;padding:7px;border-radius:8px;background:transparent;color:var(--green);font-size:12px;font-weight:700;text-decoration:none}
.signin-card .password-toggle:hover{background:#edf4f0;color:var(--green)}
.signin-card button.signin-primary,.signin-card>button[type="submit"]{width:100%;min-height:48px;margin-top:17px;padding:12px 15px;border:1px solid var(--green);border-radius:11px;background:var(--green);color:#fff;font:700 14px/1 inherit;cursor:pointer;box-shadow:0 7px 16px rgba(23,61,53,.16)}
.signin-card button.signin-primary:hover,.signin-card>button[type="submit"]:hover{background:var(--green2);border-color:var(--green2)}
.signin-card button.signin-secondary{width:100%;min-height:46px;margin-top:10px;padding:11px;border:1px solid #ccd8d2;border-radius:11px;background:#fff;color:var(--green);font:700 13px/1 inherit;cursor:pointer}
.signin-card button.signin-secondary:hover{background:#f2f6f3;border-color:#9fb8af}
.signin-help{margin:15px 0 0!important;color:var(--muted)!important;font-size:12px!important;line-height:1.55}
.signin-note{min-height:20px;margin:14px 0 0!important;color:var(--muted)!important;font-size:12px!important;line-height:1.45}
.signin-note[data-tone="bad"]{color:#a43d38!important}.signin-note[data-tone="good"]{color:#19724f!important}
.signin-footer{display:flex;align-items:center;justify-content:space-between;gap:12px;margin:18px -6px -4px;padding-top:17px;border-top:1px solid var(--line)}
.signin-card .signin-footer button{width:auto;min-height:40px;margin:0;padding:7px 6px;border:0;border-radius:7px;background:transparent;color:#52645f;font:500 12px/1.35 inherit;cursor:pointer;box-shadow:none}
.signin-card .signin-footer button:hover{background:#f1f5f2;color:var(--green)}.signin-card .signin-footer strong{color:var(--green)}
.signin-footer-single{justify-content:center}.signin-or{color:var(--muted)}.signin-or::before,.signin-or::after{background:var(--line)}
.signin-card .signin-google{min-height:46px;margin-top:10px;border-color:#ccd8d2;background:#fff;color:var(--ink)}.signin-card .signin-google:hover{background:#f2f6f3}
.signin-other{color:var(--green)}.signin-other:hover{color:var(--green2)}
.who{border-color:#d5dfda;background:#f7faf8;color:var(--ink)}.who:hover{background:#edf4f0}.who-avatar{background:var(--mint);color:var(--green)}

.operator-signin-veil{background:
  linear-gradient(rgba(255,255,255,.025) 1px,transparent 1px),
  linear-gradient(90deg,rgba(255,255,255,.025) 1px,transparent 1px),#071512;
  background-size:34px 34px;color:#edf6f2}
.signin-card.operator-signin{border-color:#1f463c;background:rgba(10,31,26,.97);color:#edf6f2;box-shadow:0 32px 90px rgba(0,0,0,.5)}
.operator-signin .auth-brand-mark{background:#c29a5b;color:#11221e}.operator-signin .auth-brand strong,.operator-signin .signin-heading h1{color:#f6faf8}
.operator-signin .auth-brand small,.operator-signin .signin-heading p,.operator-signin label{color:#9cb7af}
.signin-card.operator-signin input{border-color:#285348;background:#0b241e;color:#f6faf8}.signin-card.operator-signin input::placeholder{color:#6f9187}
.signin-card.operator-signin input:focus{border-color:#77b8a5;background:#0d2b24;box-shadow:0 0 0 4px rgba(119,184,165,.12)}
.signin-card.operator-signin .password-toggle{color:#9ed3c3}.signin-card.operator-signin .password-toggle:hover{background:#153a31;color:#fff}
.signin-card.operator-signin button.signin-primary,.signin-card.operator-signin>button[type="submit"]{border-color:#c29a5b;background:#c29a5b;color:#10231e;box-shadow:0 8px 22px rgba(194,154,91,.18)}
.signin-card.operator-signin button.signin-primary:hover,.signin-card.operator-signin>button[type="submit"]:hover{border-color:#d2ad70;background:#d2ad70}
.signin-card.operator-signin .signin-footer{border-color:#1f463c}.signin-card.operator-signin .signin-footer button{color:#9cb7af}.signin-card.operator-signin .signin-footer button:hover{background:#12352d;color:#fff}
.signin-card.operator-signin .signin-note{color:#9cb7af!important}.signin-card.operator-signin .signin-note[data-tone="bad"]{color:#ffad9c!important}.signin-card.operator-signin .signin-note[data-tone="good"]{color:#8fe3c8!important}

@media(max-width:520px){
  .signin-veil{place-items:start center;padding:14px}.signin-card{margin:auto 0;padding:25px 21px;border-radius:18px}
  .auth-brand{margin-bottom:24px}.signin-footer{align-items:stretch;flex-direction:column;gap:2px}.signin-card .signin-footer button{width:100%}
}

/* ── The new-message pill ────────────────────────────────────────────────────
   Shown only when the reader has deliberately scrolled up and something has
   arrived below them. The alternative — moving the thread under someone who is
   mid-sentence reading it — is what the old code did, and it is the single
   fastest way to make a screen feel untrustworthy. Sits inside .chat, above
   the composer, exactly where WhatsApp puts it, because the people using this
   already know what it means and that is training we do not have to pay for. */
.chat{position:relative}
.new-message-pill{position:absolute;left:50%;bottom:82px;transform:translateX(-50%);
  z-index:4;border:0;border-radius:999px;padding:9px 16px;background:var(--green);color:#fff;
  font:inherit;font-size:13px;font-weight:600;cursor:pointer;
  box-shadow:0 6px 20px rgba(12,30,26,.28);animation:pill-in .18s ease-out}
.new-message-pill:hover{filter:brightness(1.08)}
.new-message-pill:focus-visible{outline:3px solid var(--gold);outline-offset:2px}
@keyframes pill-in{from{opacity:0;transform:translate(-50%,8px)}to{opacity:1;transform:translate(-50%,0)}}
@media(prefers-reduced-motion:reduce){
  .new-message-pill{animation:none}
  /* A thread that glides is a thread that moves without being asked. Anyone
     who has said they do not want that gets the instant jump instead. */
  .messages{scroll-behavior:auto}
}
@media(max-width:760px){.new-message-pill{bottom:76px;font-size:12px}}

/* ── Handoff queues ──────────────────────────────────────────────────────────
   Two queues, and they must not look alike. The one holding a guest who has
   had no reply for forty hours is the only thing on the page anybody needs to
   act on right now; the one holding conversations a person is already in is
   reference. Colouring both the same is what made the desk stop reading either. */
.attention-urgent .attention-symbol{background:var(--amber-bg);color:#8a6d3b}
.attention-calm{opacity:.86}
.attention-calm .attention-symbol{background:var(--mint);color:var(--green)}
.attention-calm strong{font-weight:600}
.handoff-done{flex:none;margin-left:auto;font-size:12px;padding:7px 12px;border-radius:9px;
  border:1px solid var(--line);background:#fff;color:var(--green);cursor:pointer;white-space:nowrap}
.handoff-done:hover{background:var(--mint)}
.handoff-done:focus-visible{outline:2px solid var(--green);outline-offset:2px}
@media(max-width:520px){.handoff-done{padding:6px 9px;font-size:11px}}

/* ── A tappable availability board ───────────────────────────────────────────
   The desk is already looking at the night they want. Making them re-enter it
   in a modal is the friction that gets a system worked around instead of used.
   Tapping a date opens the booking form on that date; tapping a room line fills
   the room in too. Only shown as tappable to a role that can actually book. */
.calendar-day-date[data-calendar-date],
.calendar-day-room.bookable{cursor:pointer}
.calendar-day-date[data-calendar-date]{border-radius:8px;transition:background .12s}
.calendar-day-date[data-calendar-date]:hover{background:var(--mint)}
.calendar-day-room.bookable{position:relative;transition:filter .12s,transform .12s}
.calendar-day-room.bookable:hover{filter:brightness(.96);transform:translateY(-1px)}
.calendar-day-date[data-calendar-date]:focus-visible,
.calendar-day-room.bookable:focus-visible{outline:2px solid var(--green);outline-offset:2px}
.app-shell .calendar-day.day-bookable:hover{box-shadow:0 2px 10px rgba(31,52,45,.09)}
@media(prefers-reduced-motion:reduce){
  .calendar-day-room.bookable:hover{transform:none}
}

/* ── Where a booking came from ───────────────────────────────────────────────
   A letter, not an emoji: emoji render differently on every Android build a
   front desk might be holding, and several of the obvious ones are illegible at
   this size. Colour carries the same information a second way, and the title
   carries it in words for anyone who cannot use either. */
.ref-cell{display:inline-flex;align-items:center;gap:7px;white-space:nowrap}
.chan{display:grid;place-items:center;width:19px;height:19px;flex:none;border-radius:6px;
  font-size:10px;font-weight:700;line-height:1;letter-spacing:0;cursor:help}
.chan-bot{background:var(--mint);color:var(--green)}
.chan-desk{background:var(--gold-bg);color:#8a6d3b}
.chan-channel{background:#e8e4f3;color:#5b4a86}

/* ── Dragging a stay across the board ────────────────────────────────────────
   A walk-in is almost never one night. Press on the first night, drag to the
   last, let go — the form opens with both dates in it. `touch-action:none` on
   the cells is what lets a finger drag select instead of scrolling the page;
   it is scoped to the cells so the rest of the board still scrolls normally. */
.calendar-days [data-calendar-date]{touch-action:none;-webkit-user-select:none;user-select:none}
.calendar-day-date.in-drag,
.calendar-day-room.in-drag{background:var(--green);color:#fff;border-radius:8px}
.calendar-day-room.in-drag span,
.calendar-day-room.in-drag b,
.calendar-day-date.in-drag span,
.calendar-day-date.in-drag strong{color:#fff}

/* ── Money that has only partly arrived ──────────────────────────────────────
   The property takes GHS 500 to secure a room and the balance at check-in, so
   most bookings sit part paid. The amount cell says what is still owed under
   the total, because "GHS 2,580" alone reads as settled. */
.amount .owing{display:block;margin-top:3px;font-size:10px;font-weight:600;color:var(--amber)}
.pay-cell{width:1%;white-space:nowrap}
.pay-button{font-size:11px;padding:6px 10px;border:1px solid var(--line);border-radius:9px;
  background:#fff;color:var(--green);cursor:pointer}
.pay-button:hover{background:var(--mint)}
.pay-button:focus-visible{outline:2px solid var(--green);outline-offset:2px}

/* Turnover, on today's departures. A guest having left is not the end of the
   job — the unit still has to be ready before anyone else goes in it. */
.turnover-button{align-self:start;font-size:11px;padding:8px 12px;min-height:34px;border:1px solid var(--line);
  border-radius:9px;background:#fff;color:var(--green);cursor:pointer;white-space:nowrap}
.turnover-button:hover{background:var(--mint)}
.turnover-button:focus-visible{outline:2px solid var(--green);outline-offset:2px}

/* ── The laws of UX, applied to controls that were already here ─────────────
   Refinement, not redesign: the owner asked for the psychology without losing
   the interface she has learned. Every rule below adjusts an existing control
   rather than introducing a new one. */

/* FITTS'S LAW — time to hit a target grows as it shrinks. The Done button on a
   waiting guest was 12px text in 7px padding: about 30px tall, on a phone, held
   one-handed, next to a row that navigates if you miss. The owner reported
   tapping it and nothing happening. 44px is the smallest reliable thumb target
   and is what every other control here already uses. */
.handoff-done{min-height:44px;padding:10px 14px}
@media(max-width:520px){.handoff-done{min-height:44px;padding:10px 12px;font-size:12px}}

/* The same measurement, applied to the two controls still under it. */
.property-change,.icon-button{min-width:44px;min-height:44px}

/* DOHERTY THRESHOLD — under ~400ms a person stays on task; past it they start
   wondering whether the machine heard them. Anything waiting on the network now
   says so in the control itself, where the eye already is, rather than only in
   a toast that arrives after the wait it was meant to explain. */
button[aria-busy="true"]{opacity:.72;cursor:progress}
button[aria-busy="true"]::after{
  content:"";display:inline-block;width:11px;height:11px;margin-left:7px;
  vertical-align:-1px;border:2px solid currentColor;border-right-color:transparent;
  border-radius:50%;animation:staybot-spin .7s linear infinite}
@keyframes staybot-spin{to{transform:rotate(360deg)}}

/* VON RESTORFF — the item that differs is the item that is remembered. An
   escalated guest has been waiting long enough that the owner was told about
   it, and in a list of identical amber rows it read as one more of the same.
   One row in the list may carry this; if everything is urgent, nothing is. */
.attention-urgent .attention-symbol{
  box-shadow:0 0 0 3px rgba(207,99,92,.18);
  animation:staybot-pulse 2.4s ease-in-out infinite}
@keyframes staybot-pulse{50%{box-shadow:0 0 0 6px rgba(207,99,92,.09)}}

/* AESTHETIC-USABILITY / PRÄGNANZ — a control that moves when pressed confirms
   the press before the network can. Cheap, and it is the difference between
   "did that register?" and knowing it did. */
button:active:not(:disabled),.tappable:active{transform:translateY(1px)}

/* Every law above is a motion or a size. Motion is a preference, not a
   requirement, and a guest or an owner who has asked their device to stop
   animating has asked for a reason. */
@media(prefers-reduced-motion:reduce){
  button[aria-busy="true"]::after,.attention-urgent .attention-symbol{animation:none}
  button:active:not(:disabled),.tappable:active{transform:none}
}

/* Room photographs. The bot can send these to a guest who asks to see the
   place; this is where an owner puts them.

   Previews are served from /api/v1/room-photos/file — this dashboard's own
   origin — because the CSP here is img-src 'self' data:. A Supabase storage URL
   would render as a blank square with a console error, which is why the API
   proxies the bytes rather than the policy being widened for a picture. */
.photo-strip{margin-top:16px;padding-top:15px;border-top:1px solid var(--line)}
.photo-strip-head{display:flex;align-items:baseline;gap:10px;flex-wrap:wrap;margin-bottom:11px}
.photo-strip-head strong{font-size:11px}
.photo-tiles{display:grid;grid-template-columns:repeat(auto-fill,minmax(132px,1fr));gap:11px}
.photo-tile{position:relative;margin:0;border:1px solid var(--line);border-radius:13px;overflow:hidden;background:#fafbf9}
.photo-tile img{display:block;width:100%;aspect-ratio:4/3;object-fit:cover;background:#eef1ef}
.photo-tile figcaption{padding:8px 9px;font-size:10px;color:var(--muted);line-height:1.35;overflow-wrap:anywhere}
.photo-tile .missing{color:#a5b0ad}
/* Always visible rather than revealed on hover: half the people using this are
   on a tablet at a front desk, where there is no hover. */
.photo-remove{position:absolute;right:5px;top:5px;background:rgba(255,255,255,.94);border:1px solid var(--line);border-radius:8px;padding:4px 8px;font-size:9px;color:var(--red-ink)}
.photo-remove:hover{background:var(--red-bg)}
.photo-add{display:inline-block;margin-top:12px}
.photo-add span{display:inline-block;border:1px dashed #b8cac4;border-radius:10px;padding:9px 14px;cursor:pointer;font-size:11px}
.photo-add span:hover{background:var(--mint)}
@media(max-width:600px){.photo-tiles{grid-template-columns:repeat(auto-fill,minmax(112px,1fr))}}

/* ── The conversation header, on a phone ────────────────────────────────────
   Reported from a real device: the header read "Archive Hand…" with the next
   control cut off entirely. Three buttons — Archive, Hand back, Mark resolved —
   plus a back arrow, an avatar, the guest's name and their status, all in one
   non-wrapping row on a 390px screen. The buttons a receptionist needs mid-shift
   were the ones off the edge.

   The row wraps instead. The identity block keeps the first line to itself, the
   actions drop underneath it, and nothing is hidden behind a menu the operator
   has to learn about — a control they cannot see is one they will not use.

   SUPERSEDED, and left here as the record of why: wrapping the actions onto
   their own row made the header three lines tall (175px measured). They live
   behind the ⋯ sheet further down instead. Only the parts that survived that
   change are kept here — dropping the rest stops two layouts fighting, which
   is how the ⋯ control ended up 129px wide and the guest's number cut off. */
@media(max-width:760px){
  .chat-head strong{white-space:nowrap;overflow:hidden;text-overflow:ellipsis}
}

/* THE DRAWER WAS A DESKTOP SIDEBAR ON A PHONE. At 248px it covered about
   two-thirds of a 390px screen and left a strip of the page showing behind it,
   which reads as unfinished rather than deliberate. Nearly full width makes it
   a navigation sheet — a decision, not an accident — while the remaining sliver
   still says "there is a page behind this, tap to return". */
@media(max-width:520px){
  .sidebar{width:min(88vw,340px);left:calc(-1 * min(88vw,340px))}
  .sidebar.open{left:0}
}

/* ── The reply box, which now grows ─────────────────────────────────────────
   It was an <input>: one line forever, so a long reply scrolled sideways and
   the operator could see only the tail of their own sentence. Reading back what
   you have written before sending it to a guest is how a typo in a price gets
   caught. Every rule the input had is repeated for the textarea, because the
   selector above matches `input` only. */
.chat-compose textarea{
  flex:1;border:1px solid var(--line);border-radius:24px;padding:13px 16px;
  font:inherit;font-size:15px;line-height:1.45;background:#fff;color:var(--ink);
  /* resize:none because the height is computed from the content — a manual
     drag handle would fight growComposer on the next keystroke. */
  resize:none;overflow-y:hidden;min-height:48px;max-height:9.5em;
  /* The pill shape only reads as a pill on one line. Once it wraps, a softer
     radius stops the corners eating the first and last characters. */
  transition:border-radius .12s}
.chat-compose textarea:not(:placeholder-shown){border-radius:18px}
.chat-compose textarea:focus-visible{outline:2px solid var(--green);outline-offset:-2px}
/* The buttons sit at the BOTTOM of a grown box, next to the last line being
   typed, rather than floating in the middle of a tall composer. */
.chat-compose{align-items:flex-end}

/* ── The guest inbox on a phone ─────────────────────────────────────────────
   Measured on a 390×844 screen: the thread had 420px — half the display — and
   the other half was the GUEST PANEL. That panel is a 280px desktop column, and
   `.inbox{display:block}` on mobile stacks the three columns instead of laying
   them side by side, so it sat underneath the conversation taking as much room
   as the conversation itself. The owner reads three messages and then scrolls
   past a details card she did not ask for.

   Hidden on a phone. Everything on it that matters mid-conversation — who the
   guest is, their number, what state the conversation is in — is already in the
   header two centimetres above it. */
@media(max-width:760px){
  .inbox .guest-panel{display:none}

  /* THE THREAD TAKES THE SCREEN. 100vh is wrong on a phone: the browser chrome
     is counted, so the composer ends up under the address bar. 100dvh follows
     the bar as it hides and shows, which is what makes a chat feel native. */
  .inbox{height:calc(100dvh - 84px)}
  .inbox.chat-open .chat{height:100%}

  /* Breathing room, because the bubbles are now the whole screen rather than
     a column inside it. */
  .messages{padding:14px 12px 18px}
  .bubble{max-width:82%;line-height:1.45}
  /* The timestamp under each bubble was 8px — unreadable, and it is how the
     owner tells this morning's message from yesterday's. */
  .bubble small{font-size:11px;margin-top:5px}

  /* The composer sits on the safe area, so the send button is not under the
     home indicator on a modern iPhone. */
  .chat-compose{padding:10px 10px calc(10px + env(safe-area-inset-bottom))}
}

/* THE LIST IS A LIST OF PEOPLE, so it is sized for a thumb rather than a mouse.
   Rows were 14px padding with 12px names — a desktop density on a screen held
   at arm's length. */
@media(max-width:760px){
  .conversation{padding:14px 16px;min-height:72px}
  .conversation strong{font-size:16px}
  .conversation p{font-size:13px;margin:4px 0 0}
  .conversation time{font-size:12px}
  .conversation-search input{font-size:16px;min-height:44px}
}

/* ── The conversation header, sized for the screen it is on ─────────────────
   Measured before this: the header was 175px on a 390×844 phone and the topbar
   another 146px — 321px of chrome, 38% of the display, before one message. My
   earlier fix caused half of it: wrapping three 44px buttons onto their own
   line stopped the clipping and made the header three rows tall.

   So the actions go behind one control on a phone. I resisted this at first —
   a control you cannot see is one you will not use — but the measurement
   settles it: three buttons sharing 390px are a worse thumb target than three
   full-width rows in a sheet, and the header goes back to one line. */
.chat-more{display:none}
.chat-actions{display:contents}

@media(max-width:760px){
  .chat-head{flex-wrap:nowrap;row-gap:0;padding:10px 12px;position:relative}
  .chat-head .chat-who{flex:1;order:0;min-width:0}
  .chat-more{
    display:grid;place-items:center;flex:none;order:1;
    min-width:44px;min-height:44px;border:0;background:none;
    color:var(--green);font-size:22px;line-height:1;border-radius:12px;cursor:pointer}
  .chat-more:hover{background:var(--mint)}

  /* Closed by default; opened by the ⋯ control. Positioned rather than pushed
     into the flow, so opening it never moves the conversation underneath. */
  .chat-actions{
    display:none;position:absolute;top:100%;right:8px;z-index:20;
    background:#fff;border:1px solid var(--line);border-radius:14px;
    box-shadow:0 18px 40px rgba(0,0,0,.16);padding:6px;min-width:190px}
  .chat-head.actions-open .chat-actions{display:grid;gap:2px}
  .chat-actions button{
    width:100%;min-height:46px;text-align:left;border:0;background:none;
    border-radius:10px;font-size:15px;padding:0 14px}
  .chat-actions button:hover{background:var(--mint)}

  /* THE TOPBAR WAS 146px on an 844px screen — a sixth of the display spent on
     a title and a row of buttons. It is not scoped to the inbox because
     `.topbar` is a SIBLING of `.content`, not a child: `.inbox-content .topbar`
     matched nothing, which is why the first attempt at this changed nothing.
     Every page gains the room anyway. */
  .topbar{padding:10px 14px;gap:8px}
  .topbar h1{font-size:19px;line-height:1.2}
  .eyebrow{font-size:9px;letter-spacing:.1em}
}

/* THE TOPBAR WAS THREE ROWS ON A PHONE. Measured at 390px: the menu button
   (46px), the title block (40px) and the action icons (64px) each wrapped onto
   a line of their own — 138px of chrome for a title and four icons, on a screen
   where the owner is trying to read a conversation.

   One row. The title takes the space that is left and truncates rather than
   pushing the icons off; the icons stay visible because their badges are how
   she sees an unread guest without opening anything. */
@media(max-width:760px){
  .topbar{flex-wrap:nowrap;align-items:center;min-height:0}
  .topbar > div:not(.top-actions){flex:1;min-width:0}
  .topbar h1{white-space:nowrap;overflow:hidden;text-overflow:ellipsis}
  /* WHAT SURVIVES ON A 390px SCREEN. Hiding the whole row (the original
     design) took away New booking at the desk; showing all five pushed it off
     the right edge and dropped the eyebrow on top of the menu button. Both
     were wrong. Keep the two badges — they are the only reason she picks the
     phone up — and the one action a walk-in triggers. Search and the install
     prompt are in the drawer, one tap away, and neither is urgent. */
  .top-actions{flex:none;order:0;width:auto;gap:6px;align-items:center}
  .top-actions > *{display:none}
  .top-actions #inbox-alert,
  .top-actions #alerts,
  .top-actions .primary{display:grid;place-items:center}
  .top-actions .icon-button{width:44px;height:44px;flex:none;padding:0}
  /* Icon only. The word costs 96px on a screen that has 390 of them, and the
     ＋ with an aria-label reads the same to a screen reader as it ever did. */
  .top-actions .primary{width:44px;min-height:44px;flex:none;padding:0;font-size:21px;line-height:1}
  .top-actions .primary .button-label{display:none}
  /* Beats the rule further up that re-showed this one by id. Installing to the
     home screen still matters on a phone — more than anywhere else — so it
     moves into the drawer rather than disappearing. */
  .top-actions #pwa-install:not([hidden]){display:none}

  /* THE EYEBROW WAS TALLER THAN THE TITLE. "CONVERSATIONS & HANDOFFS" wrapped
     onto three lines in the space left over and squeezed "Guest inbox" down to
     "Gu…" — the label shouting over the thing it labels. On a phone the h1
     already says where you are. */
  .topbar .eyebrow{display:none}

}

/* THE HEADER READ IN THE WRONG ORDER. A screenshot showed it as back-arrow,
   name, avatar, ⋯ — because the identity block was given order:0 while the
   avatar kept the source order after it. And the number wrapped onto its own
   line, making a one-line header three lines tall again.

   Measurements said 84px and it still looked broken, which is the argument for
   looking at the thing rather than only measuring it. */
@media(max-width:760px){
  .chat-head .chat-back{order:0}
  .chat-head .avatar{order:1;flex:none}
  .chat-head .chat-who{order:2;flex:1;min-width:0}
  .chat-more{order:3}
  /* One line each, truncated. A guest's name and number are for recognising
     them, not for reading in full — the guest panel and the booking hold the
     complete versions. */
  .chat-head strong,.chat-head small{
    display:block;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}
  .chat-head small{font-size:12px}
}

/* The install action, in the drawer where a phone has room for words. Sized
   and coloured like a nav item because that is what it sits among. */
/* REMOVED, AND IT WAS NEVER A STRAY BOX.
   The drawer showed an empty dashed rectangle under Control centre. The button
   was there and working; its text was invisible. It was styled for a light
   background — `color:var(--green)`, the dark green — and the sidebar it sits
   in is dark green. Green on green, with the dashed border the only thing
   anybody could see.
   So it has never once been readable on a phone, which is the only place it
   appears. Removed rather than recoloured, because the browser's own "Add to
   Home Screen" does the same job and is where people already look for it.
   #pwa-install-nav stays in the markup as hidden, and pwa.js still binds it —
   see offerInstall() — so restoring this is a colour and a display rule, not a
   feature to rebuild. */
.sidebar-install{display:none !important}

/* MEASURED, NOT GUESSED. The ⋯ control came out 129px wide and the guest's name
   and number were squeezed into the 85px left over — "Ama Boa…" and a phone
   number cut off mid-digits, on the header whose whole job is saying who you
   are talking to. An earlier rule gives every header button `flex:1 1 auto` so
   the old Archive / Hand back / Mark resolved row shared the width evenly;
   ⋯ is not that kind of button and must not grow. */
@media(max-width:760px){
  .chat-head .chat-more{flex:0 0 44px;width:44px;min-width:44px}
  /* The number is the identity the owner asked us to stop hiding, so it gets
     the room it needs; the state word behind it can be the part that goes. */
  .chat-head small{font-size:11px}
}

/* A RED "0" SAT ON THE ALERTS ICON PERMANENTLY. The code hides the badge
   correctly — `badge.hidden = open === 0` — but `.notification span` sets
   `display:grid`, and any author display rule beats the `hidden` attribute's
   UA `display:none`. So an owner glancing at her phone saw a red alert badge
   at all times, which is the fastest way to teach someone to ignore badges. */
.notification span[hidden]{display:none}

/* The paperclip. Same size and weight as the clear-draft control beside it so
   the composer reads as one row of equals, and a real thumb target on a phone —
   this is the button the owner reaches for while standing in a room she is
   photographing. */
.chat-attach{
  flex:none;width:40px;height:40px;display:grid;place-items:center;
  border:1px solid var(--line);border-radius:50%;background:#fff;
  font-size:16px;line-height:1;cursor:pointer}
.chat-attach:hover{background:var(--mint);border-color:var(--green)}
@media(max-width:760px){.chat-attach{width:44px;height:44px;font-size:18px}}

/* Inbox presentation pass: the inbox is a messaging workspace, not a card
   dropped into a dashboard. Keep the list dense enough to scan and let the
   thread own the available height on both a POS and a phone. */
.inbox{border-radius:12px;overflow:hidden;background:#fff;box-shadow:0 4px 18px rgba(31,52,45,.06)}
.conversation-list{background:#fff}
.conversation-list-head{min-height:70px;padding:14px 18px}
.conversation-list-head strong{font-size:17px}
.conversation-list-head small{font-size:12px}
.inbox-list-title-mobile{display:none}
.conversation-search{padding:10px 14px;background:#f7f9f7}
.conversation-search .searchbox{width:100%;height:40px;min-height:40px;border-radius:20px;background:#eef2ef;padding:0 16px}
.conversation{min-height:74px;padding:13px 17px}
.conversation-avatar{width:46px;height:46px;background:#dcebe5}
.conversation-top strong{font-size:14px}
.conversation-preview{font-size:12px}
.conversation.unread .conversation-preview{color:var(--ink);font-weight:600}
.chat{background:#f3f5f1}
.chat-head{min-height:68px;background:#fff;border-bottom:1px solid #dfe6e2;box-shadow:0 1px 4px rgba(31,52,45,.05);z-index:2}
.messages{background-color:#edf3ee;background-image:radial-gradient(rgba(23,61,53,.055) .7px,transparent .7px);background-size:14px 14px}
.chat-compose{display:flex;align-items:flex-end;padding:10px 12px calc(10px + env(safe-area-inset-bottom));background:#f3f6f3;border-top:1px solid #dfe6e2}
.chat-compose textarea{order:1;min-height:46px;padding:11px 16px;border-color:#d9e3de;background:#fff}
.chat-attach{order:2;width:44px;height:44px;background:#fff}
.chat-clear{order:3}
.send-button{order:4;width:46px;height:46px}
.chat-compose-disabled input{min-height:46px}
.chat-compose:has(textarea:placeholder-shown) .chat-clear{visibility:hidden;pointer-events:none}

@media(min-width:761px){
  .app-shell.inbox-page .content.inbox-content .inbox{border-radius:12px}
  .inbox{grid-template-columns:320px minmax(0,1fr)}
  .inbox .guest-panel{display:none}
  .conversation-list{min-width:0}
  .chat{min-width:0}
  .chat-head{padding-inline:18px}
  .messages{padding:22px 24px 26px}
  .chat-compose{padding:12px 16px}
  .chat-compose textarea{min-height:48px}
}

@media(max-width:760px){
  .app-shell.inbox-page .content.inbox-content .inbox{border-radius:0}
  /* Same reasoning as .inbox-page above: the bar goes so the thread gets the
       space, and the heading stays in the accessibility tree so the screen still
       says what it is. Two rules hid this bar and fixing only one left the
       warning standing. */
    .app-shell.inbox-thread-open .topbar{position:absolute;width:1px;height:1px;padding:0;margin:-1px;overflow:hidden;clip-path:inset(50%);white-space:nowrap}
    .app-shell.inbox-thread-open .topbar .eyebrow,.app-shell.inbox-thread-open .topbar .top-actions{display:none}
  .app-shell.inbox-thread-open .main{height:100dvh;min-height:100dvh}
  .app-shell.inbox-thread-open .content.inbox-content{height:100%;min-height:0}
  .app-shell.inbox-thread-open .content.inbox-content .inbox{height:100%;min-height:0}
  .app-shell.inbox-thread-open .chat{height:100%;min-height:0}
  /* WHATSAPP'S HEADER IS ONE LINE AND OURS WAS A CARD.
     Measured side by side against the real thing on the same phone: theirs is
     ~56px with a 40px avatar and the name at 16px; ours was 68px with a 42px
     avatar, a 16px name and a 12px subtitle, inside padding built for a
     desktop card. On a screen where the whole point is the conversation, that
     is twelve pixels of furniture and a header that reads as a panel rather
     than as a title bar.
     The subtitle stays. The guest's number with its country code is the one
     thing on this screen that says WHICH human this is, and it was hidden
     once before. */
  .app-shell.inbox-thread-open .chat-head{min-height:58px;padding:8px 10px;gap:9px}
  .app-shell.inbox-thread-open .chat-head .avatar{width:38px;height:38px;font-size:13px}
  .app-shell.inbox-thread-open .chat-head strong{font-size:15px;line-height:1.25}
  .app-shell.inbox-thread-open .chat-head small{font-size:11px;line-height:1.3}
  .app-shell.inbox-thread-open .chat-back{font-size:22px}
  .app-shell.inbox-thread-open .messages{min-height:0}
  .app-shell.inbox-thread-open .chat-compose{flex:none}
  .inbox-list-title-desktop{display:none}
  .inbox-list-title-mobile{display:inline}
  .conversation-list-head{min-height:64px;padding:13px 17px}
  .conversation-list-head strong{font-size:20px;letter-spacing:-.01em}
  .dashboard-grid{display:flex;flex-direction:column}
  .dashboard-grid>div{order:-1}
}


/* ── MESSAGES SIT ON THE COMPOSER, NOT UNDER THE HEADER ─────────────────────
   A three-message thread rendered as three bubbles at the top of the screen
   and six hundred pixels of empty wallpaper beneath them. Every messaging app
   ever shipped does the opposite: a short conversation rests on the composer
   and grows upwards, because the newest message is the one you are reading and
   it belongs where your thumb already is.

   `margin-top:auto` on the first child rather than
   `justify-content:flex-end` on the container. They look equivalent and are
   not: with flex-end, once the thread is long enough to overflow, Chrome makes
   the top of the scroll range unreachable and the oldest messages cannot be
   scrolled back to at all. An auto margin resolves to zero the moment free
   space goes negative, so a long thread scrolls exactly as it did before.

   :not(.empty) because "No messages recorded on this conversation yet" is a
   notice about the whole pane, and a notice pinned to the bottom edge reads as
   a message somebody sent. */
.messages > :first-child:not(.empty){margin-top:auto}

/* ── THE GUEST'S OWN BUBBLES DO NOT NEED A LABEL SAYING "GUEST" ─────────────
   Every bubble carried its author: "12:04 Guest", "12:04 Staybot". On the two
   that leave this property the label is doing real work — whether the guest
   was answered by Kaleasia or by a person is the single most useful fact on
   the screen, and the colours alone do not carry it.

   On the guest's own messages it is noise. They are white, they are on the
   left, and there is one other party in the conversation. WhatsApp does not
   label them either, for the same reason. The time stays. */
.bubble.guest .bubble-meta em{display:none}

/* Edge to edge means edge to edge. The card's 1px top border survived the
   corner and border removal above and left the chat header one pixel down the
   screen with a hairline over it, which is exactly the sort of detail that
   makes a phone screen look assembled rather than designed. */
@media(max-width:760px){
  .app-shell.inbox-page .content.inbox-content .inbox{border-top:0;box-shadow:none}
}

/* Installed PWA mode has no browser chrome to absorb awkward dimensions. The
   dashboard must own the whole viewport and reserve space for its own controls. */
/* ── INSTALLED ON THE HOME SCREEN ───────────────────────────────────────────
   In a browser the address bar sits between the phone's status bar and the
   page, so the top of the document is never underneath the clock. Installed,
   there is no address bar — the page owns the whole display, status bar
   included — and anything at top:0 is drawn UNDER the time, the signal bars
   and the battery.

   Which is exactly what the inbox does. `.app-shell.inbox-page .topbar` is
   display:none on a phone, deliberately, so the conversation header IS the top
   of the screen; and installed, that header was sitting behind the status bar
   with the guest's name half under the clock.

   So the fix is not more layout. Almost everything below used to restate the
   mobile rules that already apply here — same heights, same overflow, same nav
   reserve — and duplicated rules only drift apart. What genuinely differs when
   there is no browser chrome is the safe area, and that is all this block is
   now for.

   Backgrounds are painted on the padded element rather than behind it, so the
   colour runs up under the status bar the way a native app's does instead of
   leaving a white strip. */
@media (display-mode: standalone) and (max-width: 760px) {
  /* The two elements that can be topmost on the inbox: the conversation header
     with a thread open, and the list header without one. */
  .app-shell.inbox-page .chat-head,
  .app-shell.inbox-page .conversation-list-head {
    padding-top: calc(10px + env(safe-area-inset-top));
  }

  /* Every other page still shows the topbar, so it is the one that clears the
     status bar there. */
  .app-shell:not(.inbox-page) .topbar {
    padding-top: calc(14px + env(safe-area-inset-top));
  }

  /* The composer and the navigation bar clear the home indicator at the other
     end. .mobile-nav already reserves its own; the composer does not, because
     in a browser the bottom chrome does it. */
  .app-shell.inbox-thread-open .chat-compose {
    padding-bottom: calc(10px + env(safe-area-inset-bottom));
  }

  /* An installed app has nowhere to bounce to — a rubber-band scroll that
     reveals the page background behind the conversation reads as the app
     coming apart. */
  .messages,
  .conversation-list {
    overscroll-behavior: contain;
  }
}

@media(max-width:760px){
  .bookings-card{padding:14px}
  .bookings-card .card-head{align-items:flex-start;gap:10px}
  .bookings-card .card-head .secondary{min-height:40px;padding-inline:9px;font-size:12px}
  .booking-table{overflow:visible}
  .booking-table table{display:block;min-width:0}
  .booking-table thead{display:none}
  .booking-table tbody{display:grid;gap:10px}
  .booking-table tr{display:grid;grid-template-columns:1fr 1fr;gap:0 12px;padding:13px;border:1px solid var(--line);border-radius:13px;background:#fff}
  .booking-table td{display:flex;align-items:baseline;gap:7px;padding:7px 0;border:0;font-size:13px;min-width:0}
  .booking-table td::before{content:'';flex:none;color:var(--muted);font-size:10px;font-weight:700;text-transform:uppercase;letter-spacing:.04em}
  .booking-table td:nth-child(-n+4){grid-column:1/-1}
  .booking-table td:nth-child(1)::before{content:'Ref'}
  .booking-table td:nth-child(2)::before{content:'Guest'}
  .booking-table td:nth-child(3)::before{content:'Stay'}
  .booking-table td:nth-child(4)::before{content:'Room'}
  .booking-table td:nth-child(5)::before{content:'Guests'}
  .booking-table td:nth-child(6)::before{content:'Payment'}
  .booking-table td:nth-child(7)::before{content:'Status'}
  .booking-table td:nth-child(8)::before{content:'Total'}
  .booking-table td:nth-child(9)::before{content:'Action'}
  .booking-table .guest-cell,.booking-table .ref-cell{min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
  .booking-table .status{font-size:10px}
  .booking-table .pay-cell{justify-content:flex-end;grid-column:1/-1}
  .booking-table tr>td.empty{display:block;grid-column:1/-1;text-align:center}
  .edit-bar{position:sticky;bottom:calc(60px + env(safe-area-inset-bottom));z-index:8;margin:0 -14px 14px;padding:10px 14px;background:rgba(255,255,255,.96);border-block:1px solid var(--line);box-shadow:0 -6px 18px rgba(31,52,45,.08);backdrop-filter:blur(10px)}
  .edit-bar>div{display:flex;gap:7px}
  .edit-bar button{min-height:42px}
  .permission-actions{position:sticky;bottom:calc(60px + env(safe-area-inset-bottom));z-index:7;margin-inline:-15px}
  .team-layout .permission-card{overflow:visible}
}

/* "Load earlier messages", above the oldest bubble. Centred and quiet: it is
   a way back through the history, not an action on the conversation, and it
   must not compete with the messages for attention. */
.thread-older{display:grid;place-items:center;padding:2px 0 10px}
.thread-older button{border-radius:999px;font-size:12px;padding:7px 15px}
@media(max-width:760px){.thread-older button{min-height:36px;font-size:13px}}

.compose-identity{padding:7px 14px 0;font-size:11px;font-weight:600;color:var(--green)}

@media(max-width:760px){
  .handoff-strip{padding:6px 12px;gap:8px}
  .handoff-strip .handoff-take{min-height:36px}
  .handoff-note{margin:2px 4px 10px}
  .compose-identity{padding:6px 12px 0}
}

/* Said once, quietly, under the headline figures it qualifies. A number that
   is a floor rather than a total must say so somewhere the reader will see it
   before they act on it. */
.platform-truncated{margin:8px 0 0;font-size:12px;color:var(--gold-ink);font-weight:600}

/* ── THE HANDOFF STRIP ──────────────────────────────────────────────────────
   One line, and only while somebody has to decide something: nobody has this
   guest, or a colleague does. A conversation you already own shows nothing
   here at all.

   It replaced a card — headline, summary paragraph, meta line, a row of
   full-width buttons — that came to roughly 150px of fixed chrome above a
   thread on a 390px screen. A quarter of the message area, permanently, and
   on the commonest state it was spending that to repeat what the composer
   already said.

   Not sticky. The card was, on the argument that somebody scrolling back
   through a conversation still needs the action in view. At 38px it can sit
   above the thread without being worth pinning, and pinned chrome on a phone
   is the thing being complained about. */
.handoff-strip{display:flex;align-items:center;gap:10px;flex:none;
  padding:7px 14px;border-bottom:1px solid var(--line);background:#fff}
.handoff-strip-text{flex:1;min-width:0;font-size:12px;font-weight:600;
  white-space:nowrap;overflow:hidden;text-overflow:ellipsis}
.handoff-strip.waiting{background:var(--gold-bg);border-bottom-color:var(--gold-ink)}
.handoff-strip.theirs{background:#f6f7f5;color:var(--muted)}
.handoff-strip button{flex:none;padding:6px 13px;font-size:12px;border-radius:999px}

/* ── AND THE SUMMARY, INSIDE THE CONVERSATION ───────────────────────────────
   The stored brief is the model's account of what the guest wants, written
   once when the handoff opened and read by the WhatsApp alert and this screen
   alike. It is what stops a staff member opening with "hello?" and making
   somebody repeat a complaint they have already made, so it stays — it simply
   stops being nailed to the top of the screen.

   In the thread it scrolls like everything else: there when you look for it,
   gone while you are reading the conversation. Centred and quiet, the same way
   the date separators read, because it is an annotation rather than something
   anybody actually said. */
.handoff-note{align-self:center;max-width:min(92%,560px);margin:2px 0 12px;
  padding:9px 13px;border-radius:12px;background:rgba(255,255,255,.82);
  border:1px solid var(--line);text-align:center}
.handoff-note strong{display:block;font-size:10px;letter-spacing:.05em;
  text-transform:uppercase;color:var(--muted);margin-bottom:3px}
.handoff-note span{display:block;font-size:13px;line-height:1.45;color:var(--ink)}

/* ── WHAT THE GUEST SENT ────────────────────────────────────────────────────
   A payment screenshot, most often, and the reason the desk opened the thread
   at all. Sized so it reads at a glance without a tap — a proof-of-payment
   thumbnail nobody can read is the same as no thumbnail. Tapping opens the
   full image in a new tab via the signed link. */
/* THE BOX EXISTS BEFORE THE PICTURE DOES.
   The <img> is rendered with no src — loadThreadMedia fetches each photo
   afterwards and assigns it — so until the bytes arrive the element is ZERO
   pixels tall. Every photo in a thread then grows the pane AFTER the scroll
   has been set, which is the desk report: open a chat and it settles part-way
   up, further up the more pictures the conversation has.
   aspect-ratio gives the element a definite height from the first paint, so
   the pane is its true length before a single byte of image has loaded and
   the scroll lands where it was aimed. object-fit:cover then fills that box
   whatever shape the photo turns out to be, so nothing moves when it does
   arrive. This is also why WhatsApp reserves a thumbnail box rather than
   letting the bubble reflow. */
.bubble-media{display:block;width:100%;max-width:260px;aspect-ratio:4/3;max-height:340px;
  object-fit:cover;border-radius:10px;margin-bottom:6px;background:#e8ece9}
/* A voice note is played, not looked at. Sized like the picture box beside
   it so a thread mixing the two does not jump about, and given a definite
   height for the same reason .bubble-media has an aspect-ratio: the element
   exists at its full size before loadThreadMedia fills in the src, so the
   pane is its true length from the first paint. */
.bubble-audio{display:block;width:100%;max-width:260px;height:40px;
  margin-bottom:6px}
@media(max-width:760px){.bubble-audio{max-width:220px}}
.bubble-media-missing{display:block;padding:22px 14px;margin-bottom:6px;
  border:1px dashed var(--line);border-radius:10px;text-align:center;
  font-size:12px;color:var(--muted)}
@media(max-width:760px){.bubble-media{max-width:220px}}

/* ── NOTHING A THUMB CAN FOCUS IS UNDER 16px ────────────────────────────────
   The owner: "the moment you tap the textbox, everything zooms up, then it
   becomes impossible to read and type."

   That is not a layout bug and it is not something the page chose to do. iOS
   Safari automatically zooms the viewport whenever a focused text control has
   a computed font-size below 16px — it is a legibility feature, and the page
   cannot cancel it after the fact. `user-scalable=no` would suppress it and is
   the wrong answer: it also takes pinch-zoom away from anybody who needs it,
   which Safari now ignores for exactly that reason.

   The composer was 15px. One pixel, and it zoomed the whole conversation on
   every single reply.

   So the rule is the class of bug, not the one instance: on a phone, every
   control a person can put a cursor into is at least 16px. This is set LAST in
   the file so nothing later quietly reintroduces the problem, and it is scoped
   to the mobile breakpoint so the denser desktop sizes are untouched. */
@media (max-width: 760px) {
  input:not([type=checkbox]):not([type=radio]),
  textarea,
  select,
  .searchbox,
  .chat-compose textarea,
  .filter {
    font-size: 16px;
  }
}

/* ── Measured fixes, npm run ux:audit ────────────────────────────────────

   Every rule below replaces a number the audit produced, not an opinion:

     .eyebrow            2.62:1 against WCAG 1.4.3’s 4.5. It carries the section
                         label on every screen AND the date on Today, which
                         measured 1.98:1 — the faintest text in the product was
                         the one telling you what day it is.
     --muted             3.80:1 on the canvas. Used for nearly every secondary
                         line in the app.
     --amber             3.29:1 inside its own pill, so "Payment due" and
                         "held" — the two states that cost money — were the
                         hardest to read.
     .nav-item           40px tall. Fitts’s Law and WCAG 2.5.5 both want 44,
                         and this is the control every session starts with.
     .quiet              33px. "Next month", "Check now", "Refresh" — the
                         small print of the interface, and the hardest to hit.
     .icon-button        40x40, including "Account options", which is the way
                         out of the app.
     select              37–41px.

   Sizes are min-heights, so nothing that is already bigger shrinks. */

.nav-item{min-height:44px}
.quiet,.secondary,.primary{min-height:44px}
.icon-button,.menu{width:44px;height:44px}
select,.filter,.field select,.field input{min-height:44px}

/* A pill is not a target, but it IS the thing a person reads to decide what
   to do next, so it holds itself to the same contrast as body text. */
.status.amber{color:var(--amber)}

/* THE CALENDAR DAY WAS 19px TALL. A month grid cell is a booking control —
   tapping one starts a stay — and at nineteen pixels it is a control only a
   mouse can use. The night grid beside it already stands at 46. */
.calendar-days [data-calendar-date]{min-height:44px}

/* ── Second pass, from the same audit ─────────────────────────────────────

     .nav-label              4.12:1 on the sidebar green. The section headings
                             a person scans to find a screen. #8fb0a6 is 5.09.
     .calendar-view-option   38px. This is the control that switches between
                             the month grid and the night grid — the one the
                             owner had to find to see the new board at all.
     .calendar-month-trigger 42px, two short of the line.
     .icon-button            still 40x40 where a later rule overrode the
                             earlier fix; "Account options" is the way out of
                             the app and the smallest thing on the screen.
     health check rows       24px tall and focusable, so keyboard and touch
                             both land on a target the height of the text.

   Every number above came out of npm run ux:audit, not out of an eye. */

.calendar-view-option{min-height:44px}
/* 154x33 on the Today banner — the first button the owner sees after signing
   in, and the only one on that screen. It was already given min-height:44px
   in the narrow layout and left short everywhere else, which is the same rule
   applied to one viewport and forgotten in the other. */
.notice button{min-height:44px}
.calendar-month-trigger{min-height:44px}
.top-actions .icon-button,.icon-button{min-width:44px;min-height:44px}

/* A row that can be focused is a row that can be tapped, and it should be
   big enough to tap on purpose. */
.health-check,[data-check],.check-row{min-height:44px}

/* ── Third pass. Every selector below came from the audit naming it ───────

   The first two passes wrote CSS at classes guessed from on-screen text, and
   some of those classes did not exist. Making the audit print the element
   alongside the measurement ended that: each rule here is aimed at a
   selector the tool reported.

     .profile button          40x40 — "Account options", the way out of the
                              app, and the smallest control in it.
     .detail-row.tappable     1084x24. Focusable rows on Health and in the
                              guest panel: full width, and the height of the
                              text inside them.
     .searchbox               40px.
     --red as text            4.36:1. It marks the things that are wrong.
     --muted                  4.43:1 over the tinted status pills.

   Contrast fixes change only the TEXT tokens. A fill has no contrast duty,
   so --red, --gold and --amber keep their values and the brand is unchanged. */

.profile button{min-width:44px;min-height:44px;border-radius:10px}
.detail-row.tappable{min-height:44px;align-items:center}
.searchbox{min-height:44px}

/* Red used as text, never as a fill. */
.down,.metric-foot.warn,.status.red{color:var(--red-ink)}

/* ── Fourth pass ──────────────────────────────────────────────────────────

     --red as text     4.36:1, on "0/1" and on the room name in a sold-out
                       row. It is the colour this interface uses to say
                       something is wrong, so it was the least readable text
                       exactly where reading matters.

     .conversation-search .searchbox
                       height:40px AND min-height:40px, set deliberately for
                       the WhatsApp-shaped inbox. More specific than the
                       earlier fix, so it won. It is the search box above the
                       conversation list and it is tapped with a thumb, so it
                       goes to 44 like everything else. */

.conversation-search .searchbox{height:44px;min-height:44px}

/* ── Mobile, where a target is hit with a thumb ───────────────────────────

   The desktop passes fixed every HEIGHT. At 390px the same controls shed
   their labels to fit, and their WIDTH collapsed instead:

     26x44  .quiet                  "Previous month" / "Next month" become a
                                    bare arrow at 26px wide.
     35x44  .calendar-view-option   the switch between the month grid and the
                                    night grid, with its word hidden.
     42x42  .mobile-page-back       two pixels short.

   Fitts’s Law and WCAG 2.5.5 are about the AREA a finger has to land on, so
   44 applies to both dimensions. A control narrow enough to miss is not made
   safe by being tall. */

@media(max-width:760px){
  .quiet,.calendar-view-option,.mobile-page-back,.icon-button{min-width:44px;min-height:44px}
  /* Centred, because a 26px arrow inside a 44px button that is padded from
     one side is still a 26px target — the box grew and the ink did not. */
  .quiet,.calendar-view-option{display:inline-flex;align-items:center;justify-content:center}
}

/* ── The last two, both real ──────────────────────────────────────────────

     44x42  .mobile-page-back   two pixels short; an explicit height was
                                beating the min-height set earlier.

     38x44  the month grid’s day cells. Tapping one starts a booking, so it
                                is a control, and 38px is a control a thumb
                                misses. It was worth checking whether 44
                                could fit before forcing it: seven columns at
                                44 is 308px, plus 28 of padding, against a
                                390px screen. It fits with room to spare, so
                                the narrowness was spacing rather than a
                                constraint — and the sideways-scroll check in
                                the audit is what proves this did not simply
                                push the page wider instead. */

@media(max-width:760px){
  .mobile-page-back{height:44px;min-height:44px}
  .calendar-day-date,.calendar-day-room{min-width:44px;min-height:44px}
}
