/* RoomPilot Admin — design system matching the approved KB mockups.
 * Light surfaces, RoomPilot brand blue accent, clean grotesk type.
 * Token-driven; KB component classes mirror the brainstorm mockups
 * (room rail + item cards, two-column item detail + timeline, Q&A + queue).
 */
@import url('https://fonts.googleapis.com/css2?family=IBM+Plex+Mono:wght@400;500;600&family=Outfit:wght@400;450;500;600;700;800&display=swap');

/* ============ THEME TOKENS — copied verbatim from report.roompilot.com ============ */
:root {
  /* report.roompilot LIGHT (mapped to admin semantics: --bg-primary=cards, --bg-secondary=page) */
  --bg-primary:   #ffffff;   /* cards / panels */
  --bg-secondary: #f8fafc;   /* page */
  --bg-tertiary:  #f1f5f9;
  --bg-hover:     #e2e8f0;
  --text-primary: #0f172a;
  --text-secondary:#475569;
  --text-muted:   #64748b;
  --text-faint:   #94a3b8;
  --accent:       #2563eb;
  --accent-hover: #1d4ed8;
  --accent-light: #3b82f6;
  --accent-bg:    #2563eb14;
  --brand-deep:   #1d4ed8;
  --positive: #059669; --positive-bg: #05966914;
  --negative: #dc2626; --negative-bg: #dc262614;
  --warn:     #d97706; --warn-bg: #d9770614;
  --border:   #00000014;
  --border-strong: #00000026;
  --shadow-sm: 0 1px 2px #0000000d;
  --shadow-md: 0 4px 12px #00000014;
  --radius: 14px;
  --sans: 'Outfit', -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif;
  --mono: 'IBM Plex Mono', ui-monospace, SFMono-Regular, Menlo, monospace;
  /* Tells the browser which palette its NATIVE UI (scrollbars, date pickers, select popups,
     form controls) should use, per theme. Without it the dark admin scrolled with a glaring
     light scrollbar and popped light-grey native widgets. */
  color-scheme: light;
}

* { box-sizing: border-box; margin: 0; padding: 0; }

body {
  font-family: var(--sans);
  background-color: var(--bg-secondary);
  color: var(--text-primary);
  line-height: 1.5;
  -webkit-font-smoothing: antialiased;
  text-rendering: optimizeLegibility;
  min-height: 100vh;
  transition: background-color .2s, color .2s;
}

/* report.roompilot DARK (.dark) — applied via html[data-theme="dark"]; the head
 * script in _layout.html sets data-theme from localStorage. Cards (--bg-primary)
 * sit raised above the deep page (--bg-secondary). */
html[data-theme="dark"] {
  --bg-primary:   #0f1629;
  --bg-secondary: #0a0f1a;
  --bg-tertiary:  #162036;
  --bg-hover:     #1e2d4a;
  --text-primary: #f1f5f9;
  --text-secondary:#94a3b8;
  --text-muted:   #64748b;
  --text-faint:   #475569;
  --accent:       #3b82f6;
  --accent-hover: #60a5fa;
  --accent-light: #60a5fa;
  --accent-bg:    #3b82f626;
  --brand-deep:   #60a5fa;
  --positive: #34d399; --positive-bg: #34d39926;
  --negative: #f87171; --negative-bg: #f8717126;
  --warn:     #fbbf24; --warn-bg: #fbbf2426;
  --border:   #ffffff14;
  --border-strong: #ffffff26;
  --shadow-sm: 0 1px 2px #0000004d;
  --shadow-md: 0 4px 12px #0006;
  color-scheme: dark;
}
/* theme toggle: show the option you'd switch TO */
.rp-th-light { display:none; }
html[data-theme="dark"] .rp-th-dark { display:none; }
html[data-theme="dark"] .rp-th-light { display:inline; }

/* Make `hidden` mean hidden.

   The UA sheet's `[hidden] { display:none }` is the WEAKEST rule there is - any author
   declaration of `display` beats it, including an inline one. So `<div id="ppNew" hidden
   style="display:flex">` (the Requests page's "a request came in while you were here" banner)
   rendered ALWAYS, and every banner.setAttribute('hidden','') / removeAttribute('hidden') in its
   poller was inert: the banner sat there permanently announcing a request that had long since
   been dealt with, over an empty Requested tab. passport.css has carried this exact guard for the
   same reason since the guest pass shipped; admin.css never did. */
[hidden] { display: none !important; }

/* ============ REDESIGN SHARED LAYER (Passport + Kross) ============ */
@keyframes rpRise { from { opacity:0; transform:translateY(10px); } to { opacity:1; transform:none; } }
@keyframes rpFade { from { opacity:0; } to { opacity:1; } }
@keyframes rpPop { 0% { opacity:0; transform:scale(.94); } 60% { transform:scale(1.01); } 100% { opacity:1; transform:scale(1); } }
@keyframes rpToast { 0% { opacity:0; transform:translate(-50%,14px); } 12% { opacity:1; transform:translate(-50%,0); } 88% { opacity:1; transform:translate(-50%,0); } 100% { opacity:0; transform:translate(-50%,8px); } }
/* `backwards`, NOT `both`. With `both` the final keyframe keeps applying forever, and a keyframed
   `transform:none` resolves to an IDENTITY MATRIX rather than the keyword `none` - which still
   establishes a containing block for fixed-position descendants. That silently turned every
   `position:fixed` overlay nested inside an animated wrapper into `position:absolute` against a
   page-tall box: the Stay-context modal centred itself ~1200px down the DOCUMENT while the body
   scroll was locked, so it read as "blocks the screen, only half of it, can't scroll".
   `backwards` still holds the FROM state during the delay (which is all the stagger needs) and
   drops back to the base style - opacity:1, no transform - the moment the animation ends. */
.rp-anim > * { animation:rpRise .4s cubic-bezier(.2,.7,.2,1) backwards; }
.rp-anim > *:nth-child(2){animation-delay:.04s} .rp-anim > *:nth-child(3){animation-delay:.08s}
.rp-anim > *:nth-child(4){animation-delay:.12s} .rp-anim > *:nth-child(5){animation-delay:.16s}
.rp-anim > *:nth-child(6){animation-delay:.20s} .rp-anim > *:nth-child(7){animation-delay:.24s}
/* Scroll UI follows the theme EVERYWHERE - the page, the sidebar and every inner scroller -
   not just the boxes that remembered to add .rp-sc. scrollbar-color covers Firefox (and new
   Chromium); the -webkit rules cover the rest; color-scheme above keys the overlay style. */
html { scrollbar-color: var(--border-strong) transparent; }
::-webkit-scrollbar { width:10px; height:10px; }
::-webkit-scrollbar-track { background:transparent; }
::-webkit-scrollbar-corner { background:transparent; }
::-webkit-scrollbar-thumb { background:var(--border-strong); border-radius:8px; border:3px solid transparent; background-clip:padding-box; }
::-webkit-scrollbar-thumb:hover { background:var(--text-faint); border:3px solid transparent; background-clip:padding-box; }
.rp-toast { position:fixed; left:50%; bottom:26px; transform:translateX(-50%); z-index:2000; background:var(--text-primary); color:var(--bg-primary); font-size:13px; font-weight:600; padding:11px 18px; border-radius:11px; box-shadow:var(--shadow-md); animation:rpToast 2.4s ease forwards; pointer-events:none; }
.rpx-hero { position:relative; overflow:hidden; border-radius:16px; color:#eaf1ff; box-shadow:var(--shadow-sm); background:linear-gradient(162deg,#0a1740 0%,#0d2a73 58%,#1147b0 100%); }
.rpx-hero::before { content:''; position:absolute; right:-60px; top:-70px; width:240px; height:240px; border-radius:50%; background:radial-gradient(circle, rgba(125,166,232,.30), transparent 70%); pointer-events:none; z-index:0; }
.rpx-hero > * { position:relative; z-index:1; }
.rpx-hero-eyebrow { font-size:11px; font-weight:700; letter-spacing:0.16em; text-transform:uppercase; color:#7fa6e8; }
.rpx-hero h1 { color:#fff; font-size:26px; font-weight:700; letter-spacing:-0.025em; margin:6px 0 0; }
.rpx-hero p { color:#b9cdfb; font-size:13.5px; margin:8px 0 0; max-width:64ch; line-height:1.55; }
.ppl input::placeholder { color:#9bb4e8; }
/* white ghost chip on the blue hero: the booking view's contact cluster (call/copy/WhatsApp/thread) */
.ppl-ghost { display:inline-flex; align-items:center; gap:6px; padding:6px 11px; border-radius:9px;
             border:1px solid rgba(255,255,255,.28); background:rgba(255,255,255,.10); color:#fff;
             font-size:12px; font-weight:600; cursor:pointer; text-decoration:none; white-space:nowrap;
             transition:background .14s ease; }
.ppl-ghost:hover { background:rgba(255,255,255,.20); color:#fff; }
@media (prefers-reduced-motion: reduce) { .rp-anim > *, .rp-toast { animation:none; } }
/* The Kross inbox's small-screen layout used to live here, in !important, fighting the
   stylesheet that actually owns those classes. It is all in kross.css now - which is loaded
   only on the pages that have a .km-split, and loaded AFTER this file, so it wins on its own
   without the shouting. */

/* The apartment strip on a multi-flat booking. Flat names run long ("The Queensborough 3 Bed
   Apartment By Hyde Park"), and a single un-shrinkable flex item wider than the phone made the
   WHOLE PAGE scroll sideways - so the tabs dragged the content out from under the reader.
   Below, the strip scrolls inside itself and the page does not scroll at all. */
.rpx-legs { scrollbar-width: thin; }
@media (max-width: 760px) {
  .rpx-legs { flex-wrap: nowrap !important; overflow-x: auto; -webkit-overflow-scrolling: touch; }
  .rpx-legs > * { flex: 0 0 auto; }
  .rpx-legs .rpx-leg-name { max-width: 42vw; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
}

h1, h2, h3, h4 { font-family: var(--sans); font-weight: 600; letter-spacing: -0.01em; line-height: 1.25; color: var(--text-primary); }
a { color: var(--accent); text-decoration: none; transition: color .14s ease; }
a:hover { color: var(--accent-hover); }
::selection { background: var(--accent-bg); color: var(--accent-hover); }
.rp-muted { color: var(--text-muted); }

/* ============ LOGIN (brand-blue splash + form) ============ */
.rp-login { display: grid; grid-template-columns: 5fr 4fr; min-height: 100vh; }
@media (max-width: 768px) { .rp-login { grid-template-columns: 1fr; } .rp-login-brand { min-height: 200px; padding: 32px 24px; } }
.rp-login-brand {
  position: relative; overflow: hidden; color: #fff; padding: 60px 52px;
  display: flex; flex-direction: column;
  background:
    radial-gradient(120% 90% at 80% -10%, rgba(255,255,255,0.18), transparent 55%),
    linear-gradient(150deg, var(--brand-deep) 0%, var(--accent) 60%, #4f7ef0 100%);
}
.rp-login-brand::before { content:''; position:absolute; top:-38%; right:-22%; width:420px; height:420px; background:radial-gradient(circle, rgba(255,255,255,0.16), transparent 62%); pointer-events:none; }
.rp-login-brand-logo { display:flex; align-items:center; gap:12px; font-weight:700; font-size:18px; letter-spacing:-0.01em; z-index:1; }
.rp-logo-mark { width:38px; height:38px; object-fit:contain; display:block; border-radius:10px; }
.rp-login-brand-title { font-size:40px; font-weight:800; letter-spacing:-0.03em; line-height:1.1; margin:auto 0 16px 0; z-index:1; }
.rp-login-brand-title em { font-style:normal; opacity:0.72; }
.rp-login-brand-desc { color:rgba(255,255,255,0.78); font-size:14.5px; line-height:1.65; max-width:380px; z-index:1; }
.rp-login-form-panel { padding:60px 52px; display:flex; flex-direction:column; justify-content:center; background:var(--bg-primary); }
.rp-login-form-header h2 { font-size:25px; }
.rp-login-form-header p { color:var(--text-muted); font-size:14px; margin-top:6px; margin-bottom:30px; }

/* ============ FIELDS / BUTTONS ============ */
.rp-field { margin-bottom:16px; }
.rp-field label { display:block; font-size:12px; font-weight:600; color:var(--text-secondary); margin-bottom:6px; }
.rp-input { width:100%; padding:11px 14px; font-size:14px; font-family:inherit; background:var(--bg-secondary); border:1px solid var(--border-strong); border-radius:10px; color:var(--text-primary); outline:none; transition:border-color .14s ease, box-shadow .14s ease, background .14s ease; }
.rp-input::placeholder { color:var(--text-faint); }
.rp-input:focus { border-color:var(--accent); background:var(--bg-primary); box-shadow:0 0 0 3px var(--accent-bg); }
.rp-btn { display:inline-flex; align-items:center; justify-content:center; gap:8px; padding:10px 18px; font-size:13.5px; font-weight:600; font-family:inherit; border-radius:10px; border:1px solid transparent; cursor:pointer; transition:transform .12s ease, background .14s ease, box-shadow .14s ease, border-color .14s ease; }
.rp-btn:active { transform:translateY(1px); }
.rp-btn-primary { background:var(--accent); color:#fff; box-shadow:var(--shadow-sm); }
.rp-btn-primary:hover { background:var(--accent-hover); }
.rp-btn-secondary { background:var(--bg-primary); color:var(--text-primary); border-color:var(--border-strong); }
.rp-btn-secondary:hover { background:var(--bg-secondary); }
.rp-btn-danger { background:var(--negative-bg); color:var(--negative); }
.rp-btn-danger:hover { background:#fbd5d0; }
.rp-btn-w-full { width:100%; }
.rp-flash { padding:11px 15px; border-radius:10px; background:var(--accent-bg); color:var(--accent-hover); font-size:13.5px; margin-bottom:14px; border:1px solid rgba(30,80,216,0.18); }
.rp-flash-success { background:var(--positive-bg); color:var(--positive); border-color:rgba(31,122,68,0.2); }
.rp-flash-error { background:var(--negative-bg); color:var(--negative); border-color:rgba(194,59,48,0.22); }
.rp-flash-info { background:var(--bg-tertiary); color:var(--text-secondary); border-color:var(--border); }

/* ============ APP SHELL ============ */
/* 260, not 248: the icon column costs each row 26px, and "Deposits (Test) SANDBOX" - the
   longest label in the rail carrying the widest tag - was the one row with less slack than
   that, so it wrapped onto two lines. Twelve more pixels clear it with room to spare, and
   .rp-nav-item is nowrap below so no future label can quietly do the same thing again. */
.rp-app { display:grid; grid-template-columns:260px 1fr; min-height:100vh; }
/* The VERTICAL padding lives on the two sticky children, not on this scroller, and that is
   load-bearing. Blink clamps a sticky box to its containing block - the scroller's CONTENT
   box - rather than to the scrollport, so `padding:24px 16px 18px` here left a 24px strip
   above the pinned brand and an 18px strip below the pinned footer that neither sticky
   background could reach. Nav items scrolled straight through both gaps in the open: half a
   Search button floating above the logo, half a Passport row under Sign out. Moving the
   padding into the children puts both pinned blocks flush against the scrollport edges, so
   their opaque backgrounds cover the full height. Spacing is unchanged - the 24px and 18px
   are simply paid by the brand and the footer instead (26 = 24+2, 22 = 18+4). */
.rp-sidebar { background:var(--bg-primary); border-right:1px solid var(--border); padding:0 16px; display:flex; flex-direction:column; position:sticky; top:0; height:100vh; overflow-y:auto; scrollbar-width:thin; }
.rp-sidebar-brand { display:flex; align-items:center; gap:11px; padding:26px 8px 18px; font-weight:800; font-size:17px; letter-spacing:-0.02em; position:sticky; top:0; z-index:4; background:var(--bg-primary); border-bottom:1px solid transparent; transition:border-color .18s ease; }
/* The pinned brand earns an edge only once there is something behind it. A gradient tail was
   the first attempt and it was worse than the bug it fixed: a half-dissolved Messages row
   ghosting under the logo is still an element showing through. A hairline instead - the same
   one the footer already draws - so a row passing under the brand is cut by a line that
   plainly belongs there. The border is transparent from the start rather than added on
   demand, so nothing shifts by a pixel when it appears; rpadmin.js sets .is-scrolled off the
   rail's own scrollTop. */
.rp-sidebar.is-scrolled .rp-sidebar-brand { border-bottom-color:var(--border); }
.rp-sidebar-brand .rp-logo-mark { width:30px; height:30px; border-radius:9px; }
.rp-nav-toggle { display:none; }  /* mobile-only hamburger; shown in the max-width:768px block */
.rp-nav-section { font-size:10px; font-weight:700; letter-spacing:0.14em; color:var(--text-faint); text-transform:uppercase; padding:16px 8px 6px; }
.rp-nav-item { position:relative; display:flex; align-items:center; gap:10px; padding:9px 12px; border-radius:9px; font-size:13.5px; font-weight:500; color:var(--text-secondary); margin:1px 0; white-space:nowrap; transition:background .14s ease, color .14s ease; }
/* The icon column. Stroke, size and colour are declared once here and inherit into every
   <use>d symbol, so the sprite in _layout.html carries geometry and nothing else. currentColor
   is what makes an icon follow its own row from rest to hover to active without a single
   extra rule. flex:0 0 16px so a long label can never squeeze the glyph out of round. */
.rp-nav-ic { flex:0 0 16px; width:16px; height:16px; fill:none; stroke:currentColor;
             stroke-width:1.7; stroke-linecap:round; stroke-linejoin:round; }
/* --bg-secondary is the PAGE colour, one step DARKER than this rail in dark mode (#0a0f1a
   under #0f1629) - so hovering a row punched a hole in the sidebar instead of lifting it,
   and dark is the default theme. --bg-tertiary is the token that moves the right way in
   both: lighter than the panel in dark, darker than it in light. */
.rp-nav-item:hover { background:var(--bg-tertiary); color:var(--text-primary); }
.rp-nav-item:focus-visible { outline:2px solid var(--accent); outline-offset:-2px; }
/* One signal, not four. The active row carried a tinted pill AND accent text AND a 3px rail
   bar AND a jump from weight 500 to 700 - the bar cutting across the pill's own rounded
   corner, and the weight jump reflowing the label wider than its neighbours. Pill + accent +
   600 is already unmistakable. That the bar was hidden on mobile from day one was the tell
   that nothing depended on it. */
.rp-nav-item.active { background:var(--accent-bg); color:var(--accent); font-weight:600; }
/* One ROW of actions, not two stacked full-width blocks. Two 34px bordered buttons on top of
   each other made the account block the heaviest thing in the rail, for the two controls
   used least often. The theme switch only ever needed its glyph, so it becomes a square and
   Sign out takes the rest of the line - which is also the shape the phone header already
   used, so both breakpoints now share one layout. */
.rp-sidebar-footer { margin-top:auto; padding:12px 8px 22px; border-top:1px solid var(--border); position:sticky; bottom:0; z-index:4; background:var(--bg-primary); display:flex; flex-wrap:wrap; align-items:center; gap:7px; }
.rp-sidebar-user { flex:0 0 100%; display:flex; align-items:center; gap:10px; padding:6px; }
/* Ghost buttons, not .rp-btn-secondary: that class fills with --bg-primary, which IS the
   footer's own colour (so the fill was invisible), and hovers to --bg-secondary, which is
   the same dent the nav rows had. Transparent over a hairline reads lighter and hovers the
   right way in both themes. */
.rp-side-act { flex:0 0 auto; display:inline-flex; align-items:center; justify-content:center; gap:7px;
               height:34px; padding:0 12px; font:inherit; font-size:12.5px; font-weight:600;
               border:1px solid var(--border-strong); border-radius:9px; background:transparent;
               color:var(--text-secondary); cursor:pointer;
               transition:background .14s ease, color .14s ease, border-color .14s ease; }
.rp-side-act:hover { background:var(--bg-tertiary); color:var(--text-primary); border-color:var(--text-faint); }
.rp-side-act:focus-visible { outline:2px solid var(--accent); outline-offset:2px; }
.rp-side-act svg { display:block; }
.rp-side-icon { width:34px; padding:0; }                    /* glyph only - the theme switch */
.rp-sidebar-footer form { flex:1; margin:0; }
.rp-sidebar-footer form .rp-side-act { width:100%; }
.rp-avatar { width:34px; height:34px; border-radius:50%; background:linear-gradient(140deg, var(--accent), #6c8cf5); color:#fff; font-weight:700; font-size:13px; display:flex; align-items:center; justify-content:center; }
.rp-sidebar-user .rp-name { font-size:13px; font-weight:600; color:var(--text-primary); display:block; }
.rp-sidebar-user .rp-role { font-size:11px; color:var(--text-muted); }
.rp-cmdk-trigger { display:flex; align-items:center; gap:8px; width:100%; padding:8px 12px; margin:0 0 6px; font-size:12.5px; font-weight:500; border-radius:9px; border:1px solid var(--border); background:var(--bg-secondary); color:var(--text-muted); cursor:pointer; }
.rp-cmdk-trigger:hover { color:var(--text-secondary); border-color:var(--border-strong); }
.rp-cmdk-kbd { margin-left:auto; font-family:var(--mono); font-size:10px; border:1px solid var(--border-strong); border-radius:5px; padding:1px 5px; color:var(--text-faint); }
.rp-nav-badge { margin-left:auto; min-width:17px; height:17px; padding:0 5px; border-radius:999px; background:var(--negative); color:#fff; font-size:10px; font-weight:700; letter-spacing:-0.02em; font-variant-numeric:tabular-nums; display:inline-flex; align-items:center; justify-content:center; }

/* min-width:0 is load-bearing: as a grid item its default min-width:auto resolves to
 * min-content, so any wide inner block (the 880-1000px scroll tables on settings,
 * requests, links, analytics, kross/reservations) would stretch the 1fr track past the
 * viewport and scroll the whole page sideways instead of scrolling inside its own box. */
.rp-content { padding:32px 38px; min-width:0; }
.rp-content > * { animation: rp-rise .45s cubic-bezier(.2,.7,.2,1) backwards; }   /* see .rp-anim: `both` broke position:fixed */
.rp-content > *:nth-child(2){animation-delay:.04s} .rp-content > *:nth-child(3){animation-delay:.08s} .rp-content > *:nth-child(4){animation-delay:.12s}
@keyframes rp-rise { from { opacity:0; transform:translateY(10px); } to { opacity:1; transform:none; } }
@media (prefers-reduced-motion: reduce) { .rp-content > * { animation:none; } }

.rp-hero, .rp-page-head { display:flex; justify-content:space-between; align-items:flex-end; gap:16px; margin-bottom:24px; }
.rp-hero h1, .rp-page-head h1 { font-size:30px; letter-spacing:-0.03em; }
.rp-page-head p { color:var(--text-muted); font-size:14px; margin-top:5px; }
.rp-greeting { font-size:13px; color:var(--text-muted); display:block; margin-bottom:2px; }
.rp-hero-sub { font-size:13px; color:var(--text-muted); margin-top:4px; }

.rp-metrics { display:grid; grid-template-columns:repeat(3,1fr); gap:16px; margin-bottom:24px; }
.rp-metric { background:var(--bg-primary); border:1px solid var(--border); border-radius:var(--radius); padding:18px 20px; box-shadow:var(--shadow-sm); display:flex; flex-direction:column; gap:4px; }
.rp-metric .v { font-size:28px; font-weight:800; letter-spacing:-0.02em; }
.rp-metric .k { font-size:12px; color:var(--text-muted); }

.rp-card { background:var(--bg-primary); border:1px solid var(--border); border-radius:var(--radius); overflow:hidden; box-shadow:var(--shadow-sm); }
/* A combobox dropdown (.rp-combo-list) is absolutely positioned and would be clipped by the card's
   overflow:hidden (used for the rounded corners). While a dropdown is OPEN (its list is not [hidden]),
   let the card overflow and lift it above following cards so the list shows in full; clipping and the
   default stacking return the moment it closes. Fixes the reservation/property pickers, catalogue
   price picker, deposit forms, etc. in one place. */
.rp-card:has(.rp-combo-list:not([hidden])) { overflow:visible; position:relative; z-index:20; }
.rp-card-head { display:flex; align-items:center; justify-content:space-between; padding:15px 20px; border-bottom:1px solid var(--border); }
.rp-card-head h3 { font-size:15px; }
.rp-collapsible { padding:0; }
.rp-collapsible-head { cursor:pointer; list-style:none; user-select:none; }
.rp-collapsible-head::-webkit-details-marker { display:none; }
.rp-collapsible-head::marker { content:''; }
.rp-collapsible-meta { margin-left:auto; font-size:11px; color:var(--text-muted); text-transform:uppercase; letter-spacing:0.04em; padding:2px 9px; background:var(--bg-tertiary); border-radius:999px; }
.rp-collapsible-chevron { margin-left:12px; color:var(--text-muted); transition:transform .18s ease; flex-shrink:0; }
.rp-collapsible[open] .rp-collapsible-chevron { transform:rotate(180deg); }
.rp-activity-row { display:flex; align-items:flex-start; gap:12px; padding:13px 20px; border-bottom:1px solid var(--border); font-size:13.5px; }
.rp-activity-row:last-child { border-bottom:none; }
.rp-activity-row .rp-when { margin-left:auto; font-size:11px; color:var(--text-faint); white-space:nowrap; padding-left:16px; }
.rp-badge { display:inline-block; padding:2px 9px; border-radius:999px; font-size:11px; font-weight:600; }
.rp-badge-info { background:var(--accent-bg); color:var(--accent); }
.rp-badge-positive { background:var(--positive-bg); color:var(--positive); }
.rp-badge-negative { background:var(--negative-bg); color:var(--negative); }
.rp-badge-warn { background:var(--warn-bg); color:var(--warn); }
.rp-badge-danger { background:var(--negative-bg); color:var(--negative); }
.rp-stat-num { font-size:26px; font-weight:800; letter-spacing:-0.01em; }
.rp-stat-lbl { font-size:11px; text-transform:uppercase; letter-spacing:0.08em; color:var(--text-muted); margin-top:2px; }
.rp-field-label { font-size:12px; font-weight:600; margin-bottom:6px; color:var(--text-secondary); }
.rp-req { color:var(--accent); }

/* ============ KB: crumb, page head, hub tiles ============ */
.rp-kb-crumb { font-size:12.5px; color:var(--text-muted); margin:0 0 14px; }
.rp-kb-crumb a { color:var(--accent); }
.rp-kb-prop-head { display:flex; align-items:flex-start; justify-content:space-between; gap:14px; }
.rp-kb-search { display:flex; gap:10px; margin:18px 0 26px; }
.rp-kb-search .rp-input { flex:1; padding:15px 18px; font-size:16px; border-radius:12px; }
.rp-kb-grid { display:grid; grid-template-columns:repeat(auto-fill, minmax(248px,1fr)); gap:16px; }
.rp-kb-tile { display:block; text-decoration:none; color:inherit; background:var(--bg-primary); border:1px solid var(--border); border-radius:var(--radius); padding:18px; box-shadow:var(--shadow-sm); transition:transform .16s ease, box-shadow .16s ease, border-color .16s ease; }
.rp-kb-tile:hover { transform:translateY(-3px); box-shadow:var(--shadow-md); border-color:var(--accent); }
.rp-kb-tile-name { font-size:16px; font-weight:800; margin-bottom:4px; }
.rp-kb-tile-addr { font-size:12.5px; color:var(--text-muted); margin-bottom:14px; }
.rp-kb-tile-stats { display:flex; align-items:center; gap:12px; font-size:12px; color:var(--text-muted); border-top:1px solid var(--border); padding-top:12px; }
.rp-kb-cov { margin-left:auto; font-weight:800; color:var(--accent); }
.rp-empty { text-align:center; color:var(--text-muted); padding:32px; background:var(--bg-primary); border:1px dashed var(--border-strong); border-radius:var(--radius); }

/* ============ KB: property = room rail + item cards (mockup A) ============ */
.rp-kb-layout { display:flex; background:var(--bg-primary); border:1px solid var(--border); border-radius:var(--radius); overflow:hidden; box-shadow:var(--shadow-sm); }
.rp-kb-rail { width:210px; flex:none; border-right:1px solid var(--border); padding:14px 10px; background:var(--bg-secondary); display:flex; flex-direction:column; gap:3px; }
.rp-kb-rail-h { font-size:10px; font-weight:700; letter-spacing:0.12em; text-transform:uppercase; color:var(--text-faint); padding:4px 10px 8px; }
.rp-kb-rail-item { display:flex; align-items:center; justify-content:space-between; gap:8px; font-size:13px; font-weight:600; padding:8px 11px; border-radius:8px; color:var(--text-secondary); text-decoration:none; cursor:pointer; }
.rp-kb-rail-item:hover { background:var(--bg-tertiary); color:var(--text-primary); }
.rp-kb-rail-item.active { background:var(--accent-bg); color:var(--accent); }
.rp-kb-rail-item .n { font-size:10px; font-weight:700; color:var(--text-faint); }
.rp-kb-rail-add { margin-top:8px; }
.rp-kb-main { flex:1; min-width:0; padding:20px 22px; }
.rp-kb-roomsec { margin-bottom:26px; scroll-margin-top:20px; }
.rp-kb-roomsec:last-child { margin-bottom:0; }
.rp-kb-roomsec-head { display:flex; align-items:center; gap:10px; margin-bottom:12px; }
.rp-kb-roomsec-head h3 { font-size:16px; }
.rp-kb-roomsec-head .rp-kb-room-meta { color:var(--text-muted); font-size:12px; font-weight:600; }
.rp-kb-roomsec-head .spacer { margin-left:auto; }
.rp-kb-icards { display:grid; grid-template-columns:repeat(auto-fill, minmax(230px,1fr)); gap:11px; }
.rp-kb-icard { border:1px solid var(--border); border-radius:11px; padding:12px; display:flex; gap:11px; text-decoration:none; color:inherit; background:var(--bg-primary); transition:border-color .15s ease, box-shadow .15s ease, transform .15s ease; }
.rp-kb-icard:hover { border-color:var(--accent); box-shadow:var(--shadow-md); transform:translateY(-2px); }
.rp-kb-ithumb { width:46px; height:46px; border-radius:8px; background:linear-gradient(135deg,#eaeefb,#d7e0fb); flex:none; object-fit:cover; }
.rp-kb-iname { font-size:13px; font-weight:700; color:var(--text-primary); line-height:1.25; }
.rp-kb-imeta { font-size:11px; color:var(--text-muted); margin-top:2px; }
.rp-kb-ichips { display:flex; gap:5px; margin-top:7px; flex-wrap:wrap; }
.rp-kb-lchip { font-size:10px; font-weight:700; padding:2px 8px; border-radius:8px; background:var(--positive-bg); color:var(--positive); }
.rp-kb-lchip.buy { background:var(--accent-bg); color:var(--accent); }
.rp-chip { font-size:10.5px; font-weight:700; padding:3px 9px; border-radius:999px; background:var(--bg-tertiary); color:var(--text-secondary); }
.rp-chip.cat { background:var(--warn-bg); color:var(--warn); }
.rp-chip.room { background:var(--accent-bg); color:var(--accent); }
.rp-kb-room-actions { display:flex; gap:8px; align-items:center; flex-wrap:wrap; }
.rp-kb-room-actions form { margin:0; display:inline; }
.rp-kb-room-actions .rp-btn { padding:6px 11px; font-size:12px; }
.rp-kb-empty-room { color:var(--text-muted); font-size:13px; padding:10px 0; }

/* ============ KB: item detail (two columns) ============ */
.rp-kb-det { background:var(--bg-primary); border:1px solid var(--border); border-radius:var(--radius); overflow:hidden; box-shadow:var(--shadow-sm); }
.rp-kb-det-top { background:var(--bg-secondary); border-bottom:1px solid var(--border); padding:12px 18px; font-size:12.5px; color:var(--text-muted); display:flex; align-items:center; justify-content:space-between; gap:12px; }
.rp-kb-det-top b { color:var(--text-primary); }
.rp-kb-det-grid { display:grid; grid-template-columns:1.05fr 1fr; }
.rp-kb-det-left { padding:22px; border-right:1px solid var(--border); }
.rp-kb-det-right { padding:22px; background:var(--bg-secondary); }
.rp-kb-itemtitle { font-size:21px; font-weight:800; margin:0 0 10px; }
.rp-kb-badges { display:flex; gap:6px; margin-bottom:16px; flex-wrap:wrap; }
.rp-kb-frow { display:flex; padding:9px 0; border-bottom:1px solid var(--border); font-size:13.5px; }
.rp-kb-frow:last-child { border-bottom:none; }
.rp-kb-fk { width:130px; color:var(--text-muted); font-weight:600; flex:none; }
.rp-kb-fv { color:var(--text-primary); font-weight:600; }
.rp-kb-fv a { color:var(--accent); word-break:break-all; }
.rp-kb-section-h { display:flex; align-items:center; justify-content:space-between; margin:0 0 14px; }
.rp-kb-section-h h3 { font-size:15px; }

/* gallery */
.rp-kb-gallery { display:flex; flex-wrap:wrap; gap:10px; margin:0 0 18px; }
.rp-kb-gimg { width:68px; height:68px; border-radius:9px; object-fit:cover; border:1px solid var(--border); box-shadow:var(--shadow-sm); }
.rp-kb-gadd { width:68px; height:68px; border-radius:9px; background:var(--bg-secondary); border:1.5px dashed var(--border-strong); display:flex; align-items:center; justify-content:center; color:var(--text-faint); font-size:24px; font-weight:300; cursor:pointer; }
.rp-kb-media-tile { width:68px; }
.rp-kb-media-tile button { background:none; border:none; color:var(--accent); cursor:pointer; font:inherit; padding:2px 0 0; font-size:10px; font-weight:600; }
.rp-kb-media-tile form { margin:0; }
.rp-kb-upload { margin:10px 0; display:flex; align-items:center; gap:12px; }

/* timeline */
.rp-kb-timeline { position:relative; padding-left:26px; margin-top:4px; }
.rp-kb-timeline::before { content:''; position:absolute; left:9px; top:5px; bottom:5px; width:2px; background:var(--border); }
.rp-kb-tl-item { list-style:none; position:relative; margin-bottom:18px; }
.rp-kb-tl-item::before { content:''; position:absolute; left:-22px; top:3px; width:14px; height:14px; border-radius:50%; border:3px solid var(--bg-secondary); background:var(--text-muted); box-shadow:0 0 0 2px var(--border-strong); }
.rp-kb-tl-item.t-replaced::before, .rp-kb-tl-item.t-fixed::before { background:var(--negative); box-shadow:0 0 0 2px #f3c6c3; }
.rp-kb-tl-item.t-serviced::before, .rp-kb-tl-item.t-inspected::before { background:var(--accent); box-shadow:0 0 0 2px #c3d2f3; }
.rp-kb-tl-item.t-support_contacted::before { background:var(--warn); box-shadow:0 0 0 2px #f0dcb8; }
.rp-kb-tl-item.t-installed::before, .rp-kb-tl-item.t-painted::before, .rp-kb-tl-item.t-cleaned::before { background:var(--positive); box-shadow:0 0 0 2px #bfe6cd; }
.rp-kb-tl-head { display:flex; align-items:center; gap:8px; flex-wrap:wrap; }
.rp-kb-tl-type { font-size:12.5px; font-weight:800; color:var(--text-primary); text-transform:capitalize; }
.rp-kb-tl-date { font-size:11px; color:var(--text-muted); font-weight:600; }
.rp-kb-tl-title { font-weight:700; margin-top:4px; font-size:13.5px; }
.rp-kb-tl-body { font-size:12.5px; color:var(--text-secondary); margin:4px 0 5px; line-height:1.45; }
.rp-kb-tl-who { display:flex; align-items:center; gap:7px; font-size:11.5px; color:var(--text-muted); }
.rp-kb-cost { font-size:11px; font-weight:800; color:var(--positive); background:var(--positive-bg); padding:1px 8px; border-radius:999px; }
.rp-kb-ava { width:21px; height:21px; border-radius:50%; background:var(--accent); color:#fff; font-size:9px; font-weight:800; display:flex; align-items:center; justify-content:center; }
.rp-kb-tl-actions { display:flex; gap:12px; font-size:11.5px; margin-top:7px; }
.rp-kb-tl-actions form { display:inline; margin:0; }
.rp-kb-tl-actions button { background:none; border:none; color:var(--accent); cursor:pointer; padding:0; font:inherit; font-weight:600; }

/* ============ KB: forms ============ */
.rp-kb-form { max-width:580px; display:flex; flex-direction:column; gap:16px; background:var(--bg-primary); border:1px solid var(--border); border-radius:var(--radius); padding:24px; box-shadow:var(--shadow-sm); }
.rp-kb-field { display:flex; flex-direction:column; gap:6px; font-size:12.5px; font-weight:600; color:var(--text-secondary); }
.rp-kb-field em { color:var(--accent); font-style:normal; }
.rp-kb-form-actions { display:flex; gap:10px; justify-content:flex-end; }
.rp-kb-fieldset { border:1px solid var(--border); border-radius:11px; padding:16px; }
.rp-kb-fieldset legend { font-size:11px; font-weight:700; color:var(--text-muted); padding:0 8px; text-transform:uppercase; letter-spacing:0.06em; }
.rp-kb-extras { border:1px solid var(--border); border-radius:11px; padding:14px; }
.rp-kb-extras legend { font-size:11px; font-weight:700; color:var(--text-muted); padding:0 6px; text-transform:uppercase; letter-spacing:0.06em; }
.rp-kb-extra-row { display:flex; gap:8px; margin-bottom:8px; }

/* ============ KB: lists, deleted, "other items" ============ */
.rp-kb-itemlist { list-style:none; padding:0; margin:12px 0 0; display:flex; flex-direction:column; gap:2px; }
.rp-kb-itemrow { display:flex; align-items:center; gap:9px; font-size:13.5px; padding:8px 11px; border-radius:9px; transition:background .12s ease; }
.rp-kb-itemrow:hover { background:var(--bg-secondary); }
.rp-kb-itemrow a { color:var(--text-primary); font-weight:700; }
.rp-kb-itemrow a:hover { color:var(--accent); }
.rp-kb-other { margin-top:26px; }
.rp-kb-other-head { display:flex; align-items:center; justify-content:space-between; gap:12px; margin-bottom:12px; }
.rp-kb-other-head h2 { font-size:18px; }
.rp-kb-deleted { margin-top:24px; }
.rp-kb-deleted summary { cursor:pointer; color:var(--text-muted); font-size:13px; font-weight:600; }
.rp-kb-deleted-row { display:flex; align-items:center; justify-content:space-between; gap:12px; padding:10px 0; border-bottom:1px solid var(--border); }
.rp-kb-deleted-row form { margin:0; }
.rp-kb-history { margin-top:28px; }

/* ============ KB: Q&A (question + answers, "needs an answer" queue) ============ */
.rp-kb-qa { background:var(--bg-primary); border:1px solid var(--border); border-radius:var(--radius); overflow:hidden; box-shadow:var(--shadow-sm); }
.rp-kb-qa-grid { display:grid; grid-template-columns:1.55fr 1fr; }
.rp-kb-qa-main { padding:20px 22px; border-right:1px solid var(--border); min-width:0; }
.rp-kb-qa-side { padding:18px; background:var(--bg-secondary); }
.rp-kb-stat { display:inline-flex; align-items:center; gap:5px; font-size:11px; font-weight:800; padding:3px 11px; border-radius:999px; }
.rp-kb-stat.answered, .rp-kb-stat.resolved { background:var(--positive-bg); color:var(--positive); }
.rp-kb-stat.open { background:var(--negative-bg); color:var(--negative); }
.rp-kb-qtitle { font-size:19px; font-weight:800; margin:10px 0 8px; line-height:1.25; }
.rp-kb-ctx { display:flex; gap:6px; flex-wrap:wrap; margin-bottom:10px; }
.rp-kb-ctxchip { font-size:10px; font-weight:700; padding:2px 9px; border-radius:999px; background:var(--accent-bg); color:var(--accent); }
.rp-kb-ctxchip.tag { background:var(--bg-tertiary); color:var(--text-muted); }
.rp-kb-qmeta { font-size:11px; color:var(--text-muted); margin-bottom:12px; }
.rp-kb-qbody { font-size:13.5px; color:var(--text-secondary); line-height:1.55; padding-bottom:16px; border-bottom:1px solid var(--border); }
.rp-kb-acount { font-size:13px; font-weight:800; margin:16px 0 12px; }
.rp-kb-answer { padding:14px 0; border-bottom:1px solid var(--border); }
.rp-kb-answer:last-child { border-bottom:none; }
.rp-kb-accepted { background:#f5fbf7; border:1px solid var(--positive); border-radius:11px; padding:14px 16px; }
.rp-kb-accepted-label { font-size:10.5px; font-weight:800; color:var(--positive); margin-bottom:6px; letter-spacing:0.03em; }
.rp-kb-atext { font-size:13.5px; color:var(--text-primary); line-height:1.55; }
.rp-kb-awho { display:flex; align-items:center; gap:7px; font-size:11.5px; color:var(--text-muted); margin-top:9px; }
.rp-kb-addans { margin-top:16px; }
.rp-kb-side-h { font-size:12.5px; font-weight:800; margin:0 0 12px; display:flex; align-items:center; gap:7px; }
.rp-kb-openq { display:block; border:1px solid var(--border); border-radius:10px; padding:11px; margin-bottom:9px; background:var(--bg-primary); text-decoration:none; color:inherit; transition:border-color .14s ease, box-shadow .14s ease; }
.rp-kb-openq:hover { border-color:var(--accent); box-shadow:var(--shadow-sm); }
.rp-kb-openq-title { font-size:12.5px; font-weight:700; color:var(--text-primary); line-height:1.3; }
.rp-kb-openq-meta { font-size:10px; color:var(--text-muted); margin-top:5px; display:flex; gap:6px; flex-wrap:wrap; align-items:center; }
.rp-kb-mini { font-size:9px; font-weight:800; color:var(--negative); background:var(--negative-bg); padding:1px 7px; border-radius:7px; text-transform:uppercase; letter-spacing:0.04em; }

/* ============ MOBILE ============ */
@media (max-width: 768px) {
  .rp-app { grid-template-columns:1fr; min-height:100vh; align-content:start; }
  .rp-sidebar { position:sticky; top:0; z-index:50; height:auto; align-self:start; overflow:visible; border-right:none; border-bottom:1px solid var(--border); padding:10px 12px; flex-direction:row; flex-wrap:wrap; align-items:center; gap:6px; }
  /* Nothing is pinned on a phone - the brand is a static cell in the header row - so it must
     not draw the scrolled-under hairline. Left on, it underlined the logo cell alone. */
  .rp-sidebar-brand { padding:0; margin-right:auto; font-size:16px; position:static; border-bottom:none; }
  .rp-sidebar-brand .rp-logo-mark { width:24px; height:24px; }
  .rp-nav-toggle { display:inline-flex; align-items:center; justify-content:center; flex:0 0 auto; width:40px; height:40px; border:1px solid var(--border-strong); background:var(--bg-secondary); border-radius:10px; color:var(--text-secondary); font-size:19px; line-height:1; cursor:pointer; }
  /* Collapsed by default on mobile: the long nav (search + items + sections) hides so it
     can't swallow the screen. Tapping the hamburger reveals it. The footer's Dark-mode +
     Sign-out stay ALWAYS visible as a second row, so they are never buried. */
  .rp-cmdk-trigger,
  .rp-sidebar > .rp-nav-section,
  .rp-sidebar > .rp-nav-item { display:none; }
  .rp-sidebar-footer { display:flex; flex:0 0 100%; align-items:center; gap:8px; margin-top:6px; padding:8px 0 0; border-top:1px solid var(--border); position:static; }
  .rp-sidebar-footer .rp-sidebar-user { display:none; }               /* drop avatar/name on mobile; keep the actions */
  /* Only the FORM stretches. The theme switch keeps its 34px square here too, so Sign out -
     the one with a word on it - gets the whole rest of the row instead of half of it. */
  .rp-sidebar-footer > form { flex:1; margin:0 !important; }
  /* Expanded: each nav entry is a full-width row stacked under the brand. */
  .rp-sidebar.rp-nav-open .rp-cmdk-trigger { display:flex; flex:0 0 100%; margin-top:4px; }
  .rp-sidebar.rp-nav-open > .rp-nav-section { display:block; flex:0 0 100%; margin-top:8px; }
  .rp-sidebar.rp-nav-open > .rp-nav-item { display:flex; flex:0 0 100%; padding:9px 10px; font-size:13px; margin:0; }
  .rp-content { padding:18px 16px; }
  .rp-hero, .rp-page-head { flex-direction:column; align-items:flex-start; gap:10px; }
  .rp-hero h1, .rp-page-head h1 { font-size:24px; }
  .rp-hero > div:last-child { width:100%; flex-wrap:wrap; }
  .rp-metrics { grid-template-columns:1fr; }
  .rp-card-head { flex-wrap:wrap; gap:10px; padding:12px 14px; }
  .rp-card-head h3 { width:100%; }
  .rp-card-head input[type="search"], .rp-card-head .rp-input { max-width:none !important; width:100%; }
  .rp-kb-search { flex-direction:column; }
  /* property rail + main stack */
  .rp-kb-layout { flex-direction:column; }
  .rp-kb-rail { width:100%; flex-direction:row; flex-wrap:wrap; border-right:none; border-bottom:1px solid var(--border); }
  .rp-kb-rail-h { width:100%; padding:2px 6px; }
  /* item detail + Q&A single column */
  .rp-kb-det-grid, .rp-kb-qa-grid { grid-template-columns:1fr; }
  .rp-kb-det-left { border-right:none; border-bottom:1px solid var(--border); }
  .rp-kb-qa-main { border-right:none; border-bottom:1px solid var(--border); }
  .rp-kb-prop-head { flex-direction:column; align-items:flex-start; }
  .rp-kb-form { padding:16px; }
  [style*="grid-template-columns: repeat(2"] { grid-template-columns:1fr !important; }
  [style*="grid-template-columns: 1fr 2fr auto"], [style*="grid-template-columns: 2fr 1fr auto"] { grid-template-columns:1fr auto !important; }
  .rp-input, input[type="text"], input[type="password"], input[type="search"], input[type="email"], input[type="number"], input[type="time"], input[type="url"], input[type="date"], textarea, select { font-size:16px !important; }
  .rp-btn { min-height:40px; padding:10px 14px; }
  /* The OPEN menu is a full-screen layer, not a taller sticky bar. The old expand-in-place grew
     the header and left the page showing - and scrolling - underneath it, which read as broken.
     html.rp-nav-lock pins the document while it is up, and the transient rpFade carries no
     transform, so it cannot become a containing block for the fixed overlays (the .rp-anim
     lesson). z 580 sits under the palette (600) and over the notification dropdown's default -
     which is why .rp-notif runs at 590: opened from the bell in this menu it must beat it. */
  .rp-sidebar.rp-nav-open { position:fixed; inset:0; z-index:580; overflow-y:auto;
                            overscroll-behavior:contain; touch-action:pan-y;
                            align-content:flex-start; border-bottom:none;
                            align-self:stretch;
                            padding:10px 12px calc(14px + env(safe-area-inset-bottom));
                            animation:rpFade .16s ease; }
  /* ^ align-self:stretch is THE line that makes the menu scroll, and it took three failed
     scroll-lock rewrites to find: the collapsed bar above sets align-self:start (so the
     grid row hugs its content), and per css-align, self-alignment ALSO applies to
     absolutely/fixed-positioned boxes - start turns inset:0 from "fill the viewport"
     into "start-align at CONTENT height". The open layer was always as tall as its own
     items (scrollHeight == clientHeight), so there was never anything to scroll, on any
     engine - the lock strategy was never the problem. */
  /* Scroll lock: position:fixed is the ONLY body lock that leaves the menu scrollable on
     iOS. overflow:hidden cannot work on EITHER root element: per css-overflow, when <html>
     is overflow:visible the BODY's overflow value is applied to the viewport itself - so
     "html overflow:hidden" and "body overflow:hidden" are the same viewport lock, and a
     hidden viewport is exactly the state in which iOS Safari refuses to touch-scroll
     position:fixed descendants (the menu froze solid both times). Fixing the body instead
     freezes the page without touching viewport overflow. rpNavToggle() sets an inline
     top:-scrollY so the page does not visually jump, and restores the scroll on close. */
  html.rp-nav-lock body { position:fixed; left:0; right:0; width:100%; }
  /* Palette on a phone: full-width sheet pinned high, sized by the DYNAMIC viewport so the
     results keep scrolling above the on-screen keyboard instead of hiding behind it. */
  .rp-cmdk { padding:10px 10px calc(10px + env(safe-area-inset-bottom)); }
  .rp-cmdk-box { max-height:calc(100dvh - 20px); }
  .rp-cmdk-results { max-height:none; flex:1; }
}
@media (max-width: 480px) {
  .rp-login-form-panel { padding:32px 22px; }
  .rp-login-brand { padding:28px 22px; min-height:160px; }
  .rp-login-brand-title { font-size:30px; }
}

/* ============================================================================
   SHELL CHROME v2 - notifications, command palette, flash toasts
   Ported from the Passport Admin prototype. The old chrome used emoji glyphs, an
   amber ring-less badge and a full-screen scrim modal for notifications; this is
   the anchored dropdown + SVG + tabular-nums bubble the mockup specifies.
   ============================================================================ */

/* ---- bell + its bubble ---------------------------------------------------- */
.rp-bell { position:relative; margin-left:auto; flex:none; width:34px; height:34px; display:inline-flex;
           align-items:center; justify-content:center; border:1px solid var(--border-strong);
           background:var(--bg-primary); border-radius:10px; cursor:pointer; color:var(--text-secondary); }
.rp-bell:hover { color:var(--text-primary); border-color:var(--text-faint); }
.rp-bell svg { display:block; }
/* The 2px ring is the page colour, so the bubble reads as punched out of the button rather than
   sitting on its edge. tabular-nums so 1 -> 2 -> 10 does not make the bubble jitter. */
.rp-bell-count { position:absolute; top:-5px; right:-5px; min-width:16px; height:16px; padding:0 4px;
                 border-radius:999px; background:var(--negative); color:#fff; font-size:9.5px;
                 font-weight:700; letter-spacing:-0.02em; display:none; align-items:center;
                 justify-content:center; box-shadow:0 0 0 2px var(--bg-primary);
                 font-variant-numeric:tabular-nums; }

/* ---- notification dropdown ------------------------------------------------
   position:FIXED and JS-anchored to the bell, not position:absolute. The bell lives in the
   sidebar, and .rp-sidebar is `overflow-y:auto` - an absolutely-positioned panel would be
   clipped by it and would scroll away with the nav. Fixed + a measured origin escapes that. */
.rp-notif { display:none; position:fixed; z-index:590; width:min(354px, calc(100vw - 24px));
            background:var(--bg-primary); border:1px solid var(--border-strong); border-radius:15px;
            box-shadow:var(--shadow-md); overflow:hidden; animation:rpPop .16s ease; }
.rp-notif-head { display:flex; align-items:center; gap:9px; padding:13px 16px; border-bottom:1px solid var(--border); }
.rp-notif-title { font-size:13px; font-weight:650; color:var(--text-primary); letter-spacing:-0.01em; }
.rp-notif-new { display:none; font-size:10px; font-weight:700; padding:2px 8px; border-radius:999px;
                color:var(--accent); background:var(--accent-bg); font-variant-numeric:tabular-nums; }
.rp-notif-read { margin-left:auto; border:none; background:transparent; color:var(--text-muted);
                 font-size:11px; font-weight:600; cursor:pointer; padding:2px 0; }
.rp-notif-read:hover { color:var(--accent); }
.rp-notif-list { max-height:min(330px, 50vh); overflow-y:auto; }
.rp-notif-item { display:flex; gap:11px; padding:12px 16px; border-bottom:1px solid var(--border);
                 cursor:pointer; align-items:flex-start; text-decoration:none; color:inherit; }
.rp-notif-item:hover { background:var(--bg-secondary); }
.rp-notif-ic { width:30px; height:30px; flex:none; border-radius:9px; display:inline-flex;
               align-items:center; justify-content:center; }
.rp-notif-ic-request  { background:var(--warn-bg);     color:var(--warn); }
.rp-notif-ic-payment  { background:var(--positive-bg); color:var(--positive); }
.rp-notif-ic-identity { background:var(--accent-bg);   color:var(--accent); }
.rp-notif-ic-checkout { background:var(--positive-bg); color:var(--positive); }
.rp-notif-ic-feedback { background:var(--warn-bg);     color:var(--warn); }
.rp-notif-ic-leads    { background:var(--accent-bg);   color:var(--accent); }
.rp-notif-ic-deposit  { background:var(--negative-bg); color:var(--negative); }  /* unsecured guest = red */
.rp-notif-ic-eta      { background:var(--accent-bg);   color:var(--accent); }   /* an arrival time is information, not a task */
.rp-notif-tx { flex:1; min-width:0; }
.rp-notif-t { display:block; font-size:12.5px; font-weight:600; color:var(--text-primary); line-height:1.45; }
.rp-notif-s { display:block; font-size:10.5px; color:var(--text-faint); margin-top:3px; }
.rp-notif-s .rp-m { font-family:var(--mono); }
.rp-notif-dot { width:6px; height:6px; flex:none; border-radius:50%; background:var(--accent); margin-top:7px; }
/* Seen = quieter, not dimmer. The old shell dropped the whole row to opacity .55, which made a
   read notification look disabled rather than read. */
.rp-notif-item.rp-seen .rp-notif-t   { font-weight:450; color:var(--text-muted); }
.rp-notif-item.rp-seen .rp-notif-ic  { opacity:.45; }
.rp-notif-item.rp-seen .rp-notif-dot { opacity:0; }
.rp-notif-empty { padding:28px 18px; text-align:center; font-size:12.5px; color:var(--text-muted); }
.rp-notif-sound { display:flex; align-items:center; gap:8px; width:100%; border:none;
                  border-top:1px solid var(--border); background:var(--bg-secondary); padding:9px 16px;
                  font-size:11.5px; font-weight:600; color:var(--text-muted); cursor:pointer; text-align:left; }
.rp-notif-sound:hover { color:var(--text-secondary); }

/* ---- command palette ------------------------------------------------------ */
/* The palette overlay itself. Classes rather than inline styles so the phone can reshape it:
   near-full-height sheet, dvh-bounded (the dynamic unit follows the on-screen keyboard), with
   the results scrolling INSIDE the box - see the mobile block. */
.rp-cmdk { display:none; position:fixed; inset:0; z-index:600; background:rgba(6,10,20,0.72);
           align-items:flex-start; justify-content:center; padding:12vh 20px 20px;
           animation:rpFade .15s ease; }
.rp-cmdk-box { display:flex; flex-direction:column; min-height:0; width:100%; max-width:560px;
               background:var(--bg-primary); border:1px solid var(--border-strong);
               border-radius:16px; overflow:hidden; box-shadow:var(--shadow-md);
               animation:rpPop .18s ease; }
.rp-cmdk-results { max-height:340px; overflow-y:auto; -webkit-overflow-scrolling:touch; }
.rp-cmdk-in-row { display:flex; align-items:center; gap:12px; padding:0 18px; border-bottom:1px solid var(--border); }
.rp-kbd { font-family:var(--mono); font-size:9.5px; border:1px solid var(--border-strong); border-radius:5px;
          padding:2px 6px; color:var(--text-muted); background:var(--bg-secondary); line-height:1; flex:none; }
.rp-cmdk-foot { display:flex; align-items:center; gap:14px; padding:9px 18px;
                background:var(--bg-secondary); border-top:1px solid var(--border); }
.rp-cmdk-hint { display:inline-flex; align-items:center; gap:6px; font-size:10.5px; color:var(--text-faint); }
.rp-cmdk-res { display:flex; align-items:center; gap:12px; padding:11px 18px;
               border-bottom:1px solid var(--border); cursor:pointer; text-decoration:none; }
.rp-cmdk-res:hover, .rp-cmdk-res.rp-on { background:var(--bg-secondary); }
.rp-cmdk-kind { font-size:9.5px; font-weight:650; letter-spacing:0.07em; text-transform:uppercase;
                color:var(--text-faint); width:52px; flex:none; }
.rp-cmdk-ttl { font-size:13.5px; font-weight:600; color:var(--text-primary); }
.rp-cmdk-sub { font-size:11px; color:var(--text-faint); font-family:var(--mono); margin-top:1px; }
.rp-cmdk-empty { padding:18px; font-size:13px; color:var(--text-muted); }

/* ---- flash toasts ---------------------------------------------------------
   ONE host, a real column. Flashes used to be rendered by each page for itself, so a page that
   forgot (every Passport page did) left its messages in the session to pile up and dump all at
   once on the next page that did render them - "multiple notifications one after another, all
   plotted in the same place". They stack now, and they leave on their own. */
.rp-toasts { position:fixed; left:50%; bottom:24px; transform:translateX(-50%); z-index:2000;
             display:flex; flex-direction:column; align-items:stretch; gap:8px;
             width:min(440px, calc(100vw - 28px)); pointer-events:none; }
.rp-tt { display:flex; align-items:flex-start; gap:10px; padding:11px 12px; border-radius:12px;
         background:var(--bg-primary); border:1px solid var(--border-strong); box-shadow:var(--shadow-md);
         font-size:13px; font-weight:500; line-height:1.45; color:var(--text-primary);
         pointer-events:auto; animation:rpTtIn .22s cubic-bezier(.2,.7,.2,1) both; }
.rp-tt-out { animation:rpTtOut .26s ease forwards; }
.rp-tt-ic { width:20px; height:20px; flex:none; border-radius:6px; display:inline-flex;
            align-items:center; justify-content:center; margin-top:1px; }
.rp-tt-msg { flex:1; min-width:0; word-break:break-word; }
.rp-tt-x { border:none; background:transparent; color:var(--text-faint); cursor:pointer;
           font-size:14px; line-height:1; padding:3px 2px 3px 6px; flex:none; }
.rp-tt-x:hover { color:var(--text-secondary); }
.rp-tt-success { border-color:color-mix(in srgb, var(--positive) 30%, transparent); }
.rp-tt-success .rp-tt-ic { background:var(--positive-bg); color:var(--positive); }
.rp-tt-error   { border-color:color-mix(in srgb, var(--negative) 30%, transparent); }
.rp-tt-error   .rp-tt-ic { background:var(--negative-bg); color:var(--negative); }
.rp-tt-info    .rp-tt-ic { background:var(--accent-bg);   color:var(--accent); }
@keyframes rpTtIn  { from { opacity:0; transform:translateY(10px) scale(.98); } to { opacity:1; transform:none; } }
@keyframes rpTtOut { to { opacity:0; transform:translateY(6px) scale(.98); } }
@media (prefers-reduced-motion: reduce) { .rp-notif, .rp-tt, .rp-tt-out { animation:none; } }

/* ===================================================================
   ID LIGHTBOX
   -------------------------------------------------------------------
   The stage CLIPS and carries the image; the toolbar sits above it in the stacking order.
   A rotated <img> keeps its unrotated layout box (transform paints, it does not reflow), so
   a turned landscape photo used to spill upward and cover Rotate/Close/the arrows - the JS
   in _idlightbox.html caps it against the post-rotation axis, and these two rules make sure
   that even an unexpected overspill is clipped instead of eating the controls.
   =================================================================== */
.rp-lb { position:fixed; inset:0; z-index:300; background:rgba(6,10,20,0.80); align-items:center; justify-content:center; padding:40px; }
.rp-lb-dialog { display:flex; flex-direction:column; max-width:560px; width:100%; max-height:100%; background:var(--bg-primary); border:1px solid var(--border-strong); border-radius:16px; overflow:hidden; box-shadow:var(--shadow-md); }
.rp-lb-head { position:relative; z-index:2; display:flex; align-items:center; justify-content:space-between; gap:12px; padding:14px 18px; border-bottom:1px solid var(--border); background:var(--bg-primary); flex-wrap:wrap; }
.rp-lb-title { font-size:13.5px; font-weight:600; color:var(--text-primary); }
.rp-lb-tools { display:flex; gap:8px; align-items:center; flex-wrap:wrap; }
.rp-lb-count { font-size:11.5px; color:var(--text-faint); font-variant-numeric:tabular-nums; }
.rp-lb-btn { border:1px solid var(--border-strong); background:var(--bg-secondary); color:var(--text-secondary); border-radius:8px; padding:5px 12px; font-size:12px; font-weight:600; cursor:pointer; }
.rp-lb-btn:hover { background:var(--bg-tertiary); color:var(--text-primary); }
.rp-lb-stage { position:relative; z-index:1; overflow:hidden; height:60vh; padding:20px; display:flex; align-items:center; justify-content:center; background:var(--bg-secondary); }
.rp-lb-img { display:block; border-radius:12px; transition:transform .25s ease; }
.rp-lb-strip { display:flex; gap:8px; padding:12px 18px; border-top:1px solid var(--border); background:var(--bg-primary); }
.rp-lb-tab { flex:1; text-transform:capitalize; border-radius:8px; padding:6px 8px; font-size:11.5px; font-weight:600; cursor:pointer; border:1px solid var(--border-strong); background:var(--bg-secondary); color:var(--text-secondary); }
.rp-lb-tab.is-on { border-color:var(--accent); background:var(--accent); color:#fff; }
@media (max-width: 640px) {
  .rp-lb { padding:10px; }
  .rp-lb-head { padding:11px 12px; gap:8px; }
  .rp-lb-tools { width:100%; justify-content:space-between; }
  .rp-lb-btn { padding:8px 12px; }          /* a real tap target on a phone */
  .rp-lb-stage { height:52vh; padding:12px; }
}

/* ===================================================================
   PASSPORT BOOKING VIEW - LAYOUT + COMPONENTS
   -------------------------------------------------------------------
   The booking view used to be laid out entirely with inline grid styles, and it only reflowed on a
   phone because the 900px media query above carried five selectors that matched the LITERAL TEXT of
   those style attributes:

       .rpx-main [style*="grid-template-columns:repeat(4"] { grid-template-columns:1fr !important; }

   That is a substring match on an attribute value, not a layout rule. Re-ordering a grid, changing a
   gap, or so much as deleting a space inside the style attribute silently unmatched the selector and
   the page shipped four tiles side by side on a 390px screen with nothing failing anywhere. Those
   five selectors are gone; the grids below are real classes that the markup carries. Never reach for
   [style*=...] again - if a grid needs to reflow, give it a class here.
   =================================================================== */
.rpx-tiles { display:grid; grid-template-columns:repeat(4,1fr); gap:12px; }
.rpx-2col  { display:grid; grid-template-columns:1.55fr 1fr; gap:0; }
.rpx-2col-l { padding:18px 20px; border-right:1px solid var(--border); }
.rpx-2col-r { padding:18px 20px; }
.rpx-3col  { display:grid; grid-template-columns:repeat(3,1fr); gap:12px; }
.rpx-2grid { display:grid; grid-template-columns:1fr 1fr; gap:16px; }
.rpx-split { display:grid; grid-template-columns:1.55fr 1fr; gap:16px; }
/* Both cells of a 2-up are permission-gated, so a user who can see only one of them would get a
   card marooned in half the width with a hole beside it. A lone survivor takes the whole row. */
.rpx-2grid > :only-child, .rpx-split > :only-child { grid-column:1 / -1; }
/* A grid item's min-width resolves to auto, so it refuses to shrink below its content: one long flat
   name ("The Queensborough 3 Bed Apartment By Hyde Park") in one cell is enough to widen the whole
   track and push the page sideways. Same class of bug the .rpx-legs strip above was fixed for. */
.rpx-tiles > *, .rpx-2col > *, .rpx-3col > *, .rpx-2grid > * { min-width:0; }

/* Four tiles collapsed straight to one column wastes a tablet; two-up is the readable middle. */
@media (max-width: 1024px) {
  .rpx-tiles { grid-template-columns:repeat(2,1fr) !important; }
}

/* ---- surface card --------------------------------------------------------- */
.rpx-card { background:var(--bg-primary); border:1px solid var(--border); border-radius:14px;
            box-shadow:var(--shadow-sm); padding:18px 20px; margin-bottom:16px; }
.rpx-card-h { display:flex; align-items:center; justify-content:space-between; gap:10px; margin-bottom:12px; }
.rpx-card-h h3 { font-size:15px; }

/* ---- collapsible card (<details>) -----------------------------------------
   Finance and Timeline are reference material, not the job in hand: they ship folded shut, and the
   summary row carries an at-a-glance line so the fold costs no information when closed. */
details.rpx-fold { background:var(--bg-primary); border:1px solid var(--border); border-radius:14px;
                   box-shadow:var(--shadow-sm); padding:18px 20px; margin-bottom:16px; }
summary.rpx-fold-h { display:flex; align-items:center; gap:10px; cursor:pointer; user-select:none;
                     list-style:none; }
summary.rpx-fold-h::-webkit-details-marker { display:none; }
summary.rpx-fold-h::marker { content:''; }
summary.rpx-fold-h h3 { font-size:15px; }
/* A summary is a real button to a keyboard user, so it gets a real focus ring. */
summary.rpx-fold-h:focus-visible { outline:2px solid var(--accent); outline-offset:3px; border-radius:8px; }
.rpx-fold-sum { margin-left:auto; font-size:12px; color:var(--text-muted); font-variant-numeric:tabular-nums; }
.rpx-fold-chev { flex:none; display:inline-flex; align-items:center; color:var(--text-faint);
                 transition:transform .18s ease; }
details.rpx-fold[open] .rpx-fold-chev { transform:rotate(180deg); }
details.rpx-fold[open] .rpx-fold-sum { display:none; }
/* The glance line is what holds the chevron out at the right edge (margin-left:auto). Hiding it on
   open took that away and the chevron jumped back across the card to sit against the title, so
   every fold twitched as it opened. Hand it the auto margin when the summary line goes. */
details.rpx-fold[open] .rpx-fold-chev { margin-left:auto; }
.rpx-fold-body { padding-top:14px; }

/* ---- flush variants --------------------------------------------------------
   A card whose body is a list (timeline events, request rows, messages) carries no padding of its
   own: each row pads itself, so the dividers between them reach both edges instead of stopping
   short in a gutter. The header keeps its padding and becomes the divider. */
.rpx-card-flush { padding:0; }
.rpx-card-flush > .rpx-card-h { padding:15px 20px; margin-bottom:0; border-bottom:1px solid var(--border); }
details.rpx-fold-flush { padding:0; }
details.rpx-fold-flush > .rpx-fold-h { padding:15px 20px; }
details.rpx-fold-flush[open] > .rpx-fold-h { border-bottom:1px solid var(--border); }
details.rpx-fold-flush > .rpx-fold-body { padding-top:0; }
.rpx-row { display:flex; align-items:flex-start; gap:11px; padding:11px 20px; border-bottom:1px solid var(--border); }
.rpx-row:last-child { border-bottom:none; }
.rpx-empty { padding:18px 20px; color:var(--text-faint); font-size:13px; }

/* ---- secured / unsecured stay --------------------------------------------- */
.rpx-secured { background:var(--positive-bg); border:1px solid color-mix(in srgb, var(--positive) 30%, transparent);
               border-radius:12px; padding:12px 14px; }
.rpx-secured-h { display:flex; align-items:center; gap:8px; font-size:13px; font-weight:700; color:var(--positive); }
.rpx-secured-line { font-size:12.5px; color:var(--text-secondary); margin-top:4px; }
.rpx-secured-amt { font-variant-numeric:tabular-nums; font-weight:700; color:var(--text-primary); }
.rpx-unsecured { background:var(--bg-tertiary); border:1px solid var(--border); border-radius:12px; padding:12px 14px; }
.rpx-unsecured .rpx-secured-h { color:var(--text-muted); }
.rpx-unsecured .rpx-secured-line { color:var(--text-muted); }

/* ---- pass delivery strip --------------------------------------------------- */
.rpx-deliv-row { display:flex; align-items:center; gap:10px; flex-wrap:wrap; }
/* Status pills, NOT actions. They share the look of a row but must never inherit the phone rule
   below that stretches every child of .rpx-deliv-row to full width - a "Sent" pill as a full-width
   block reads as a button you can press. */
.rpx-deliv-pills { display:flex; align-items:center; gap:8px; flex-wrap:wrap; }
.rpx-deliv-state { font-size:11px; font-weight:700; padding:3px 10px; border-radius:999px; white-space:nowrap; }
.rpx-deliv-state.is-sent     { background:var(--positive-bg); color:var(--positive); }
.rpx-deliv-state.is-unsent   { background:var(--warn-bg);     color:var(--warn); }
.rpx-deliv-state.is-open     { background:var(--accent-bg);   color:var(--accent); }
.rpx-deliv-state.is-unopened { background:var(--bg-tertiary); color:var(--text-muted); }
.rpx-deliv-msg { width:100%; min-height:74px; padding:10px 12px; border-radius:10px;
                 border:1px solid var(--border); background:var(--bg-secondary); color:var(--text-secondary);
                 font-family:var(--mono); font-size:12px; line-height:1.5; resize:vertical; }

/* ---- action buttons -------------------------------------------------------- */
.rpx-btn { display:inline-flex; align-items:center; justify-content:center; gap:6px;
           padding:9px 14px; border-radius:9px; font-family:var(--sans); font-size:12.5px; font-weight:600;
           cursor:pointer; border:1px solid var(--border-strong);
           background:var(--bg-secondary); color:var(--text-secondary);
           transition:background-color .14s ease, color .14s ease, border-color .14s ease; }
.rpx-btn:hover { background:var(--bg-hover); color:var(--text-primary); }
.rpx-btn:focus-visible { outline:2px solid var(--accent); outline-offset:2px; }
.rpx-btn-primary { background:var(--accent); color:#fff; border:none; }
.rpx-btn-primary:hover { background:var(--accent-hover); color:#fff; }
.rpx-btn-ghost { background:transparent; color:var(--text-muted); }
.rpx-btn-ghost:hover { background:var(--bg-tertiary); color:var(--text-primary); }
.rpx-btn:disabled { opacity:.5; cursor:not-allowed; }
.rpx-btn:disabled:hover { background:var(--bg-secondary); color:var(--text-secondary); }
.rpx-btn-primary:disabled:hover { background:var(--accent); color:#fff; }

/* ---- blocked deposit: the team's way out -----------------------------------
   Four routes laid out side by side, because the failure this replaces was not "the team
   chose wrongly" - it was the team having nothing to choose from and the booking going quiet.
   So every option is on screen at once, including the ones the reader cannot fire (greyed,
   with who to ask) and the one that costs us money (labelled with what it costs).

   The chosen tile stays on screen after the decision rather than collapsing to a receipt:
   changing your mind is a first-class action here, not an error path. */
.rpx-dres { border-color:var(--warn); }
.rpx-dres-head { display:flex; align-items:flex-start; gap:12px; flex-wrap:wrap;
                 padding:15px 20px; border-bottom:1px solid var(--border); background:var(--warn-bg); }
.rpx-dres-decided .rpx-dres-head { background:var(--positive-bg); }
.rpx-dres-decided { border-color:var(--positive); }
.rpx-dres-head h3 { font-size:15px; margin:0; }
.rpx-dres-why { font-size:12.5px; color:var(--text-secondary); line-height:1.55; margin-top:4px; }
.rpx-dres-verdict { font-family:var(--mono); font-size:11.5px; color:var(--text-muted);
                    line-height:1.5; margin-top:7px; }
.rpx-dres-body { padding:16px 20px; }
.rpx-dres-lead { font-size:11px; font-weight:700; letter-spacing:0.04em; text-transform:uppercase;
                 color:var(--text-muted); margin-bottom:10px; }

.rpx-dres-opts { display:grid; grid-template-columns:repeat(4, 1fr); gap:12px; }
.rpx-dres-opt { display:flex; flex-direction:column; border:1px solid var(--border);
                border-radius:12px; padding:14px; background:var(--bg-secondary); }
/* The standing decision. Ringed rather than merely tinted so it reads at a glance on a
   crowded page, and so it is still obvious in the greyed (no-permission) state. */
.rpx-dres-opt.is-on { border-color:var(--positive); background:var(--positive-bg);
                      box-shadow:inset 0 0 0 1px var(--positive); }
.rpx-dres-opt.is-locked { opacity:.62; }
.rpx-dres-name { font-size:13.5px; font-weight:700; color:var(--text-primary); }
.rpx-dres-copy { font-size:12px; color:var(--text-secondary); line-height:1.5; margin-top:6px; flex:1; }
/* The honest cost of each route: the Stripe fee, the weeks a card carries a hold, the fact
   that a force-open reaches every apartment. Never hidden behind a tooltip. */
.rpx-dres-cost { font-size:11.5px; line-height:1.45; margin-top:9px; color:var(--warn);
                 background:var(--warn-bg); border-radius:8px; padding:7px 9px; }
.rpx-dres-opt form { margin-top:11px; }
.rpx-dres-opt .rpx-btn { width:100%; }
.rpx-dres-ask { font-size:11.5px; color:var(--text-faint); margin-top:11px;
                text-align:center; line-height:1.4; }
.rpx-dres-on-pill { display:inline-flex; align-items:center; gap:5px; font-size:10.5px;
                    font-weight:700; letter-spacing:0.03em; text-transform:uppercase;
                    color:var(--positive); margin-bottom:7px; }

/* Telling the guest. Deliberately a separate step: nothing here sends by itself, because a
   button that fires a real message into a real thread is not something to discover by accident. */
.rpx-dres-tell { margin-top:16px; border:1px solid var(--border); border-radius:12px;
                 padding:14px; background:var(--bg-tertiary); }
.rpx-dres-msg { font-size:12.5px; color:var(--text-secondary); line-height:1.6;
                background:var(--bg-primary); border:1px solid var(--border);
                border-radius:9px; padding:11px 13px; margin:9px 0 11px; }
.rpx-dres-acts { display:flex; gap:8px; flex-wrap:wrap; }
.rpx-dres-note { width:100%; margin-top:12px; padding:9px 11px; border-radius:9px;
                 border:1px solid var(--border-strong); background:var(--bg-secondary);
                 color:var(--text-primary); font-family:var(--sans); font-size:12.5px; }

@media (max-width: 1100px) { .rpx-dres-opts { grid-template-columns:repeat(2, 1fr); } }

/* ---- phone -----------------------------------------------------------------
   No overflow-x:hidden on html/body here. Setting it computes overflow-y to auto, which turns the
   element into a scroll container and kills every position:sticky inside it - exactly the bug that
   was just dug out of passport.css. If something scrolls sideways, contain it where it happens.

   The grid collapses carry !important because a page that still has its inline grid on the container
   (kross/finance.html) would otherwise win on specificity and stay four-up on a phone. Drop the
   !important once no template declares grid-template-columns inline. */
@media (max-width: 760px) {
  .rpx-tiles, .rpx-2col, .rpx-3col, .rpx-2grid { grid-template-columns:1fr !important; }
  .rpx-dres-opts { grid-template-columns:1fr; }
  .rpx-dres-acts > * { width:100%; }
  .rpx-2col-l { border-right:none; border-bottom:1px solid var(--border); }
  .rpx-main .rpx-card, .rpx-main details.rpx-fold { padding:14px 15px; }
  /* Must out-specify the .rpx-main .rpx-card rule above, which would otherwise re-pad a flush card
     and pull its row dividers away from the edges. */
  .rpx-main .rpx-card-flush, .rpx-main details.rpx-fold-flush { padding:0; }
  .rpx-card-flush > .rpx-card-h, details.rpx-fold-flush > .rpx-fold-h { padding:13px 15px; }
  .rpx-row, .rpx-empty { padding:11px 15px; }
  .rpx-card-h { flex-wrap:wrap; }
  /* Only the ACTION rows go full width. Stretching every .rpx-btn was a bug: the Add button in
     Team notes sits in a flex row next to the note input, so at 100% it took the whole line and
     squeezed the input it belongs to down to a 26px sliver - on the one card that was moved to
     the top of the page precisely so it would get used. Same for the inline "copy code" chip. */
  .rpx-deliv-row { align-items:stretch; }
  .rpx-deliv-row > *, .rpx-deliv-row > form > .rpx-btn { width:100%; }
  .rpx-btn { min-height:44px; }
  .rpx-split { grid-template-columns:1fr !important; }
}

/* ===================================================================
   BOOKINGS - "who is here now" board + the browse-all table
   -------------------------------------------------------------------
   Every row of the old Bookings list carried the nine-column track in an inline
   `style="grid-template-columns:150px 1.1fr ..."`. An inline declaration beats any rule a media
   query can write, so the list could not reflow: on a phone it just scrolled sideways, and the
   only lever left was the [style*=...] substring hack the Passport section above documents as a
   mistake. It also meant the Total column could not be dropped for a viewer without view_finance
   without misaligning every row underneath it, so the page shipped gross revenue to the whole
   team. The track lives here now, in two flavours, and the cells stack on a phone.

   Everything below is token-only (dark mode comes free) and reuses the .rpx-* and .rp-* pieces
   where they already fit: .rpx-hero for the banner, .rpx-btn for actions, .rp-sc for scrollbars.
   =================================================================== */

/* ---- Now | Browse all ------------------------------------------------------
   Two <a>s carrying ?view=now / ?view=all, so both modes are bookmarkable and the back button
   works. Lives on the page background, NOT inside .rpx-hero: these are page tokens and the hero
   is a fixed blue gradient in both themes, so on it they would vanish in dark mode. */
.kb-seg { display:inline-flex; gap:4px; padding:4px; border-radius:12px;
          background:var(--bg-tertiary); border:1px solid var(--border); }
.kb-seg-item { display:inline-flex; align-items:center; justify-content:center; gap:7px;
               min-height:36px; padding:7px 16px; border-radius:9px; white-space:nowrap;
               font-size:13px; font-weight:600; color:var(--text-muted); text-decoration:none;
               border:1px solid transparent; cursor:pointer;
               transition:background .14s ease, color .14s ease; }
.kb-seg-item:hover { color:var(--text-primary); }
.kb-seg-item.is-on { background:var(--bg-primary); color:var(--text-primary);
                     border-color:var(--border); box-shadow:var(--shadow-sm); }
.kb-seg-item:focus-visible { outline:2px solid var(--accent); outline-offset:2px; }

/* ---- counter strip --------------------------------------------------------- */
.kb-stats { display:grid; grid-template-columns:repeat(4,1fr); gap:12px; margin-bottom:16px; }
/* Three of the four tiles are <button>s (they open their section and scroll to it), so this has to
   undo the user-agent button styling: font, alignment and the centred text a button defaults to. */
.kb-stat { display:flex; flex-direction:column; align-items:flex-start; text-align:left; gap:2px;
           min-width:0; width:100%; padding:14px 16px; font-family:inherit;
           border-radius:12px; background:var(--bg-primary); border:1px solid var(--border);
           box-shadow:var(--shadow-sm); color:inherit; text-decoration:none;
           transition:border-color .14s ease, box-shadow .14s ease; }
button.kb-stat { cursor:pointer; }
button.kb-stat:hover { border-color:var(--border-strong); }
a.kb-stat:hover { border-color:var(--accent); box-shadow:var(--shadow-md); }
.kb-stat-n { font-size:26px; font-weight:800; letter-spacing:-0.02em;
             font-variant-numeric:tabular-nums; line-height:1.15; }
.kb-stat-l { font-size:11px; font-weight:600; text-transform:uppercase; letter-spacing:0.08em;
             color:var(--text-muted); }
.kb-stat.is-accent .kb-stat-n { color:var(--accent); }
.kb-stat.is-warn   .kb-stat-n { color:var(--warn); }
.kb-stat.is-alert  .kb-stat-n { color:var(--negative); }

/* ---- filter bar + More filters --------------------------------------------- */
.kb-bar { background:var(--bg-primary); border:1px solid var(--border); border-radius:14px;
          box-shadow:var(--shadow-sm); padding:12px 14px; margin-bottom:16px; }
.kb-bar-row { display:flex; align-items:center; gap:8px; flex-wrap:wrap; }
.kb-bar-row + .kb-bar-row { margin-top:10px; }
/* Real classes, not an inline flex shorthand: the phone breakpoint turns this bar into a column,
   and a basis it cannot reach becomes a height. See the reset in the 760px block. */
.kb-in-grow { flex:1 1 240px; }
.kb-in-prop { flex:0 1 280px; }
.kb-in { min-height:36px; padding:8px 10px; font-family:inherit; font-size:12.5px;
         border:1px solid var(--border-strong); border-radius:9px; background:var(--bg-secondary);
         color:var(--text-primary); outline:none; min-width:0;
         transition:border-color .14s ease, box-shadow .14s ease; }
.kb-in:focus { border-color:var(--accent); box-shadow:0 0 0 3px var(--accent-bg); }
.kb-in::placeholder { color:var(--text-faint); }
details.kb-more { margin-top:10px; border-top:1px solid var(--border); }
summary.kb-more-h { display:inline-flex; align-items:center; gap:7px; min-height:36px;
                    padding:8px 0 0; cursor:pointer; user-select:none; list-style:none;
                    font-size:12.5px; font-weight:600; color:var(--text-muted); }
summary.kb-more-h::-webkit-details-marker { display:none; }
summary.kb-more-h::marker { content:''; }
summary.kb-more-h:hover { color:var(--text-primary); }
summary.kb-more-h:focus-visible { outline:2px solid var(--accent); outline-offset:3px; border-radius:8px; }
details.kb-more[open] summary.kb-more-h { color:var(--text-primary); }
.kb-more-body { display:grid; grid-template-columns:repeat(auto-fit, minmax(180px,1fr)); gap:10px;
                padding:12px 0 4px; }
.kb-more-body > * { min-width:0; }

/* ---- active filter chips ---------------------------------------------------
   The chip IS the remove control - an <a> whose href is this same query minus that one filter.
   The x is a glyph inside it, not a nested button: a button inside a link is not a thing, and a
   28px x is not a tap target anyway. Tapping anywhere on the chip drops the filter. */
.kb-chips { display:flex; align-items:center; gap:7px; flex-wrap:wrap; }
.kb-chip { display:inline-flex; align-items:center; gap:7px; min-height:30px; padding:4px 11px;
           border-radius:999px; border:1px solid var(--border-strong); background:var(--bg-primary);
           color:var(--text-secondary); font-size:12px; font-weight:600; white-space:nowrap;
           text-decoration:none; transition:border-color .14s ease, color .14s ease; }
.kb-chip:hover { border-color:var(--negative); color:var(--negative); }
.kb-chip b { color:var(--text-primary); font-weight:700; }
.kb-chip:hover b { color:var(--negative); }
.kb-chip-x { display:inline-flex; align-items:center; justify-content:center; flex:none;
             width:16px; height:16px; border-radius:50%; background:var(--bg-tertiary);
             color:var(--text-muted); font-size:11px; line-height:1; }
.kb-chip:hover .kb-chip-x { background:var(--negative-bg); color:var(--negative); }

/* ---- a board section (Checking out today / In house / ...) ------------------
   A <details>. The heading carries the count, so a CLOSED section is still an answer ("In house 23")
   rather than a hidden list - which is what makes the folded board a summary you can read at a
   glance instead of a scroll. */
.kb-sec { background:var(--bg-primary); border:1px solid var(--border); border-radius:14px;
          box-shadow:var(--shadow-sm); overflow:hidden; margin-bottom:16px; }
summary.kb-sec-h { display:flex; align-items:center; gap:10px; flex-wrap:wrap; padding:14px 18px;
            border-bottom:1px solid var(--border); cursor:pointer; user-select:none;
            list-style:none; }
/* Both, and neither is redundant: Safari still ships the -webkit marker, everyone else reads
   list-style. Left in, the native triangle sits next to our own chevron. */
summary.kb-sec-h::-webkit-details-marker { display:none; }
summary.kb-sec-h::marker { content:''; }
summary.kb-sec-h:hover { background:var(--bg-secondary); }
.kb-sec[open] > summary.kb-sec-h { border-bottom:1px solid var(--border); }
.kb-sec:not([open]) > summary.kb-sec-h { border-bottom:none; }
.kb-sec-chev { display:inline-flex; flex:none; color:var(--text-muted);
               transition:transform .15s ease; }
.kb-sec[open] > summary .kb-sec-chev { transform:rotate(90deg); }
.kb-sec-title { font-size:14px; font-weight:700; letter-spacing:-0.01em; color:var(--text-primary); }
.kb-sec-n { font-size:11px; font-weight:700; padding:2px 9px; border-radius:999px;
            background:var(--bg-tertiary); color:var(--text-muted);
            font-variant-numeric:tabular-nums; }
.kb-sec-n.is-warn  { background:var(--warn-bg);     color:var(--warn); }
.kb-sec-n.is-alert { background:var(--negative-bg); color:var(--negative); }
.kb-sec-act { margin-left:auto; display:flex; align-items:center; gap:8px; flex-wrap:wrap; }

/* The row above the sections: collapse/expand everything, and the link out to Today ops. */
.kb-secbar { display:flex; align-items:center; gap:10px; margin:0 0 12px; flex-wrap:wrap; }
.kb-secbar > .rpx-btn { margin-left:auto; }
.kb-link { background:none; border:none; padding:6px 2px; font-family:inherit; font-size:12.5px;
           font-weight:600; color:var(--text-muted); cursor:pointer; }
.kb-link:hover { color:var(--accent); }
/* Day divider inside "Arriving next 7 days". It is a SIBLING of the grid rows inside .kb-scroll,
   not a cell of them, so it needs the track's own min-width or its background band stops at the
   scroll viewport and the rows slide out from under it. It carries `no-money` for the same reason
   the rows do: that track is narrower. */
.kb-day { padding:8px 18px; background:var(--bg-secondary); border-bottom:1px solid var(--border);
          font-size:11px; font-weight:700; text-transform:uppercase; letter-spacing:0.07em;
          color:var(--text-muted); min-width:1046px; }
.kb-day.no-money { min-width:946px; }

/* ---- the browse-all faux table ---------------------------------------------- */
.kb-tbl { background:var(--bg-primary); border:1px solid var(--border); border-radius:14px;
          box-shadow:var(--shadow-sm); overflow:hidden; }
.kb-tbl-h { border-bottom:1px solid var(--border); background:var(--bg-primary); }
.kb-tbl-h > * { padding:11px 12px; font-size:10.5px; font-weight:700; letter-spacing:0.06em;
                text-transform:uppercase; color:var(--text-muted); }
.kb-tbl-h > :first-child { padding-left:18px; }
.kb-tbl-h > :last-child  { padding-right:18px; }
/* Holds the header and the rows TOGETHER, so between 760px and the track's own width they scroll
   as one instead of the header sitting still while its columns slide out from under it. The page
   never scrolls: the overflow is contained here (same fix as .rpx-legs). */
.kb-scroll { overflow-x:auto; }

/* ---- the column track -------------------------------------------------------
   Code | Guest | Channel | Flat | Dates | Nights | Total | Check-in | State.
   `no-money` is the same track with Total REMOVED, for a viewer without view_finance. The cell is
   gone from the markup, not hidden, so the track has to lose it too or every row below shifts one
   column left of its header. This pair is the whole reason the track cannot live inline.

   Check-in is 150px, not the 104px a chip needs: the board puts the live countdown there, and
   "overdue by 1h 10m" is ~140px of nowrap text that would otherwise print over the State pill. */
.kb-cols { display:grid; align-items:center; min-width:1154px;
           grid-template-columns:150px 1.1fr 110px 1.4fr 120px 66px 116px 150px 100px 108px; }
.kb-cols.no-money { min-width:1054px;
           grid-template-columns:150px 1.1fr 110px 1.4fr 120px 66px 150px 100px 108px; }

/* ---- a row ------------------------------------------------------------------
   display:grid lives on the class, not on a style attribute, so a leg row can be collapsed with
   the plain `hidden` attribute: [hidden] is !important at the top of this file and beats it. The
   old markup had to be re-shown as display:'grid' (never '') precisely because its display was
   inline. Do not put display back in the markup. */
.kb-row { display:grid; border-bottom:1px solid var(--border); font-size:13px; cursor:pointer;
          background:var(--bg-primary); transition:background .12s ease; }
.kb-row:last-child { border-bottom:none; }
.kb-row:hover { background:var(--bg-secondary); }
.kb-row > * { padding:11px 12px; min-width:0; }
.kb-row > :first-child { padding-left:18px; }
.kb-row > :last-child  { padding-right:18px; }
.kb-row.is-leg { background:var(--bg-secondary); }
.kb-row.is-leg:hover { background:var(--bg-tertiary); }
.kb-row.is-leg > * { padding-top:9px; padding-bottom:9px; }
.kb-row.is-leg > :first-child { padding-left:42px; color:var(--text-faint); font-size:11.5px; }
.kb-row.is-cancelled { opacity:.62; }
.kb-row.is-cancelled .kb-code { text-decoration:line-through; }

/* ---- cells ------------------------------------------------------------------ */
.kb-code-cell { display:flex; align-items:center; gap:6px; }
.kb-code { font-family:var(--mono); font-size:12px; font-weight:600; color:var(--accent);
           text-decoration:none; }
.kb-code:hover { text-decoration:underline; }
.kb-mono { font-family:var(--mono); font-size:12px; color:var(--text-primary); }
.kb-num { text-align:right; font-variant-numeric:tabular-nums; color:var(--text-secondary); }
.kb-when { white-space:nowrap; font-variant-numeric:tabular-nums; color:var(--text-secondary); }
.kb-trunc { white-space:nowrap; overflow:hidden; text-overflow:ellipsis; }
.kb-sub { display:block; font-size:11px; font-weight:400; color:var(--text-faint); }
.kb-faint { color:var(--text-faint); font-size:12px; }
.kb-toggle { flex:0 0 auto; width:20px; height:20px; display:inline-flex; align-items:center;
             justify-content:center; padding:0; border:none; border-radius:6px;
             background:var(--bg-tertiary); color:var(--text-muted); cursor:pointer; }
.kb-toggle:hover { background:var(--bg-hover); color:var(--text-primary); }
.kb-toggle svg { display:block; transition:transform .15s ease; }
/* Driven off aria-expanded, which the toggle has to set anyway. The old chevron was turned by
   hand with svg.style.transform, so the state lived in two places. */
.kb-toggle[aria-expanded="true"] svg { transform:rotate(90deg); }

/* ---- row actions -------------------------------------------------------------
   Copy message / Copy link / Send, on the row itself.

   These used to exist ONLY behind right-click, which on a phone is not a gesture that exists:
   iOS Safari does not fire contextmenu on a div at all, and Android's fights native text
   selection. So the actions became a visible control and long-press is emulated nowhere.

   Desktop keeps the list quiet by revealing them on hover - a hundred rows times three buttons
   is noise on a screen that is read far more often than it is acted on. pointer-events goes
   with the opacity, or the invisible buttons would still swallow a click on the row's right
   edge and copy a link when someone meant to open the booking. Keyboard focus re-shows them,
   since Tab reaches a button that pointer-events cannot. */
.kb-acts { display:flex; align-items:center; justify-content:flex-end; gap:5px; }
.kb-acts-in { display:flex; align-items:center; gap:5px;
              opacity:0; pointer-events:none; transition:opacity .12s ease; }
.kb-row:hover .kb-acts-in,
.kb-row:focus-within .kb-acts-in { opacity:1; pointer-events:auto; }
.kb-act { flex:0 0 auto; width:27px; height:27px; display:inline-flex; align-items:center;
          justify-content:center; gap:5px; padding:0; border:1px solid var(--border-strong);
          border-radius:7px; background:transparent; color:var(--text-secondary);
          font:inherit; font-size:11.5px; font-weight:600; cursor:pointer;
          transition:color .12s ease, border-color .12s ease, background .12s ease; }
.kb-act:hover { background:var(--bg-primary); color:var(--text-primary); }
.kb-act svg { display:block; flex:0 0 auto; }
.kb-act-t { display:none; }                      /* the label is a phone affordance */
.kb-act.is-done { color:var(--positive); border-color:var(--positive); }
.kb-act[disabled] { opacity:.4; cursor:not-allowed; }
/* Already sent: the slot reports who rather than offering the button again. This is the
   tracking doing visible work - it is what stops two people chasing the same guest. */
.kb-act-sent { font-size:10.5px; color:var(--text-faint); white-space:nowrap;
               overflow:hidden; text-overflow:ellipsis; max-width:100px; }
.kb-act-more { display:none; }                   /* right-click covers this on desktop */

/* Any device that cannot hover, at ANY width. The phone is handled by the width media query
   below, but a tablet in landscape is wide enough for the desktop layout and still has no
   hover: the actions would be both invisible and pointer-events:none, and the menu button that
   is meant to rescue them is display:none. That is a row with no way in at all, which is the
   exact problem this feature was built to fix, reintroduced one breakpoint over. */
@media (hover: none) {
  .kb-acts-in { opacity:1; pointer-events:auto; }
  .kb-act-more { display:inline-flex; }
}

/* ---- the checkout clock ------------------------------------------------------
   Ticks client-side: the JS rewrites the text and swaps is-soon / is-over. Nothing else. */
.kb-clock { display:inline-flex; align-items:center; gap:5px; padding:3px 9px; border-radius:8px;
            font-family:var(--mono); font-size:12px; font-weight:600; white-space:nowrap;
            font-variant-numeric:tabular-nums;
            background:var(--bg-tertiary); color:var(--text-secondary); }
.kb-clock.is-soon { background:var(--warn-bg);     color:var(--warn); }
.kb-clock.is-over { background:var(--negative-bg); color:var(--negative); }

/* ---- state / secured pills --------------------------------------------------- */
.kb-pill { display:inline-flex; align-items:center; gap:5px; padding:3px 9px; border-radius:999px;
           font-size:10px; font-weight:700; letter-spacing:0.04em; text-transform:uppercase;
           white-space:nowrap; background:var(--bg-tertiary); color:var(--text-muted); }
.kb-pill-confirmed { background:var(--accent-bg);   color:var(--accent); }
.kb-pill-instay    { background:var(--positive-bg); color:var(--positive); }
.kb-pill-past      { background:var(--bg-tertiary); color:var(--text-muted); }
.kb-pill-pending   { background:var(--warn-bg);     color:var(--warn); }
.kb-pill-cancelled { background:var(--negative-bg); color:var(--negative); }
.kb-pill-secured   { background:var(--positive-bg); color:var(--positive); }
/* Unsecured is the RESTING state of every future booking, so it stays quiet - painting 900 rows
   amber would mean nothing. It takes .is-urgent only where somebody has to act today. */
.kb-pill-unsecured { background:var(--bg-tertiary); color:var(--text-muted); }
.kb-pill.is-urgent { background:var(--warn-bg);     color:var(--warn); }

/* ---- pager -------------------------------------------------------------------- */
.kb-pager { display:flex; align-items:center; gap:8px; flex-wrap:wrap; padding:12px 18px;
            border-top:1px solid var(--border); font-size:12px; color:var(--text-muted);
            font-variant-numeric:tabular-nums; }
.kb-pager-n { margin-right:auto; }
.kb-pager-btn { display:inline-flex; align-items:center; justify-content:center; gap:6px;
                min-width:38px; min-height:38px; padding:7px 13px; border-radius:9px;
                border:1px solid var(--border-strong); background:var(--bg-secondary);
                color:var(--text-secondary); font-family:inherit; font-size:12.5px; font-weight:600;
                text-decoration:none; cursor:pointer;
                transition:background .14s ease, color .14s ease; }
.kb-pager-btn:hover { background:var(--bg-hover); color:var(--text-primary); }
.kb-pager-btn.is-on { background:var(--accent); border-color:var(--accent); color:#fff; }
.kb-pager-btn:disabled, .kb-pager-btn[aria-disabled="true"] { opacity:.45; pointer-events:none; }
.kb-pager-btn:focus-visible { outline:2px solid var(--accent); outline-offset:2px; }

.kb-empty { padding:26px 20px; text-align:center; font-size:13.5px; color:var(--text-muted); }
.kb-empty b { color:var(--text-primary); }
/* "no exact match - these are the closest" banner over loose (any-word) search results */
.kb-loosenote { margin:0 0 12px; padding:10px 14px; border:1px solid var(--warn); border-radius:10px;
                background:var(--warn-bg); color:var(--warn); font-size:12.5px; font-weight:500; }
.kb-loosenote b { font-weight:700; }

/* ---- phone --------------------------------------------------------------------
   The rows STACK. Each becomes a card whose cells are label/value lines, the label coming from
   the cell's own data-label attribute - so the header row can be dropped without the numbers
   losing their meaning.

   The label is absolutely positioned in a left gutter rather than being a flex item, because the
   cells hold wildly different content (a bare text node plus a sub-line; a toggle button plus a
   link; two inline spans) and any layout that makes the label the first FLEX or GRID item drags
   the rest of the cell into its flow: the chevron would end up stacked above the booking code.
   Out of flow, the label cannot reorder anything.

   The counter strip goes 2-up, not 4-across - passport/today.html ships four tiles side by side
   on a 390px screen and they are unreadable. */
@media (max-width: 1024px) {
  .kb-stats { grid-template-columns:repeat(2,1fr); }
}
@media (max-width: 760px) {
  .kb-seg { display:flex; width:100%; }
  .kb-seg-item { flex:1; min-height:44px; }
  .kb-stats { gap:10px; }
  .kb-bar-row { flex-direction:column; align-items:stretch; }
  .kb-bar-row > * { width:100%; }
  /* flex-basis is the size on the MAIN axis, and the bar's main axis is now VERTICAL. A control
     left at `flex: 1 1 240px` here is not 240px wide, it is 240px TALL - the container's height is
     auto, so there is no free space to shrink it back. The search box grew into a quarter of the
     screen. Reset the basis with the direction that created it. */
  .kb-in-grow, .kb-in-prop { flex:0 0 auto; }
  .kb-in, summary.kb-more-h, .kb-chip { min-height:44px; }
  .kb-chip { padding:8px 14px; }
  .kb-more-body { grid-template-columns:1fr; }

  .kb-scroll { overflow-x:visible; }
  .kb-tbl-h { display:none; }                       /* the labels are on the cells now */
  .kb-tbl, .kb-sec { background:transparent; border:none; box-shadow:none; border-radius:0;
                     overflow:visible; }
  /* The fold heading IS the control on a phone - the whole strip is the tap target, so it keeps its
     card background here rather than going transparent with the section around it. */
  summary.kb-sec-h { padding:12px 12px; border-bottom:none; min-height:44px;
                     background:var(--bg-primary); border:1px solid var(--border);
                     border-radius:12px; margin-bottom:8px; }
  .kb-sec:not([open]) > summary.kb-sec-h { margin-bottom:0; }
  .kb-sec-sub { display:none; }        /* the count says it; the sentence is desktop furniture */
  .kb-secbar > .rpx-btn { margin-left:0; }
  .kb-secbar { gap:8px; }
  /* min-width:0 with the rest: the desktop band is 1046px, .kb-scroll is no longer a scroll
     container here, and nothing else clips - so the day divider alone would push the whole PAGE
     sideways on a phone, on the default view. */
  .kb-day { padding:10px 2px; background:transparent; border-bottom:none; min-width:0; }

  .kb-cols, .kb-cols.no-money { grid-template-columns:1fr; min-width:0; align-items:stretch; }
  .kb-row { border:1px solid var(--border); border-radius:12px; background:var(--bg-primary);
            box-shadow:var(--shadow-sm); margin:0 0 10px; padding:2px 0; }
  /* Hover is a pointer affordance. On touch it sticks after the tap and the card reads as stuck
     in a pressed state on the way back from the booking page. */
  .kb-row:hover { background:var(--bg-primary); }
  .kb-row.is-leg, .kb-row.is-leg:hover { background:var(--bg-secondary); margin-left:14px; }
  .kb-row > *, .kb-row.is-leg > * { display:flex; flex-wrap:wrap; align-items:center; gap:6px;
                                    min-height:44px; padding:8px 14px; }
  .kb-row > * + * { border-top:1px solid var(--border); }
  .kb-row > [data-label] { position:relative; padding-left:104px; }
  .kb-row > [data-label]::before {
    content:attr(data-label); position:absolute; left:14px; top:0; bottom:0; width:84px;
    display:flex; align-items:center;
    font-size:10px; font-weight:700; letter-spacing:0.06em; text-transform:uppercase;
    color:var(--text-faint);
  }
  .kb-row.is-leg > :first-child { padding-left:14px; }
  /* A spacer cell (a leg row carries four) has to EXIST on desktop to hold its grid track, but
     stacked it would be a blank 44px band. .kb-blank says so out loud; :empty is the belt to its
     braces, since a cell the template leaves genuinely empty is a spacer whether it says so or
     not. Anything a spacer follows keeps its own top border, so nothing is left hanging. */
  .kb-row > .kb-blank, .kb-row > :empty { display:none; }
  .kb-row .kb-sub { flex:0 0 100%; }                /* its own line, aligned under the value */
  .kb-toggle { width:44px; height:44px; }

  /* Row actions on a phone. Hover does not exist here, so they are permanent - and the whole
     reason this feature was built is that the phone had no way in at all.
     Three inline chips read fine on desktop but tip a card that already carries a code, a
     channel, a flat pill and a state pill into clutter, and they put Send a stray thumb from
     Copy. So the phone gets the ONE action this was built for, labelled, plus a menu for the
     rest. Send stays in the menu deliberately: it is the only action that reaches a guest. */
  .kb-acts { justify-content:flex-start; gap:8px; }
  .kb-acts-in { opacity:1; pointer-events:auto; }
  .kb-act { width:auto; min-height:38px; padding:0 13px; border-radius:9px; font-size:12.5px; }
  .kb-act-t { display:inline; }
  .kb-act[data-act="link"], .kb-act[data-act="send"] { display:none; }
  .kb-act-more { display:inline-flex; width:38px; min-height:38px; padding:0;
                 margin-left:auto; }

  .kb-pager { padding:12px 0 0; border-top:none; }
  .kb-pager-n { flex:0 0 100%; order:-1; margin:0 0 4px; }
  .kb-pager-btn { flex:1; min-height:44px; }
  .kb-empty { padding:22px 14px; border:1px dashed var(--border-strong); border-radius:12px; }
}

/* --- Money-mode marking: is this screen real money, or the sandbox? -----------------------
   Deposits and Upsells each ship a near-identical clone on a TEST Stripe account, and the only
   thing telling them apart used to be "(Test)" in the nav label. That is a lot of weight on one
   word when the wrong tab puts a real hold on a real guest's card. Red = real, green = safe.
   Uses the existing --negative / --positive tokens, so both themes are handled already. */
.rp-moneybar {
  display: flex; align-items: baseline; gap: 10px; flex-wrap: wrap;
  padding: 9px 14px; margin: 0 0 16px; border-radius: 8px;
  font-size: 12.5px; line-height: 1.5; border: 1px solid;
}
.rp-moneybar-tag {
  font-size: 10px; font-weight: 800; letter-spacing: .09em;
  padding: 2px 7px; border-radius: 4px; flex: none;
}
.rp-moneybar-live { background: var(--negative-bg); border-color: var(--negative); color: var(--negative); }
.rp-moneybar-live .rp-moneybar-tag { background: var(--negative); color: #fff; }
.rp-moneybar-sandbox { background: var(--positive-bg); border-color: var(--positive); color: var(--positive); }
.rp-moneybar-sandbox .rp-moneybar-tag { background: var(--positive); color: #fff; }
/* The copy has to stay readable; only the tag carries the colour at full strength. */
.rp-moneybar span:not(.rp-moneybar-tag) { color: var(--text-primary); }

/* The same signal in the sidebar, so it is visible BEFORE the click that costs money. */
/* margin-left:auto, not 6px: pinned to the right edge these line up in one column with the
   count badges, which read as the row's status either way. Trailing the label instead left
   them ragged at four different x positions - and made the longest row the widest one. */
.rp-navtag {
  font-size: 8.5px; font-weight: 800; letter-spacing: .07em; flex: none;
  padding: 1px 4px; border-radius: 3px; margin-left: auto; vertical-align: 1px;
}
.rp-navtag-live { background: var(--negative); color: #fff; }
.rp-navtag-sandbox { background: var(--positive); color: #fff; }

/* ------------------------------------------------ deposit request panel (0104)
   The panel answers before it asks: a computed verdict sits above the tiles, so staff read what
   is possible instead of working it out from the dates. */
.rpx-dres-verdict {
  margin-top: 12px; padding: 13px 15px; border-radius: 12px; display: flex; gap: 12px;
  align-items: flex-start; border: 1px solid var(--border-strong); background: var(--bg-secondary);
}
.rpx-dres-verdict.is-go { border-color: color-mix(in srgb, var(--positive) 40%, transparent); background: var(--positive-bg); }
.rpx-dres-verdict.is-wait { border-color: color-mix(in srgb, var(--warn) 40%, transparent); background: var(--warn-bg); }
.rpx-dres-verdict.is-stop { border-color: color-mix(in srgb, var(--negative) 40%, transparent); background: var(--negative-bg); }
.rpx-dres-vdot {
  width: 9px; height: 9px; border-radius: 50%; flex: none; margin-top: 6px; background: var(--text-muted);
}
.rpx-dres-verdict.is-go .rpx-dres-vdot { background: var(--positive); box-shadow: 0 0 0 4px color-mix(in srgb, var(--positive) 18%, transparent); }
.rpx-dres-verdict.is-wait .rpx-dres-vdot { background: var(--warn); box-shadow: 0 0 0 4px color-mix(in srgb, var(--warn) 18%, transparent); }
.rpx-dres-verdict.is-stop .rpx-dres-vdot { background: var(--negative); box-shadow: 0 0 0 4px color-mix(in srgb, var(--negative) 18%, transparent); }
.rpx-dres-vh { font-size: 14.5px; font-weight: 600; letter-spacing: -0.018em; }
.rpx-dres-vp { font-size: 12.5px; line-height: 1.62; color: var(--text-secondary); margin: 5px 0 0; }

/* Why the guest asked, in their words. It outlives the answer. */
.rpx-dres-said {
  margin-top: 12px; padding: 11px 14px; border-left: 2px solid var(--accent);
  border-radius: 0 10px 10px 0; background: var(--accent-bg);
  font-size: 12.5px; line-height: 1.6; color: var(--text-primary);
}
.rpx-dres-said cite { display: block; margin-top: 6px; font-style: normal; font-size: 10.5px; color: var(--text-muted); }

/* The recommended route, and the two flavours of cost line. */
.rpx-dres-opt.is-best { border-color: color-mix(in srgb, var(--accent) 45%, transparent); background: var(--accent-bg); }
.rpx-dres-cost.is-ok { background: var(--positive-bg); color: var(--positive); }
.rpx-dres-cost.is-stop { background: var(--negative-bg); color: var(--negative); }
/* The panel is a <details> now. It opens by itself whenever something needs answering, and the
   summary only exists for the folded case - so hide it whenever the panel is NOT folded, rather
   than duplicating the heading that is already inside. */
/* A folded panel is NOT an alert. The amber border and amber head exist because this panel used
   to appear only when a leg was stuck; on an ordinary booking, where it is now simply the place
   a deposit is granted, that colouring says "something is wrong here" about a booking where
   nothing is. Neutral until opened, and neutral inside too. */
.rpx-dres-fold { border-color: var(--border-strong); }
.rpx-dres-fold .rpx-dres-head { background: var(--bg-secondary); }
.rpx-dres > summary { display: none; }
.rpx-dres-fold > summary { display: flex; align-items: center; gap: 10px; cursor: pointer;
  padding: 14px 20px; font-size: 13.5px; color: var(--text-secondary); list-style: none; }
.rpx-dres-fold > summary::-webkit-details-marker { display: none; }
.rpx-dres-fold > summary:hover { color: var(--text-primary); background: var(--bg-secondary); }
.rpx-dres-fold > summary strong { color: var(--text-primary); font-weight: 600; }
.rpx-dres-fold > summary:focus-visible { outline: 2px solid var(--accent); outline-offset: -2px; }
.rpx-dres-sumchev { flex: none; color: var(--text-faint); transition: transform .15s ease; }
.rpx-dres-fold[open] > summary .rpx-dres-sumchev { transform: rotate(90deg); }
@media (prefers-reduced-motion: reduce) { .rpx-dres-sumchev { transition: none; } }

/* The renewing hold's disclaimer. Not one of the situational cost lines beside it: those say
   whether this route suits THIS stay, and this one says what the approval commits us to every
   time it is used. It carries a rule down its edge so it is not read as another hint. */
.rpx-dres-cost.is-terms { border-left: 3px solid var(--warn); border-radius: 4px 8px 8px 4px; }
/* Marks an approval that needs its own grant, so the restriction is visible to everyone and not
   only discovered by the people who cannot use it. */
.rpx-dres-gated { font-size: 9.5px; font-weight: 700; letter-spacing: .07em; text-transform: uppercase;
                  padding: 2px 7px; border-radius: 999px; vertical-align: 2px; white-space: nowrap;
                  background: var(--warn-bg); color: var(--warn); }

/* The equipment grid is five columns on a desktop and one on a phone; the note's indent lines
   it up under the value box, which only exists while there are columns to line up with. */
@media (max-width: 720px) {
  .rp-eq-grid { grid-template-columns: 1fr !important; }
  .rp-eq-note { margin-left: 0 !important; width: 100% !important; }
}
