/* ============================================================
   REALUMA AGENT PLATFORM — design system
   Same brand DNA as the marketing site: champagne = brand,
   teal = the AI. Light (ivory morning) / dark (obsidian dusk).
   Mobile-first: bottom nav <900px, sidebar ≥900px.
   ============================================================ */
:root{
  --bg:#FBFAF6; --bg-alt:#F5F1E8; --surface:#FFFFFF; --surface-2:#F5F1E8;
  --text:#16333A; --text-2:#2F5B63; --muted:rgba(22,51,58,.58);
  --brand:#C9A170; --brand-ink:#8F6B36; --brand-contrast:#16333A;
  --ai:#4E8593; --ai-strong:#2F5B63; --ai-wash:#DDE9EB;
  --line:rgba(143,107,54,.28); --danger:#B0574C; --ok:#4E8593;
  --shadow:0 8px 28px rgba(22,51,58,.08);
  /* The pre-glass names for the same three faces. Kept in step with --rg-display and --rg-body in
     realuma-glass.css, which carry the reason for the swap. */
  --display:'Playfair Display',serif; --body:'Be Vietnam Pro',sans-serif; --mono:'IBM Plex Mono',monospace;
  /* RADIUS, four steps plus two structural values. It replaced 19 distinct values across 98
     declarations, of which 12 were single values doing the work of four. Declared HERE and not in
     the dark block below because a corner does not change with the theme.
     WHAT STAYS A LITERAL, deliberately: the four compound corner values (two sheet tops, two chat
     bubble tails), where the asymmetry IS the shape, and one `0`. Rounding those onto a step would
     square a sheet or straighten a bubble tail. */
  --r-1:4px; --r-2:8px; --r-3:12px; --r-4:20px; --r-pill:9999px; --r-circle:50%;
  /* PADDING IS DELIBERATELY NOT A SCALE, and these names say so: they are ROLES, not steps.
     A scale was surveyed and rejected. 105 declarations carry 69 distinct values, 44 of them
     two-value pairs used exactly once, and a 6-step scale would have moved about 78 of them.
     THE REASON IT WOULD HAVE BEEN WRONG: a pair is not a size. `.bubble` is 9px 13px and
     `.takeover-bar` is 9px 14px, and they are near-identical BECAUSE they sit adjacent and were
     tuned against each other. Rounding those onto steps pulls them APART. Radius had no such
     problem: a corner has no content beyond its size.
     So this is deduplication, not a system. Every token below is a value that ALREADY repeats,
     named for what its members are. NOTHING MOVED when they were applied. A one-off padding stays
     a literal and that is correct, not a gap waiting to be filled. */
  --p-row:12px 14px;      /* list rows, menu entries, chat head */
  --p-control:11px 12px;  /* inputs and selects, sidebar links, calendar cards */
  --p-notice:10px 12px;   /* bordered notice and preview strips */
  --p-empty:28px 10px;    /* empty and refused blocks */
  --p-chip:3px 8px;       /* mini amenity chips, import badges */
  --p-toggle:6px 12px;    /* segmented toggles, sort buttons */
  --p-card:14px;          /* the larger card inset */
  --p-block:12px;         /* small self-contained blocks */
  --p-field:10px;         /* text surfaces: log, textarea, link box */
  --p-tight:8px;          /* tight insets, sidebar foot and language toggle */
  --p-screen:24px;        /* full-screen overlays */
}
[data-theme="dark"]{
  --bg:#0E1518; --bg-alt:#101A1E; --surface:#17282E; --surface-2:#132126;
  --text:#F0EBDF; --text-2:#9DB4B8; --muted:rgba(240,235,223,.55);
  --brand:#C9A170; --brand-ink:#C9A170; --brand-contrast:#0E1518;
  --ai:#5FA0AE; --ai-strong:#5FA0AE; --ai-wash:#1E3A40;
  --line:rgba(201,161,112,.24); --danger:#D98A7F; --ok:#5FA0AE;
  --shadow:0 10px 34px rgba(0,0,0,.4);
}
*{margin:0;padding:0;box-sizing:border-box}
/* The document itself never scrolls — the fixed #app root owns a single scroll region,
   so iOS touch scrolling has nothing to pan (overflow-x:clip on body does NOT stop it). */
html,body{height:100%;overflow:hidden;overscroll-behavior:none;background:var(--bg)}
body{background:var(--bg);color:var(--text);font-family:var(--body);font-size:15px;line-height:1.55;
  transition:background .4s,color .4s}
.hidden{display:none!important}
button{font-family:var(--body);cursor:pointer}
input,select,textarea{font-family:var(--body);font-size:15px;color:var(--text);
  background:var(--surface);border:1px solid var(--line);border-radius:var(--r-2);padding:var(--p-control);width:100%}
input:focus,select:focus,textarea:focus{outline:2px solid var(--ai);outline-offset:1px}
label{display:block;font-size:13px;color:var(--text-2);margin:12px 0 6px}
.wordmark{font-family:var(--display);letter-spacing:.2em;color:var(--brand-ink);font-size:17px}
[data-theme="dark"] .wordmark{color:var(--brand)}
.mono{font-family:var(--mono)}

/* ---------- buttons ---------- */
.btn{display:inline-block;padding:11px 20px;border:1px solid var(--brand);background:transparent;
  color:var(--brand-ink);font-family:var(--mono);font-size:12px;letter-spacing:.1em;text-transform:uppercase;
  border-radius:var(--r-2);transition:all .15s}
.btn:hover{background:var(--brand);color:var(--brand-contrast)}
.btn-primary{background:var(--brand);color:var(--brand-contrast);font-weight:600}
.btn-primary:hover{filter:brightness(1.06)}
.btn-ai{border-color:var(--ai);color:var(--ai-strong)}
.btn-ai:hover{background:var(--ai);color:#fff}
.btn-danger{border-color:var(--danger);color:var(--danger)}
.btn-danger:hover{background:var(--danger);color:#fff}
.btn-block{width:100%;text-align:center}
.btn-sm{padding:7px 12px;font-size:11px}

/* ---- THE CHIP, and it is NOT a .btn ----------------------------------------------------------
   Three families were the same control drawn three times: a filter value, the filter opener, and an
   amenity toggle. All rounded, all body font, all sentence case, and all differing only by accident
   in padding and half a pixel of type.
   IT DOES NOT FOLD INTO .btn AND MUST NOT. `.btn` is uppercase mono with .1em tracking; a filter
   reading BỘ LỌC in tracked mono would be wrong rather than consistent. A chip is a value you toggle,
   not an action you commit.
   Declared HERE, early, because .amen-chip overrides its background further down and a base placed
   after it would win. `.chip` is in the selector list so future markup can use the name directly;
   the three existing families are folded without touching a single handler. */
.chip,.filters button,.filter-btn,.amen-chip{
  border:1px solid var(--line);border-radius:var(--r-4);background:none;
  padding:7px 13px;font-family:var(--body);font-size:13px;color:var(--text-2)}

/* ---- touch feedback: every button dips on press — scale 0.97 + slight opacity drop,
   80ms in / 120ms out, transform + opacity only. The pill nav keeps its own press feedback
   (the AI button carries a translateY that a bare scale would wipe). ---- */
.btn, a.btn,
button:not(.rg-tab):not(.rg-orb){
  transition: transform 120ms ease, opacity 120ms ease,
    background .15s ease, color .15s ease, border-color .15s ease, filter .15s ease;
}
.btn:active, a.btn:active,
button:not(.rg-tab):not(.rg-orb):active{
  transform: scale(0.97); opacity: .9;
  transition: transform 80ms ease, opacity 80ms ease;
}
@media (prefers-reduced-motion: reduce){
  .btn:active, a.btn:active,
  button:not(.rg-tab):not(.rg-orb):active{ transform: none; }   /* no scale under reduced motion; opacity feedback only */
}

/* ---------- login ---------- */
/* Same fixed-root + single-scroll-region contract as .app-scroll: pan-y stops the horizontal
   drag at the gesture level (the only thing that actually stops it on iOS — body overflow does
   not), overflow-x:hidden clips, overscroll-behavior:contain kills the rubber-band. */
/* NO BACKGROUND, and that omission is the whole of this rule's history. .rg-world has been the
   first child of body since the glass port began, and it is behind this screen exactly as it is
   behind every other one: same paint bucket, no z-index on either, DOM order deciding. It was
   invisible here for one reason only, a radial of --bg-alt to --bg painted straight over it, so
   the first screen a stranger sees was the only screen in the product with no world in it.
   .login-screen keeps every other line: it is still the fixed root and the single scroll region
   for this screen, and the card above it is still opaque, so nothing that has to be read is now
   being read against something that moves. */
.login-screen{position:fixed;top:0;left:0;right:0;height:100dvh;overflow-y:auto;overflow-x:hidden;touch-action:pan-y;   /* dvh, not the ICB: see .rg-sheet */
  overscroll-behavior:contain;-webkit-overflow-scrolling:touch;
  display:grid;place-items:safe center;padding:var(--p-screen)}
/* THE CARD IS GLASS NOW, so it declares no material of its own: the fill, the rim, the radius and
   the cast all come from .rg-g in the markup. Width and padding are all that is left, because
   those are this screen's layout rather than the system's material. */
.login-card{width:min(400px,100%);padding:36px}
.login-card .rg-btn{margin-top:var(--rg-sp3)}
.wordmark-lg{font-family:var(--rg-display);letter-spacing:.24em;color:var(--rg-champ);font-size:26px;text-align:center}
.login-sub{text-align:center;color:var(--rg-muted);margin:6px 0 18px;font-size:14px}
.login-note{margin-top:16px;font-size:12px;color:var(--rg-muted);text-align:center;line-height:1.5}
.login-note a{color:var(--rg-champ);text-decoration:underline}
/* signup / verify / pending */
.auth-panel{display:flex;flex-direction:column}
.auth-lang{display:flex;justify-content:center;margin:2px 0 16px}
.su-hint{font-size:12px;color:var(--rg-muted);margin:6px 0 2px;line-height:1.5}
/* password reveal — the eye sits inside the field; a slash appears when the password is shown */
.pw-field{position:relative}
.pw-field input{padding-right:44px}
.pw-reveal{position:absolute;right:4px;top:0;bottom:0;width:38px;display:flex;align-items:center;justify-content:center;
  background:none;border:0;padding:0;margin:0;color:var(--rg-muted);cursor:pointer;border-radius:var(--rg-r-sm)}
.pw-reveal:hover{color:var(--rg-text)}
.pw-reveal:active{transform:scale(.9);opacity:.75}
/* MEASURED 16 Aug: 38px wide against a 44px floor. Tall enough already, because it stretches the
   whole field (top:0;bottom:0).
   IT CANNOT BECOME 44 WIDE. The input reserves exactly 44px of right padding for it, and 44 plus
   its 4px offset is 48, so a wider box would put the eye over the last characters of the password
   somebody is trying to read. Six pixels of transparent area instead, three on each side, which
   costs the input three pixels of its right edge and moves no glyph.
   ::before AND NOT ::after, because ::after is already the slash that appears when the password is
   shown. Two rules on one pseudo-element is how one of them silently stops existing. */
.pw-reveal::before{content:'';position:absolute;inset:0 -3px}
/* CHAMPAGNE, NOT TEAL, AND THIS WAS A LAW VIOLATION. Revealing a password is A HUMAN ACTING: she
   pressed it, and the state it leaves behind is hers. It painted var(--ai), which is reserved for
   what Realuma AI produced, and a control wearing that colour spends the one signal that means it. */
.pw-reveal.pw-on{color:var(--rg-champ)}
.pw-reveal.pw-on::after{content:'';position:absolute;left:50%;top:50%;width:22px;height:1.8px;background:currentColor;
  transform:translate(-50%,-50%) rotate(-45deg);border-radius:var(--r-1)}
.seg{display:flex;gap:8px;margin-top:2px}
.seg button{flex:1;padding:10px 6px;border:1px solid var(--line);border-radius:var(--r-2);background:var(--surface);
  color:var(--text-2);font-size:13px;font-family:var(--body);cursor:pointer}
.seg button.active{border-color:var(--ai);background:var(--ai-wash);color:var(--ai-strong)}
[data-theme="dark"] .seg button.active{color:var(--ai)}
.verify-lead{color:var(--rg-muted);font-size:14px;text-align:center;margin-bottom:2px}
.verify-email{font-weight:600;text-align:center;margin-bottom:16px;word-break:break-all}
input.verify-input{text-align:center;font-family:var(--rg-mono);font-size:24px;letter-spacing:.35em}
.pending-card{text-align:center}
.pending-badge{font-size:40px;color:var(--brand);opacity:.7;margin:10px 0 6px;line-height:1}
.pending-title{font-family:var(--display);font-weight:400;font-size:22px;line-height:1.3;margin-bottom:10px}
.pending-body{color:var(--muted);font-size:14px;line-height:1.6;margin-bottom:22px}
/* onboarding gate: plan picker / VietQR payment / waiting */
/* GLASS NOW, like .login-card: the fill, the rim, the radius and the cast come from .rg-g in
   the markup. Width and padding are this screen's layout and stay. */
.gate-wrap{width:min(440px,100%);
  border-radius:var(--r-3);box-shadow:var(--shadow);padding:28px 24px}
.gate-wrap .wordmark-lg{margin-bottom:4px}
.gate-h{font-family:var(--rg-display);font-weight:400;font-size:21px;text-align:center;margin:6px 0 18px}
.gate-sub{color:var(--rg-muted);font-size:14px;text-align:center;line-height:1.6;margin-bottom:16px}
.gate-center{text-align:center}
.gate-signout{margin-top:16px}
.plan-grid{display:flex;flex-direction:column;gap:14px}
.plan-card{margin:0}
.plan-name{font-family:var(--rg-display);font-size:19px}
.plan-price{font-family:var(--rg-mono);font-size:22px;color:var(--rg-champ);margin:2px 0 12px}
.plan-per{font-size:12px;color:var(--rg-muted);margin-left:2px}
.plan-incl{list-style:none;display:flex;flex-direction:column;gap:7px;margin:0 0 14px;padding:0}
.plan-incl li{font-size:13.5px;line-height:1.4;color:var(--rg-muted);padding-left:18px;position:relative}
.plan-incl li::before{content:'✦';position:absolute;left:0;top:2px;font-size:11px;color:var(--rg-champ)}
.plan-setup{font-size:12.5px;color:var(--rg-muted);margin-bottom:6px}
.plan-waived{color:var(--ai-strong);font-weight:600}
[data-theme="dark"] .plan-waived{color:var(--ai)}
.plan-total{font-size:14px;margin-bottom:12px}
.plan-total b{font-family:var(--rg-mono)}
.plan-price s{color:var(--muted)}
.plan-free{font-family:var(--mono);font-size:12px;letter-spacing:.04em;color:var(--ai-strong);margin:-6px 0 12px}
[data-theme="dark"] .plan-free{color:var(--ai)}
.pay-total{text-align:center;font-size:14px;color:var(--rg-muted);margin-bottom:12px}
.pay-total b{display:block;font-family:var(--rg-mono);font-size:24px;color:var(--rg-text);margin-top:2px}
.pay-qr{display:flex;justify-content:center;margin:4px 0 8px}
.pay-qr img{width:220px;height:220px;border:1px solid var(--line);border-radius:var(--r-3);background:#fff;padding:var(--p-tight)}
.pay-hint{font-size:12.5px;color:var(--rg-muted);text-align:center;line-height:1.5;margin-bottom:14px}
.pay-details{padding:2px 0;margin-bottom:10px}
.pay-kv{gap:12px}
.pay-kv span{flex:none}
.pay-kv b{font-family:var(--rg-mono);text-align:right;word-break:break-all}
.pay-ref b{color:var(--rg-champ);font-size:15px}
.pay-memo-warn{margin-bottom:14px}
.pay-change{margin-top:10px}
.wait-badge{margin:6px auto 14px}
.wait-ref{font-size:13px;color:var(--rg-muted);margin-bottom:18px}
.wait-ref b{font-family:var(--rg-mono);color:var(--rg-champ)}
.partner-card{display:flex;align-items:center;gap:12px;text-align:left}
.partner-av{width:46px;height:46px;border-radius:50%;flex:none;overflow:hidden;display:grid;place-items:center;
  background:var(--rg-champ);color:var(--rg-ink);font-family:var(--rg-mono);font-size:16px}
.partner-av img{width:100%;height:100%;object-fit:cover}
.partner-info{flex:1;min-width:0}
.partner-lead{font-size:11px;color:var(--rg-muted);text-transform:uppercase;letter-spacing:.05em;font-family:var(--rg-mono)}
.partner-name{font-family:var(--rg-display);font-size:17px}
/* pending screen: partner introduced large + centered (no phone, no message button) */
.partner-av-lg{width:84px;height:84px;font-size:28px;margin:6px auto 12px}
.partner-name-lg{font-size:22px;margin-bottom:14px}
/* operator: menu entry + queue */
.more-ops{display:flex;align-items:center;gap:12px;min-height:48px;padding:var(--p-row);margin:10px 0 0;
  border:1px solid var(--ai);border-radius:var(--r-3);background:color-mix(in srgb,var(--ai) 10%,transparent);
  color:var(--ai-strong);text-decoration:none;font-size:14.5px;font-weight:600}
[data-theme="dark"] .more-ops,[data-theme="dark"] .more-ops .more-ic{color:var(--ai)}
.more-ops .more-ic{color:var(--ai-strong)}
/* The SAME active treatment .more-grid a.active uses, not a second one: champagne border, champagne
   text. This entry carries data-nav="ops" as of 00cd3da, so route() has been adding .active to it
   ever since and nothing rendered. The icon is recoloured too because .more-ops sets it explicitly,
   where a grid entry's icon simply inherits. */
.more-ops.active{border-color:var(--brand);color:var(--brand-ink)}
.more-ops.active .more-ic{color:var(--brand-ink)}
[data-theme="dark"] .more-ops.active,[data-theme="dark"] .more-ops.active .more-ic{color:var(--brand)}
/* Read-only amenity strip on the property card */
.amen-mini-row{display:flex;flex-wrap:wrap;gap:5px;margin-top:6px}
.amen-mini{font-size:11px;line-height:1;padding:var(--p-chip);border-radius:var(--r-2);background:var(--surface-2);
  color:var(--text-2);border:1px solid var(--line)}
.amen-mini.more{color:var(--muted)}
.amen-warn{font-size:11px;line-height:1;padding:var(--p-chip);border-radius:var(--r-2);
  background:rgba(200,60,60,.12);color:var(--danger);border:1px solid rgba(200,60,60,.3)}

/* Amenity chips (property form) — tap to toggle */
.chip-row{display:flex;flex-wrap:wrap;gap:8px}
/* Only what differs from the chip base: a surface fill, because these sit on a card rather than in
   a toolbar, and a flush line-height because they wrap to several rows. */
.amen-chip{background:var(--surface);line-height:1}
.amen-chip.on{border-color:var(--ai);background:var(--ai-wash);color:var(--ai-strong)}
[data-theme="dark"] .amen-chip.on{color:var(--ai)}

/* ---------- THE BUSINESS LOGO PREVIEW (settings) ----------
   ONE NEW CLASS, AND HERE IS WHY NOTHING FITS. The system has two ways of showing an image and
   neither can show this one:
     .ph-thumb   aspect-ratio:1 with object-fit:cover, i.e. it CROPS. A listing photo cropped to a
                 square is still that flat. A logo cropped to a square is a logo with its name cut
                 off, and it would be cropped differently for every account.
     .mr-photo   object-fit:contain, but it is a full-width evidence frame up to 340px tall, built
                 for a photograph of a meter face somebody has to read digits off.
   A logo needs contain, at a small fixed height, on a ground that is not the page.
   THE GROUND IS THE POINT OF THE SECOND RULE. A logo is the one image in this product that
   routinely arrives with a transparent background, and this app has two themes, so a dark logo on
   the night ground and a pale one on the day ground would each vanish into it. A neutral panel
   behind it means the preview shows what the file actually contains in both. */
.set-logo{display:flex;align-items:center;justify-content:center;padding:14px;margin-bottom:12px;
  background:var(--surface-2);border:1px solid var(--line);border-radius:var(--r-2)}
.set-logo img{display:block;max-width:100%;max-height:88px;object-fit:contain}

/* Telegram connection panel (settings) */
.tg-intro{color:var(--muted);font-size:13.5px;line-height:1.6;margin-bottom:12px}
.tg-steps{margin:0 0 14px;padding-left:22px;display:flex;flex-direction:column;gap:9px}
.tg-steps li{font-size:14px;line-height:1.55}
.tg-namebox{display:flex;align-items:center;gap:10px;flex-wrap:wrap;margin-top:8px;padding:var(--p-notice);
  background:var(--surface-2);border:1px solid var(--line);border-radius:var(--r-2)}
.tg-name{font-family:var(--display);font-size:16px;flex:1;min-width:0;word-break:break-word}
.tg-addr-lead{color:var(--muted);font-size:13px;margin:12px 0 6px}
.tg-link-row{display:flex;align-items:center;gap:10px;flex-wrap:wrap;padding:var(--p-control);
  background:var(--surface-2);border:1px solid var(--line);border-radius:var(--r-2)}
.tg-link{flex:1;min-width:0;font-family:var(--mono);font-size:15px;color:var(--brand-ink);font-weight:600;
  overflow:hidden;text-overflow:ellipsis;white-space:nowrap;text-decoration:none}
[data-theme="dark"] .tg-link{color:var(--brand)}
.tg-banner{background:rgba(200,60,60,.12);border:1px solid var(--danger);color:var(--danger);
  border-radius:var(--r-2);padding:var(--p-notice);font-size:13.5px;margin-bottom:12px}
.ops-confirm-q{color:var(--muted);font-size:13.5px;margin-bottom:10px}
/* fixed-height scroll box: 160 photos write ~200 lines, and letting the page grow by that much
   while the run is in flight moves the button out from under the operator's finger */
.ops-pm-log{margin-top:12px;max-height:340px;overflow:auto;white-space:pre-wrap;word-break:break-word;
  font-family:var(--mono);font-size:12px;line-height:1.5;background:var(--bg);border:1px solid var(--line);
  border-radius:var(--r-2);padding:var(--p-field);color:var(--muted)}

/* ---------- meter reading ----------
   FOUR CLASSES SURVIVED THE PORT and every one of them is SCREEN SPECIFIC, which is why they are
   here and not in realuma-glass.css. Same posture as .ten-snd-* and .cal-*: the design system owns
   what more than one screen uses, this file owns what one screen needs. What went: .mr-head,
   .mr-card, .mr-status, .mr-legend, .mr-note, .mr-flag and .mr-nophoto's old chrome, replaced by
   rg-bar, rg-head, rg-g, rg-help, rg-warn and rg-empty, which already existed.
   THEY ARE WRITTEN IN --rg-* TOKENS, unlike the rest of this file. A screen-specific class on a
   PORTED screen still has to follow both themes, and the legacy --line and --muted do not: the
   day theme is declared entirely in rg tokens. This block is what the surviving four look like
   when they are made to follow the theme instead of the file they live in. */
.mr-drop{width:100%;display:flex;align-items:center;justify-content:center;gap:9px;min-height:56px;
  margin-bottom:var(--rg-sp2);padding:0 var(--rg-sp3);cursor:pointer;
  border:1px dashed var(--rg-champ-rim);border-radius:var(--rg-r-sm);
  background:var(--rg-champ-wash);color:var(--rg-text);
  font-family:var(--rg-body);font-size:13.5px;font-weight:500;
  transition:transform var(--rg-t-mid) var(--rg-spring), background var(--rg-t-fast)}
.mr-drop:active{transform:scale(.985);background:rgba(201,161,112,.14)}
.mr-drop-ic{font-size:19px;color:var(--rg-champ);line-height:1}
.mr-photo{margin-bottom:var(--rg-sp2);border-radius:var(--rg-r-sm);overflow:hidden;
  background:rgba(0,0,0,.22);box-shadow:inset 0 0 0 1px var(--rg-edge)}
.mr-photo img{display:block;width:100%;max-height:340px;object-fit:contain;cursor:zoom-in}
.mr-nophoto{padding:26px 18px;text-align:center;color:var(--rg-muted);font-size:var(--rg-fs-sm)}
/* THE DIGITS ARE THE POINT OF THE TEAL CARD. This is what a person checks against a photograph
   held at arm's length, so it is the one element on the screen sized for that rather than for the
   layout. THE UNSURE MARK IS NOT COLOUR ALONE: an uncertain digit takes a champagne underline AND
   the count is stated in words directly beneath, because "which digit" and "how many" are two
   different questions and a person reading in sunlight may resolve neither from a tint. */
.mr-digits{display:flex;gap:5px;flex-wrap:wrap;margin-bottom:var(--rg-sp2)}
.mr-d{font-family:var(--rg-mono);font-size:26px;line-height:1;padding:9px 8px;
  border-radius:var(--rg-r-sm);color:var(--rg-text);
  background:rgba(0,0,0,.24);box-shadow:inset 0 0 0 1px var(--rg-edge)}
/* THE DISCARDED TAIL IS QUIETER IN COLOUR, NEVER IN OPACITY. It was opacity:.45, which measured
   4.13:1 at night and 2.42:1 in day: a digit off a real meter, dimmed below legibility to say it
   does not count toward the bill. These are still digits a person checks against a photograph, and
   the same lesson the loaded-turn dimming taught applies here. The tail is now told apart by ink
   weight and by the legend beneath, both of which survive being read in sunlight. */
.mr-d.disc{color:var(--rg-muted)}
.mr-d.unsure{box-shadow:inset 0 0 0 1px var(--rg-champ-rim), inset 0 -3px 0 var(--rg-champ)}
.mr-reading{font-family:var(--rg-mono);font-size:22px;letter-spacing:.04em}
.mr-consume{margin-bottom:var(--rg-sp2);font-family:var(--rg-mono);
  font-size:var(--rg-fs-sm);color:var(--rg-muted)}
.mr-consume b{color:var(--rg-text);font-weight:500}
/* THE DIAGNOSTIC LINE UNDER AN OCR FAILURE, and it is deliberately the only untranslated string
   on this screen. The sentence above it tells the landlord what to do in their own language;
   this carries the status and the words the server itself used, so a photographed screen is
   diagnosable by somebody who is not standing next to it.
   IT ADDS TWO PROPERTIES AND NOTHING ELSE. Size, colour, line height and margin all come from
   .rg-help, which lives in realuma-forms.css and therefore LOADS AFTER this file: at equal
   specificity anything restated here would lose, exactly as .mr-err found out above. Mono,
   because it is a status code and a machine sentence, and it wraps rather than truncating
   because a cut-off cause is worse than a long line. */
.mr-why{font-family:var(--rg-mono);word-break:break-word}
/* .rg-err CARRIES THE LOOK AND .mr-err CARRIES ONLY THE SHOW AND HIDE, and it needs !important
   for a reason worth writing down: realuma-forms.css loads AFTER this file, so .rg-err's
   `display:flex` beat a plain `display:none` here and the error block sat on the screen
   permanently, showing its bare ! badge with no message beside it. Same specificity, later file
   wins. The alternative was giving the element .rg-err only while it has something to say, which
   means the class list becomes state and two places decide whether an error is visible. */
.mr-err{display:none !important;margin-top:var(--rg-sp2)}
.mr-err.on{display:flex !important}
[data-theme="light"] .mr-photo,
[data-theme="light"] .mr-d{background:rgba(22,51,58,.05)}

/* ---------- tenancy ---------- */
.ten-head{display:flex;justify-content:space-between;align-items:center;gap:10px;margin-bottom:14px}
.ten-sec{display:flex;justify-content:space-between;align-items:center;gap:10px;margin:20px 0 8px}
.ten-sec h4{margin:0;font-size:12px;font-weight:600;letter-spacing:.06em;text-transform:uppercase;color:var(--muted)}
.ten-card-h{display:flex;justify-content:space-between;align-items:center;gap:10px;margin-bottom:10px}
.ten-card-h h3{margin:0}
.ten-item{display:flex;align-items:center;gap:10px;background:var(--surface);border:1px solid var(--line);
  border-radius:var(--r-3);padding:var(--p-row);margin-bottom:8px}
.ten-item.off{opacity:.55}
.ten-item-b{flex:1;min-width:0}
.ten-item .t1{font-weight:600;font-size:14.5px}
.ten-item .t2{font-size:12.5px;color:var(--muted);margin-top:2px}
.ten-field{margin-bottom:2px}
.ten-field.hidden{display:none}
/* the reason a field is disabled sits WITH the field, not in a toast that has already gone */
.ten-frozen{margin-top:6px;font-size:12px;color:#c98a00;line-height:1.45}
.ten-err{margin-top:12px;font-size:13px;color:#c0392b;display:none;white-space:pre-wrap}
.ten-err.on{display:block}
/* The invite panel is INLINE on the row, never an overlay: a bearer credential shown once must not
   be destroyable by a backdrop tap, Escape or a swipe. Bordered in brand so it reads as part of the
   row it belongs to rather than as a floating notice. Sized for a phone, the only device this is
   used on today. */
.ten-inv{background:var(--surface-2);border:1px solid var(--brand);border-radius:var(--r-3);
  padding:var(--p-card);margin:-4px 0 10px;font-size:13.5px;line-height:1.5}
.ten-inv h5{margin:0 0 2px;font-family:var(--display);font-size:17px;font-weight:400}
.ten-inv-who{color:var(--muted);font-size:12.5px;margin-bottom:10px}
.ten-inv-once{color:#c0392b;font-weight:600;margin-bottom:10px}
/* Monospace and wrapping, so all of the roughly 70 characters are visible at once. An input would
   scroll them horizontally and the operator could never see the whole link. */
.ten-inv-link{width:100%;font-family:ui-monospace,SFMono-Regular,Menlo,monospace;font-size:12.5px;
  line-height:1.45;padding:var(--p-field);border:1px solid var(--line);border-radius:var(--r-2);background:var(--surface);
  color:var(--text);resize:none;word-break:break-all;margin-bottom:10px}
.ten-inv-fail{display:none;margin-top:8px;color:#c0392b}
.ten-inv-fail.on{display:block}
.ten-inv-next{margin-top:12px;color:var(--text-2)}
.ten-inv-exp{margin-top:6px;color:var(--muted);font-size:12.5px}
/* 44px so the acknowledgement is a real touch target and not a mis-tap next to Done. */
.ten-inv-ack{display:flex;align-items:center;gap:9px;margin:14px 0 8px;min-height:44px}
.ten-inv-ack input{width:20px;height:20px;flex:none}
.ten-inv-hint{color:var(--muted);font-size:12px;margin-top:6px}
.ten-inv-hint.hidden{display:none}
.ten-inv-err{color:#c0392b;white-space:pre-wrap;margin-bottom:12px}
.ten-inv .btn+.btn{margin-top:8px}
.ten-item .t3{font-size:12.5px;margin-top:3px;color:var(--brand-ink)}
.ten-item .t3.warn{color:#c98a00}
/* The send panel reuses .ten-inv: both are an inline panel guarding an act that cannot be undone,
   so they are the same kind of thing and should not look like two. These are the parts only the
   bill panel needs. */
.ten-snd-t{width:100%;border-collapse:collapse;margin-bottom:12px;font-size:13px}
.ten-snd-t td{padding:6px 0;border-bottom:1px solid var(--line);vertical-align:top}
.ten-snd-t td.q{color:var(--muted);white-space:nowrap;padding-left:8px;font-size:12.5px}
.ten-snd-t td.a{text-align:right;white-space:nowrap;font-weight:600;padding-left:8px}
.ten-snd-t tr.tot td{border-bottom:none;border-top:1px solid var(--text-2);font-weight:700;padding-top:8px}
.ten-snd-grp{margin:12px 0}
.ten-snd-lbl{font-size:11.5px;font-weight:600;letter-spacing:.05em;text-transform:uppercase;
  color:var(--muted);margin-bottom:5px}
.ten-snd-p{font-size:13px;padding:5px 0 5px 10px;border-left:2px solid var(--line)}
.ten-snd-p.ok{border-left-color:var(--brand)}
.ten-snd-p.warn{border-left-color:#c98a00}
.ten-snd-p.none{color:var(--muted);border-left-color:transparent}
/* The server's own sentence, on its own line so a long reason is readable on a phone rather than
   wrapping into the name beside it. */
.ten-snd-why{color:var(--muted);font-size:12px;margin-top:2px}
.ten-snd-pv{margin-top:6px;border:1px solid var(--line);border-radius:var(--r-2);padding:8px 10px}
.ten-snd-pv summary{cursor:pointer;font-size:12.5px;color:var(--text-2)}
.ten-snd-pv pre{margin-top:8px;white-space:pre-wrap;word-break:break-word;font-size:12px;
  line-height:1.5;font-family:ui-monospace,SFMono-Regular,Menlo,monospace}
/* The thread. Direction is carried by the border side rather than by left/right chat bubbles: a
   bubble layout on a phone halves the width of every line, and this screen is read in a dispute where
   the text matters more than the shape. */
.ten-thr{padding:4px 0}
.ten-thr-t{padding:10px 0 10px 11px;border-left:3px solid var(--line);margin-bottom:2px}
.ten-thr-t.in{border-left-color:var(--brand)}
.ten-thr-t.out{border-left-color:var(--line)}
/* Undetermined direction. Red, because on this screen a row nobody can attribute is the thing to look
   at rather than skim past. */
.ten-thr-t.unk{border-left-color:#c0392b}
.ten-thr-m{display:flex;flex-wrap:wrap;align-items:baseline;gap:7px;font-size:12px;
  color:var(--muted);margin-bottom:4px}
.ten-thr-when{margin-left:auto;white-space:nowrap}
.ten-thr-b{font-size:14px;line-height:1.55;white-space:pre-wrap;word-break:break-word;color:var(--text)}
.ten-thr-tag,.ten-thr-del{font-size:10.5px;text-transform:uppercase;letter-spacing:.04em;
  padding:1px 6px;border-radius:var(--r-2);border:1px solid var(--line)}
.ten-thr-del.ok{color:var(--brand-ink);border-color:var(--brand)}
.ten-thr-del.warn{color:#c98a00;border-color:#c98a00}
.ten-thr-warn{color:#c0392b;font-weight:600}
/* A permanently mislabelled turn. Red, because it warns about the record's RELIABILITY rather than
   noting something about its contents. */
.ten-thr-badlang{color:#c0392b}
.ten-thr-note{font-size:12px;color:var(--muted);line-height:1.5;margin-top:5px}
.ten-thr-lead{margin:0 0 8px}
/* The original, revealed and never substituted, so both readings can be on screen at once. */
.ten-thr-orig{margin-top:7px;border-left:2px solid var(--line);padding-left:9px}
.ten-thr-orig summary{cursor:pointer;font-size:11.5px;color:var(--text-2);text-transform:uppercase;
  letter-spacing:.04em}
.ten-thr-orig .ten-thr-b{margin-top:6px;font-size:13.5px;color:var(--text-2)}
/* A bill document is long, so it stays collapsed and never buries the turns around it. */
.ten-thr-doc .ten-thr-b{font-family:ui-monospace,SFMono-Regular,Menlo,monospace;font-size:12px}
.ten-thr-panel .ten-thr{max-height:52vh;overflow-y:auto;overscroll-behavior:contain;
  border:1px solid var(--line);border-radius:var(--r-2);padding:8px 10px;background:var(--surface)}
.ten-rep-t{width:100%;margin-top:12px;padding:var(--p-field);border:1px solid var(--line);border-radius:var(--r-2);
  background:var(--surface);color:var(--text);font-size:14px;line-height:1.5;resize:vertical;
  font-family:inherit}

/* ---------- shell: fixed root + single scroll region ---------- */
.app{position:fixed;inset:0;height:100dvh;overflow:hidden;display:flex;flex-direction:column;touch-action:manipulation;
  padding-top:env(safe-area-inset-top);background:var(--bg);-webkit-tap-highlight-color:transparent}   /* manipulation: no double-tap zoom; still allows every single-finger pan (descendants keep their own pan-x/pan-y/none) */
.app-scroll{flex:1;min-height:0;overflow-y:auto;overflow-x:hidden;overscroll-behavior:contain;
  touch-action:pan-y;-webkit-overflow-scrolling:touch;position:relative}   /* the ONLY scroller; pan-y stops the horizontal drag at the gesture level */
.sidenav{display:none}
.topbar{flex:none;height:56px;display:flex;align-items:center;gap:8px;justify-content:space-between;
  padding:0 16px;background:var(--bg);border-bottom:1px solid var(--line);
  user-select:none;-webkit-user-select:none}   /* scrolls away with the content; solid bg (no backdrop-filter repaint while scrolling) */
.topbar-title{font-family:var(--display);font-size:19px;letter-spacing:.04em;
  flex:0 1 auto;min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}   /* long titles truncate, never push */
.agent-chip{font-family:var(--mono);font-size:11px;color:var(--ai-strong);border:1px solid var(--ai);
  padding:5px 10px;border-radius:var(--r-4);max-width:120px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}  /* long business names ellipsize */
.topbar-right{display:flex;align-items:center;gap:10px;flex:0 0 auto;min-width:0}
.theme-btn{width:34px;height:34px;border-radius:var(--r-circle);border:1px solid var(--line);background:none;
  color:var(--brand-ink);font-size:15px;line-height:1;cursor:pointer;display:grid;place-items:center}
.theme-btn:hover{border-color:var(--brand)}
[data-theme="dark"] .theme-btn{color:var(--brand)}
.lang-toggle{display:inline-flex;border:1px solid var(--line);border-radius:var(--r-2);overflow:hidden}
.lang-toggle button{background:none;border:none;font-family:var(--mono);font-size:11px;letter-spacing:.06em;
  color:var(--muted);padding:6px 9px;cursor:pointer;line-height:1}
.lang-toggle button.active{background:var(--brand);color:var(--brand-contrast)}
.lang-toggle button:not(.active):hover{color:var(--brand-ink)}
[data-theme="dark"] .lang-toggle button:not(.active):hover{color:var(--brand)}
/* ---- THE BOTTOM PADDING IS THE DEAD ZONE UNDER EVERY SCREEN, AND IT GREW ON 2026-08-18 ----
   108 cleared the dock and nothing else, because the bloom was INSIDE the dock and therefore
   inside the dock's own footprint. It floats above the bar's right end now, so the dead zone is
   the dock plus the gap plus the bloom.
   MEASURED, npm run render, at 360x800 and 390x844 with the real component mounted:
     dock bottom  18 + safe
     dock height  62      (the file that owns it says 60; 62 is what it renders)
     gap           8
     bloom        64      (56 for one commit, back to 64 when it returned to the centre)
     -----------------
     152px from the bottom edge to the top of the light, and 154 is what ships: at exactly 152
     the harness measured the tightest ink on the tightest screen at MINUS 0.4px, which is the
     boundary itself plus sub-pixel rounding. Two pixels is the rounding, not a cushion.
   THE HARNESS FOUND THE OLD NUMBER RATHER THAN ANYBODY READING IT: at 108, seven screens put ink
   under the light — Listings by 4px, Inbox by 1, Calendar and This month and Network by 36,
   Feedback by 7 and the copilot's own suggestion chips by 49.

   ---- 152 IS THE BLOOM'S NUMBER AND NOT THE BAR'S, AND THE DIFFERENCE IS 56 PIXELS ----
   The DOCK alone needs 96: 18 of inset, 62 of height, and 16 of clearance. Everything above that
   is the bloom floating 8px over the middle of the bar at 64px across.
   WHY THAT MATTERS RATHER THAN BEING TRIVIA. Measured over a genuinely busy backdrop, this bar
   keeps 0.42 to 0.61 of the structure behind it and unmistakably reads as glass. Over the product's
   own screens it reads as a dark slab, and the reason is not the material: it is that at the end of
   a page there is nothing at the bar's own height to refract, because these 152 pixels are empty by
   construction. Mid-scroll a card IS behind it and it does read as glass.
   THE 56 CANNOT BE RECOVERED WITHOUT MOVING THE BLOOM. A last row ending at 96 would sit under a
   64px light with a dark mark in the middle of it: readable is a promise this product keeps, and a
   glyph over the centre of the last card breaks it. The trade is real and it is quantified here so
   it can be taken deliberately rather than discovered again. */
/* 154px IS THE DEAD ZONE AND IT IS RESERVED FOR THE DOCK AND ITS BLOOM. It stays exactly what it
   was on every screen with nothing pending. A screen that PINS AN ACT needs the bar's own height
   and one gap on top of it, or the last row of content sits underneath the act. --rg-act-h is
   published by UI.act from the bar's measured box; the fallback is only for the frame before the
   first measurement, and [data-act] is only ever set once there is something to measure. */
.view{padding:16px 16px calc(env(safe-area-inset-bottom,0px) + 154px);max-width:1080px;margin:0 auto}
.app[data-act] .view{
  /* ⛔ DERIVED FROM THE LIFT NOW, NOT FROM 154. The 154 above is the dead zone for the dock and
     its bloom, and it was right while the act bar sat INSIDE that zone. The bar now sits ABOVE the
     bloom, so content has to clear the bar's top rather than the bloom's, and 154 + act-h + 11 was
     9px short of it. Written as the same expression the bar's own `bottom` uses, plus the bar's
     height and one gap, so the two can never disagree again: move the bar and the padding follows
     with no second number to remember. */
  padding-bottom:calc(env(safe-area-inset-bottom,0px) + 18px
                      + var(--rg-act-lift,145px) + var(--rg-act-h,64px) + 11px);
}
/* THE ONE SCREEN THAT IS NOT A SCROLLING PAGE. .view's padding exists so a scrolling screen
   breathes and clears the dock at the bottom. The thread is not a scrolling screen: .rg-chat-screen
   is already exactly as tall as the space available and pins its own composer, so that padding is
   124px of chrome wrapped around a box that has already measured the viewport, which pushes it down
   16px, makes .app-scroll scroll by 124, and drops the composer under the dock. Measured before this
   rule: #view 968px inside an 844px scrollport, composer bottom 776 against a dock top of 765.
   The reset lives HERE rather than in realuma-glass.css because the padding being cancelled is
   declared here, and a rule in the design file would have to hard-code this file's numbers. */
.view:has(.rg-chat-screen){padding:0}

/* ---------- PORTED SCREENS, one at a time ----------
   .app paints an opaque --bg across the whole viewport, which is the only thing occluding
   .rg-world. .app-scroll and .view were already transparent, so this is the single sheet in the
   way and there is no narrower element to reach for. Scoped by data-view rather than lifted
   globally, because dropping it everywhere would put a flat 56px topbar above a drifting world
   on every screen at once, and every unported screen still expects its flat ground.
   The topbar goes with it on this screen alone: rg-bar carries the title, the back affordance and
   the refresh, and it materialises on scroll, which a static header cannot do. Two bars stacked
   would mean two titles and 56px of flat colour above the glass. */
.app[data-view="tenancy"]{background:none}
.app[data-view="tenancy"] .topbar{display:none}

/* ONE BAR PER SCREEN, and the screen is what decides. data-bar is written by a view that renders
   its own rg-bar, synchronously before its first await, and cleared by the router on every
   navigation. The route cannot answer this: #/dashboard renders a bar for a landlord and none for
   a broker, so a rule keyed on data-view would take the broker's only title away.
   MATCHED ON PRESENCE. The value is documentation, so a future port that mistypes it still gets
   the right frame instead of silently stacking two bars. Adding a screen is one line in that
   screen and this rule is never edited again.
   PAIRED WITH background:none, because the two are one idea. Glass refracts what is behind it,
   and .app's opaque --bg is the single sheet between .rg-world and the eye: .app-scroll and .view
   declare no background of their own, and html/body's --bg paints BEHIND the world rather than
   over it, since .rg-world is a positioned child at z-index 0. A screen that hides the topbar for
   its own glass bar and then stands that glass on solid colour has nothing to refract, which is
   the whole reason the landlord dashboard did not look like the tenancy screen.
   THE OLD RULE STAYS AND IS NOT REDUNDANT. It carries the unit LIST, which claims no bar and
   would otherwise lose the transparent ground it has today. On the unit detail both selectors
   match, both are (0,2,0), and both declare the identical value, so there is no cascade contest
   to resolve and neither silently overrides the other. */
.app[data-bar] .topbar{display:none}
.app[data-bar]{background:none}

/* The floating pill nav lived here: .pillnav, .pill-item, .pill-ai, .pill-ind, .pi-* and the
   four ai-bloom / ai-fade keyframes, about 90 lines. RGNav replaced it, and its styling lives
   in realuma-nav.css where the lens geometry it depends on also lives. The centre AI button's
   translateY is NOT lost: it survives as .rg-orb's translate(-50%,-26px), and the exclusion at
   the head of this file was updated from .pill-item/.pill-ai to .rg-tab/.rg-orb so a bare
   scale() still cannot wipe it. That exclusion is the whole reason this comment names it. */

/* ---------- More sheet (mobile) ---------- */
.more-sheet{position:fixed;top:0;left:0;right:0;height:100dvh;z-index:60}   /* dvh, not the ICB: see .rg-sheet */
.more-backdrop{position:absolute;inset:0;background:rgba(10,15,18,.5);
  transition:opacity .32s cubic-bezier(0.32,0.72,0,1);will-change:opacity}
.more-panel{position:absolute;left:0;right:0;bottom:0;background:var(--surface);
  border-top:1px solid var(--line);border-radius:16px 16px 0 0;box-shadow:var(--shadow);
  padding:10px 16px calc(18px + env(safe-area-inset-bottom));
  transition:transform .32s cubic-bezier(0.32,0.72,0,1);will-change:transform;
  touch-action:none;overscroll-behavior:contain}
.more-sheet.dragging .more-panel,.more-sheet.dragging .more-backdrop{transition:none}
.more-grip{width:40px;height:5px;border-radius:var(--r-pill);margin:2px auto 14px;cursor:grab;
  background:color-mix(in srgb,var(--text) 22%,transparent)}
.more-sheet.dragging .more-grip{cursor:grabbing}
.more-grid{display:grid;grid-template-columns:1fr 1fr;gap:10px}
.more-grid a{display:flex;align-items:center;gap:12px;min-height:52px;padding:var(--p-row);
  border:1px solid var(--line);border-radius:var(--r-3);background:var(--surface);color:var(--text);
  text-decoration:none;font-size:14.5px}
.more-grid a.active{border-color:var(--brand);color:var(--brand-ink)}
[data-theme="dark"] .more-grid a.active{color:var(--brand)}
.more-ic{width:22px;text-align:center;color:var(--ai-strong);font-size:17px;flex:none}
.more-actions{display:flex;gap:10px;margin-top:10px}
.more-action{flex:1;display:flex;align-items:center;gap:10px;min-height:52px;padding:var(--p-row);
  /* 14.5px matches .more-grid a directly above it in the same sheet. It was 14px: drift corrected,
     not a design choice, and no other pair in this file differs by half a pixel on purpose. */
  border:1px solid var(--line);border-radius:var(--r-3);background:none;color:var(--text);font-size:14.5px}
.more-action:hover{border-color:var(--brand)}
.more-action.danger{color:var(--danger)}
.more-action.danger .more-ic{color:var(--danger)}
.more-lang{justify-content:center;padding:var(--p-tight)}
.more-signout{width:100%;margin-top:10px}
.more-close{width:100%;margin-top:12px;min-height:48px;text-align:center}
/* menu "Upgrade" door (non-assistant tiers) */
.more-upgrade{display:flex;align-items:center;gap:12px;min-height:52px;padding:var(--p-row);margin-bottom:10px;
  border:1px solid var(--brand);border-radius:var(--r-3);background:color-mix(in srgb,var(--brand) 12%,transparent);
  color:var(--brand-ink);text-decoration:none;font-size:14.5px;font-weight:600}
[data-theme="dark"] .more-upgrade{color:var(--brand)}
.more-upgrade .more-ic{color:var(--brand-ink)}
[data-theme="dark"] .more-upgrade .more-ic{color:var(--brand)}
/* upgrade sheet — the most beautiful screen in the app */
.up-cta[aria-disabled="true"]{opacity:.45;pointer-events:none;cursor:default}
.contact-unavailable{margin:10px 0 0;text-align:center;font-size:13px;line-height:1.5;color:var(--muted)}
.up-panel{max-width:520px;margin:0 auto}
.up-eyebrow{font-family:var(--mono);font-size:11px;letter-spacing:.18em;text-transform:uppercase;
  color:var(--brand-ink);text-align:center;margin-bottom:12px}
[data-theme="dark"] .up-eyebrow{color:var(--brand)}
.up-head{font-family:var(--display);font-weight:400;font-size:24px;line-height:1.3;letter-spacing:.01em;
  text-align:center;margin:0 auto 22px;max-width:20em}
.up-benefits{list-style:none;display:flex;flex-direction:column;gap:14px;margin:0 0 20px}
.up-benefits li{display:flex;gap:12px;align-items:flex-start;font-size:14.5px;line-height:1.5;color:var(--text)}
.up-b-ic{flex:none;width:24px;height:24px;border-radius:var(--r-circle);display:grid;place-items:center;
  background:var(--ai-wash);color:var(--ai-strong);font-size:12px;margin-top:1px}
.up-fomo{text-align:center;color:var(--muted);font-size:13.5px;line-height:1.55;font-style:italic;
  max-width:26em;margin:0 auto 22px}
.up-price{display:flex;align-items:baseline;justify-content:center;gap:6px;margin-bottom:16px}
.up-price-n{font-family:var(--display);font-size:30px;color:var(--brand-ink)}
[data-theme="dark"] .up-price-n{color:var(--brand)}
.up-price-p{font-family:var(--mono);font-size:13px;color:var(--muted)}
.up-notnow{display:block;width:100%;margin-top:10px;padding:var(--p-block);background:none;border:none;
  color:var(--muted);font-family:var(--mono);font-size:12px;letter-spacing:.06em;text-transform:uppercase}
.up-notnow:hover{color:var(--text-2)}

/* THE .fsheet-* CHROME IS GONE. All 25 lines of it: overlay, backdrop, panel, head, grip margin,
   title, close, body, foot and the desktop media query are now realuma-glass.css section 19, and
   UI.sheet renders that instead. Nothing in the app can produce an .fsheet class any more, and a
   block of rules nothing renders is a block the next person will read as live and edit.
   .more-grip survives untouched at line 530: the "More" menu still uses it, and only the
   `.fsheet-head .more-grip` margin override left with the sheet.
   .fsec below did NOT leave: it is form-section markup that CALL SITES write, and properties.js
   and tenancy.js are both still writing it inside their sheets. */
/* form sections */
.fsec{margin-bottom:20px}
.fsec-h{font-family:var(--mono);font-size:11px;letter-spacing:.12em;text-transform:uppercase;
  color:var(--brand-ink);margin-bottom:10px;padding-bottom:7px;border-bottom:1px solid var(--line)}
[data-theme="dark"] .fsec-h{color:var(--brand)}
.fl{display:block;font-size:13px;color:var(--text-2);margin:12px 0 6px}
.fsec .fl:first-of-type{margin-top:0}
/* Same token correction as .card-note, and for the same measured reason: the legacy --muted is
   rgba(22,51,58,.58) in daylight and fails AA under a field at 12px. The glass token is .80. */
.fhelp{font-size:12px;color:var(--rg-muted,var(--muted));margin-top:5px}
/* Deliberately NOT var(--muted) like .fhelp. This is the value the record will hold, not advice
   about the field, and it has to out-rank the help line sitting under it. min-height reserves the
   row so the form does not jump the moment a date is picked. */
.fdate{font-size:12.5px;font-weight:600;color:var(--text-2);margin-top:6px;min-height:16px}
/* The same line turns into the refusal when a finished date is impossible, so it has to read as a
   problem rather than as information. */
.fdate.bad{color:var(--danger)}
/* Day, month, year. The year box is wider because it holds twice as many digits, and a visibly
   different width is one more way to know which box the cursor is in. */
.fdate3{display:grid;grid-template-columns:1fr 1fr 1.6fr;gap:8px}
.fdate3 .finput{text-align:center}
.fgrid2{display:grid;grid-template-columns:1fr 1fr;gap:10px}
.fgrid3{display:grid;grid-template-columns:repeat(3,1fr);gap:10px}
.fgrid2 .fl,.fgrid3 .fl{margin-top:0}
.price-wrap{position:relative;display:flex;align-items:center}
.price-wrap .finput{padding-right:104px;font-size:18px;font-weight:600}
.price-suffix{position:absolute;right:12px;color:var(--muted);font-size:13px;pointer-events:none}
#f-price-help{font-family:var(--mono);color:var(--ai-strong)}
/* photo drop zone + thumbnails */
.photo-drop{width:100%;display:flex;align-items:center;justify-content:center;gap:8px;min-height:54px;
  border:1.5px dashed var(--line);border-radius:var(--r-3);background:var(--surface-2);color:var(--text-2);font-size:14px}
.photo-drop:hover{border-color:var(--brand);color:var(--brand-ink)}
.photo-drop .pd-ic{font-size:19px;color:var(--brand-ink)}
[data-theme="dark"] .photo-drop .pd-ic{color:var(--brand)}
.photo-grid{display:grid;grid-template-columns:repeat(3,1fr);gap:8px;margin-top:10px}
.ph-thumb{position:relative;aspect-ratio:1;border-radius:var(--r-2);overflow:hidden;border:1px solid var(--line);cursor:zoom-in}
.ph-thumb img{width:100%;height:100%;object-fit:cover}
.ph-del{position:absolute;top:4px;right:4px;width:24px;height:24px;border-radius:var(--r-circle);border:none;
  background:rgba(10,15,18,.6);color:#fff;font-size:12px;display:grid;place-items:center;cursor:pointer}
.ph-cover{position:absolute;left:0;right:0;bottom:0;background:var(--brand);color:var(--brand-contrast);
  font-family:var(--mono);font-size:9.5px;letter-spacing:.06em;text-transform:uppercase;text-align:center;padding:3px}
/* ---------- THE BILL DOCUMENT'S PREVIEW, inside the send panel ----------
   A REAL PREVIEW OF A TALL DOCUMENT ON A PHONE, which is a shape nothing else in this product has.
   .ph-thumb crops to a square and .mr-photo is an evidence frame for a meter face; a bill is 1080
   wide and roughly 1400 tall and the whole point is that a landlord can read it before agreeing to
   send it. So: full width, its own aspect ratio, and a bounded height with the overflow scrolling
   INSIDE the panel rather than stretching it past the fold. */
.ten-doc-prev{max-height:340px;overflow-y:auto;border:1px solid var(--line);border-radius:var(--r-2);
  background:#fff;margin:8px 0}
.ten-doc-prev img{display:block;width:100%}

/* THE MOVE-IN GRID'S CORNER CONTROL. Same position and the same 24px as .ph-del one rule up, and a
   different class for one reason: .ph-del is named for the destructive act it performs on the
   listing form, nothing on the move-in grid is ever deleted, and a class whose name lies about what
   its button does is a trap. The glyph is champagne rather than white because a human is acting,
   which is the colour law in section 0 of the glass sheet, and the ring is what separates it from
   the photograph underneath at any brightness. */
.ph-edit{position:absolute;top:4px;right:4px;width:24px;height:24px;border-radius:var(--r-circle);
  background:rgba(10,15,18,.62);border:1px solid rgba(201,161,112,.45);color:var(--brand);
  font-size:11px;line-height:1;display:grid;place-items:center;cursor:pointer;padding:0}
.ph-edit:active{transform:scale(.92)}
/* secondary + danger */
.fsec-actions{margin-top:6px}
.fsec-actions .btn{width:100%}
.danger-zone{margin-top:18px;padding-top:16px;border-top:1px solid var(--line)}
.btn-danger.armed{background:var(--danger);color:#fff}
@media(min-width:900px){.more-sheet{display:none!important}}

@media(min-width:900px){
  .app{display:grid;grid-template-columns:220px 1fr;grid-template-areas:"side scroll";padding-top:0}
  .sidenav{display:flex;grid-area:side;flex-direction:column;gap:4px;padding:20px 12px;
    background:var(--surface);border-right:1px solid var(--line);height:100%;overflow-y:auto}
  .sidenav .wordmark{padding:4px 10px 18px}
  .sidenav a{display:flex;align-items:center;gap:10px;padding:var(--p-control);border-radius:var(--r-2);
    color:var(--text-2);text-decoration:none;font-size:14.5px}
  .sidenav a:hover{background:var(--surface-2)}
  .sidenav a.active{background:var(--surface-2);color:var(--brand-ink);font-weight:600}
  [data-theme="dark"] .sidenav a.active{color:var(--brand)}
  .ni{width:20px;text-align:center;color:var(--ai-strong)}
  .sidenav-foot{margin-top:auto;display:flex;gap:8px;padding:var(--p-tight)}
  .sidenav-foot button{flex:1;background:none;border:1px solid var(--line);border-radius:var(--r-2);
    color:var(--muted);padding:var(--p-tight);font-size:12px}
  .sidenav-foot button:hover{color:var(--brand-ink);border-color:var(--brand)}
  .app-scroll{grid-area:scroll}
  .view{padding:24px 28px 40px;width:100%}
  .rg-dock-nav{display:none}
}

/* ---------- cards & layout ---------- */
.grid{display:grid;gap:14px}
.grid-2{grid-template-columns:1fr 1fr}
.grid-4{grid-template-columns:repeat(4,1fr)}
@media(max-width:720px){.grid-4{grid-template-columns:1fr 1fr}.grid-2{grid-template-columns:1fr}}
.card{background:var(--surface);border:1px solid var(--line);border-radius:var(--r-3);
  box-shadow:var(--shadow);padding:16px}
.card h3{font-family:var(--display);font-weight:400;font-size:17px;margin-bottom:10px}
.stat .n{font-family:var(--mono);font-size:26px;color:var(--ai-strong);font-weight:500}
.stat .l{font-size:12.5px;color:var(--muted);margin-top:2px}
.section-title{font-family:var(--display);font-size:20px;margin:22px 0 12px}
.empty{color:var(--muted);text-align:center;padding:var(--p-empty);font-size:14px}

/* ---------- the three states (UI.stateLoading / stateEmpty / stateFailed) ----------
   Told apart by SHAPE, not by reading, and not by animation: the global reduced-motion rule at the
   foot of this file kills every animation, so the skeleton still has to differ from an empty card
   with nothing moving. It does: bars versus a centred line. Existing tokens only, no new colour. */
.state-load{display:flex;flex-direction:column;gap:9px;padding:18px 16px}
.sk-bar{display:block;height:11px;border-radius:var(--r-2);background:var(--surface-2);border:1px solid var(--line)}
.sk-0{width:100%} .sk-1{width:72%} .sk-2{width:45%}
@media (prefers-reduced-motion: no-preference){
  .sk-bar{animation:sk-breathe 1.6s ease-in-out infinite}
  .sk-bar.sk-1{animation-delay:.18s}
  .sk-bar.sk-2{animation-delay:.36s}
}
@keyframes sk-breathe{0%,100%{opacity:.5}50%{opacity:1}}

/* The title matches `.card h3` exactly (display, 400, 17px, 10px below) because that is what the
   two rich empties already used, and nothing on screen passed a title before this. */
.state-empty .state-t,.state-refused .state-t{font-family:var(--display);font-weight:400;font-size:17px;margin-bottom:10px}
/* Only in the plain layout: the icon layout below supplies its own gap, and both would double it. */
.state-empty:not(.state-empty-ic) .state-cta{margin-top:12px}
/* Absorbed verbatim from .imp-empty / .imp-empty-ic, so the two rich empties keep their layout. */
.state-empty-ic{display:flex;flex-direction:column;align-items:center;gap:10px;padding:28px 18px}
.state-empty .state-ic{font-size:30px;opacity:.5}
/* size:'lg'. Lifted VALUE FOR VALUE from the old .cal-empty so the one empty state a new agent sees
   on day one keeps its scale: 52px padding, a 46px brand icon, a 20px title on --text rather than
   --muted, a 330px measure. Must stay after .state-empty-ic, which it overrides on padding and gap. */
.state-empty-lg{padding:52px 24px 40px;gap:12px}
.state-empty-lg .state-ic{font-size:46px;color:var(--brand);line-height:1}
.state-empty-lg .state-t{font-size:20px;color:var(--text);margin-bottom:0}
.state-empty-lg .state-b{max-width:330px;line-height:1.6}
.state-empty-lg .state-cta{margin-top:8px}

/* ---------- the muted card note ----------
   One role, one size. It was six inline styles at 15px, 13.5px, 13px, 12.5px and 12px for the same
   thing: an explanatory paragraph inside a card. 13px is the mode among the explicit sizes and it
   keeps the muted ladder legible at three steps: .fhelp 12px under a field, .card-note 13px in a
   card, .empty 14px for an empty state. TYPE ONLY, no margin: three of the six sites need none and
   would have gained one. */
/* --rg-muted RATHER THAN --muted, AND THE DIFFERENCE IS AN AA FAILURE IN DAYLIGHT. The two tokens
   are both defined per theme and they disagree about opacity: legacy --muted is rgba(22,51,58,.58)
   in light, the glass --rg-muted is the same ink at .80. Measured on a fresh light load of
   Settings, every .card-note came out at 3.64 against the 4.5 an explanatory paragraph needs, and
   nothing else on the screen was below AA. In dark this moves .55 to .60, which is slightly more
   contrast rather than less, so the change cannot regress the theme it was already passing in.
   The ladder itself is unchanged: still three steps, still type only, still no margin. */
.card-note{font-size:13px;color:var(--rg-muted,var(--muted))}

/* The 3px left rule is the whole tell: it is the only block on any screen that carries --danger on
   an edge, so "something is wrong here" lands before the sentence does. Left aligned on purpose,
   because centred text is what empty looks like. */
.state-fail{border-left:3px solid var(--danger);display:flex;align-items:center;gap:12px;
  flex-wrap:wrap;text-align:left;padding:14px 16px}
.state-fail .state-b{flex:1;min-width:170px;color:var(--danger);font-size:13.5px}

/* Refused: FLAT, DASHED, RECESSED. Deliberately NOT .empty, so a grep for the empty class keeps
   counting empties only. It differs from an empty card on three properties a reader sees before
   any text: dashed instead of solid, sunk --surface-2 instead of raised --surface, and no shadow
   at all. It carries no --danger anywhere, because nothing here is broken. */
.state-refused{background:var(--surface-2);border-style:dashed;box-shadow:none;
  color:var(--muted);text-align:center;padding:var(--p-empty);font-size:14px}

/* ---------- lists ---------- */
.list{display:flex;flex-direction:column;gap:10px}
.row{display:flex;align-items:center;gap:12px;background:var(--surface);border:1px solid var(--line);
  border-radius:var(--r-3);padding:var(--p-row);cursor:pointer;transition:transform .12s}
.row:hover{transform:translateY(-1px)}
.row .thumb{width:56px;height:56px;border-radius:var(--r-2);background:var(--surface-2);object-fit:cover;flex:none;
  display:grid;place-items:center;color:var(--muted);font-size:20px;overflow:hidden}
.row .thumb img{width:100%;height:100%;object-fit:cover}
.row .main{flex:1;min-width:0}
.row .t1{font-weight:600;font-size:14.5px;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}
.row .t2{font-size:12.5px;color:var(--muted);margin-top:1px}
.pill{font-family:var(--mono);font-size:10px;letter-spacing:.08em;text-transform:uppercase;
  padding:4px 9px;border-radius:var(--r-4);border:1px solid var(--line);color:var(--muted);white-space:nowrap}
.pill.ok{border-color:var(--ai);color:var(--ai-strong)}
.pill.hot{border-color:var(--danger);color:var(--danger)}
.pill.brand{border-color:var(--brand);color:var(--brand-ink)}
[data-theme="dark"] .pill.brand{color:var(--brand)}
.filters{display:flex;gap:8px;flex-wrap:wrap;margin-bottom:14px}
/* The chip base is the whole of it: this family had nothing of its own left once folded. */
.filters button.active{background:var(--ai);border-color:var(--ai);color:#fff}
.prop-toolbar{display:flex;gap:10px;align-items:center;flex-wrap:wrap;margin-bottom:12px}
.prop-toolbar input,.prop-toolbar select{margin:0}
.prop-toolbar input{flex:1;min-width:180px}
.prop-filters{margin-bottom:6px}
.prop-filters .filters{margin-bottom:8px}
.facet-label{font-family:var(--mono);font-size:10px;letter-spacing:.06em;text-transform:uppercase;
  color:var(--muted);align-self:center;margin-right:4px}

/* sticky search/filter row (topbar scrolls away, this follows the agent down the list) */
.stick{position:sticky;top:0;z-index:20;background:var(--bg);margin:0 -16px 10px;padding:10px 16px 8px}
.prop-toolbar.stick{display:block;flex-wrap:nowrap}
.prop-toolbar.stick #prop-search{width:100%}
.prop-tb-row{display:flex;gap:8px;align-items:center;margin-top:8px}
.prop-tb-row #prop-sort{flex:1;min-width:0}
.inbox-tools.stick .ib-search{margin-bottom:8px}
/* filter button — facets collapse into a sheet; badge shows the active count */
/* Only what differs from the chip base: it carries a count badge, so it lays out as a row and
   refuses to wrap or flex. */
.filter-btn{display:inline-flex;align-items:center;gap:6px;white-space:nowrap;flex:none}
.filter-btn.on{border-color:var(--ai);color:var(--ai-strong)}
[data-theme="dark"] .filter-btn.on{color:var(--ai)}
.filter-count{display:inline-grid;place-items:center;min-width:18px;height:18px;padding:0 5px;border-radius:var(--r-2);
  background:var(--ai);color:#fff;font-family:var(--mono);font-size:10px;line-height:1}
.filter-sheet .filters{margin-bottom:16px}

/* ---------- chat ---------- */
/* THE BUBBLES ARE GONE, and this is where the last of them lived. .chat-box, .chat-head,
   .chat-msgs, .chat-input and every .bubble rule were the copilot's chrome; the thread left them
   for section 21 of realuma-glass.css in 0bbb224 and the copilot has now followed it, so nothing
   in app/js selects any of them. Both screens are .rg-chat-screen over .rg-chat over .rg-turn.
   THE CASING NOTE WENT WITH `.chat-input button`, which is the rule it was about: it explained why
   `inbox_send` and `asst_ask` are typed in sentence case rather than as GỬI and HỎI. They still
   are, and they must stay that way. Their button is .rg-mini now, which does not uppercase at all,
   so those two labels read exactly as they are written and there is no longer a rule to match. */
.takeover-bar{padding:9px 14px;font-size:12.5px;display:flex;align-items:center;gap:10px;
  border-bottom:1px solid var(--line);background:var(--surface-2)}
.live-dot{width:7px;height:7px;border-radius:var(--r-circle);background:var(--ai);animation:pulse 1.8s infinite;display:inline-block}
/* ---------- inbox triage ---------- */
.ib-search{margin-bottom:10px}
.ib-row2{display:flex;align-items:center;justify-content:space-between;gap:10px;margin:2px 0 8px}
.ib-morebtn{background:none;border:1px solid var(--line);border-radius:var(--r-4);padding:var(--p-toggle);font-size:12.5px;color:var(--text-2)}
.ib-morebtn:hover{border-color:var(--brand);color:var(--brand-ink)}
.ib-sort{display:inline-flex;border:1px solid var(--line);border-radius:var(--r-4);overflow:hidden;flex:none}
.ib-sort button{background:none;border:none;font-size:12px;color:var(--muted);padding:var(--p-toggle)}
.ib-sort button.active{background:var(--ai);color:#fff}
.ib-extra{margin-bottom:6px}
.row.needs{border-left:3px solid var(--danger)}
.row.unread .t1{font-weight:700}
.ib-dot{display:inline-block;width:8px;height:8px;border-radius:var(--r-circle);background:var(--ai);margin-right:6px;vertical-align:middle}
/* translated messages + reveal + reply preview */
.tr-tag{display:inline-block;margin-left:6px;font-family:var(--mono);font-size:9px;letter-spacing:.06em;
  border:1px solid currentColor;border-radius:var(--r-1);padding:0 4px;opacity:.65;vertical-align:middle}
/* The copilot's greeting card, its loaded-history dimming and its memory boundary lived here.
   .as-intro is .rg-empty on teal glass now, .as-cap and .as-boundary are both .rg-chat-sep, and
   .as-note is .rg-fail. .bubble.as-past is not ported at all: two separators already bracket the
   loaded turns and say what they are in words, so 55% opacity was a third carrier of one fact,
   paid for in legibility on text a person may need to read. */
.lang-hint{padding:8px 14px;font-size:12px;color:var(--ai-strong);background:var(--ai-wash);border-top:1px solid var(--line)}
[data-theme="dark"] .lang-hint{color:var(--ai)}
.prev-block{margin-top:12px}
.prev-label{font-family:var(--mono);font-size:10px;letter-spacing:.08em;text-transform:uppercase;color:var(--muted);margin-bottom:4px}
.prev-orig{color:var(--muted);font-size:14px}
.prev-send{background:var(--surface-2);border:1px solid var(--ai);border-radius:var(--r-2);padding:var(--p-notice)}
.prev-text{font-size:15px;color:var(--text)}
/* real chat: timestamps, day/new separators, delivery state, wrap */
.chat-msgs{overscroll-behavior:contain;touch-action:pan-y}
.bubble{overflow-wrap:anywhere;word-break:break-word}
.msg-time{display:block;margin-top:4px;font-family:var(--mono);font-size:10px;color:var(--muted)}
.bubble.agent .msg-time,.bubble.ai .msg-time{text-align:right}
.ms{margin-left:6px}
.ms.ok{color:var(--ai-strong)}
[data-theme="dark"] .ms.ok{color:var(--ai)}
.ms.fail{color:var(--danger)}
.msg-retry{background:none;border:none;color:var(--danger);text-decoration:underline;font-family:var(--mono);font-size:10px;cursor:pointer;padding:0;margin-left:2px}
.sys-line{text-align:center;margin:10px 0}
.sys-line span{font-family:var(--mono);font-size:10.5px;letter-spacing:.03em;color:var(--muted);
  background:var(--surface-2);border:1px solid var(--line);border-radius:var(--r-4);padding:4px 12px;display:inline-block}
.tog-row{display:flex;align-items:center;gap:9px;margin:14px 0 4px;font-size:14px;color:var(--text);cursor:pointer}
.tog-row input{width:auto;margin:0}
.day-sep{text-align:center;margin:14px 0 8px}
.day-sep span{font-family:var(--mono);font-size:9.5px;letter-spacing:.08em;text-transform:uppercase;color:var(--muted);
  background:var(--surface-2);padding:3px 12px;border-radius:var(--r-4)}
.new-sep{display:flex;align-items:center;gap:8px;margin:12px 2px 8px;color:var(--danger)}
.new-sep::before,.new-sep::after{content:'';flex:1;height:1px;background:var(--danger);opacity:.35}
.new-sep span{font-family:var(--mono);font-size:9.5px;letter-spacing:.1em;text-transform:uppercase}
@keyframes pulse{0%,100%{opacity:1}50%{opacity:.3}}

/* ---------- calendar: phone agenda ---------- */
.cal-head{display:flex;align-items:center;justify-content:space-between;gap:10px;margin-bottom:6px}
.cal-title{font-family:var(--display);font-size:20px;letter-spacing:.02em}
/* week strip — the app's one intentional horizontal scroller; sticky under the topbar */
.cal-strip{position:sticky;top:0;z-index:25;display:flex;gap:6px;overflow-x:auto;overflow-y:hidden;
  touch-action:pan-x;margin:0 -16px;padding:8px 16px;background:var(--bg);border-bottom:1px solid var(--line);
  scrollbar-width:none;-ms-overflow-style:none}
.cal-strip::-webkit-scrollbar{display:none}
.cal-daybtn{flex:0 0 auto;width:46px;display:flex;flex-direction:column;align-items:center;gap:2px;
  padding:6px 0 5px;border:1px solid transparent;border-radius:var(--r-3);background:none;color:var(--text-2);cursor:pointer}
.cal-daybtn .cal-dw{font-size:10px;text-transform:uppercase;letter-spacing:.04em;color:var(--muted)}
.cal-daybtn .cal-dn{font-size:16px;font-weight:600;line-height:1.05;font-family:var(--body)}
.cal-daybtn .cal-dot{width:5px;height:5px;border-radius:var(--r-circle);background:transparent}
.cal-daybtn.has .cal-dot{background:var(--ai)}
.cal-daybtn.today{background:var(--surface-2);border-color:var(--line)}
.cal-daybtn.today .cal-dn{color:var(--brand-ink)}
[data-theme="dark"] .cal-daybtn.today .cal-dn{color:var(--brand)}
.cal-daybtn.none{opacity:.4;cursor:default}
.cal-daybtn:not(.none):active{transform:scale(.94)}
/* sticky day headers stack just under the strip */
.cal-day{position:sticky;top:52px;z-index:15;scroll-margin-top:58px;
  font-family:var(--mono);font-size:11px;letter-spacing:.06em;text-transform:uppercase;color:var(--muted);
  background:var(--bg);padding:12px 2px 6px}
.cal-card{display:flex;align-items:center;gap:12px;padding:var(--p-control);background:var(--surface);
  border:1px solid var(--line);border-radius:var(--r-3);margin-bottom:8px;cursor:pointer;transition:transform .1s}
.cal-card.has-lead:active{transform:scale(.99)}
.cal-time{flex:0 0 auto;min-width:46px;font-family:var(--mono);font-size:15px;font-weight:500;color:var(--text)}
.cal-cbody{flex:1;min-width:0}
.cal-l1{font-size:14.5px;font-weight:600;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}
.cal-kind{margin-right:6px}
.cal-l2{font-size:13px;color:var(--muted);margin-top:2px;display:flex;align-items:center;gap:8px}
.cal-chan{font-family:var(--mono);font-size:11px}
/* NOT A CHIP, AND DO NOT FOLD IT IN. It shares --r-4 with the three above by coincidence of SHAPE,
   not of role: those are controls an agent taps to change what they see, this is a STATUS rendered
   beside a time on the densest card in the app. It is mono at 10.5px so it aligns optically with
   that time and stays small enough not to compete with the client's name. Making it 13px body would
   enlarge it by a quarter and break the alignment. The divergence is the design. */
.cal-chip{flex:0 0 auto;border:1px solid var(--line);background:none;color:var(--muted);
  font-family:var(--mono);font-size:10.5px;letter-spacing:.02em;padding:5px 10px;border-radius:var(--r-4);
  white-space:nowrap;cursor:pointer}
.cal-chip.s-confirmed{border-color:var(--ai);color:var(--ai-strong)}
[data-theme="dark"] .cal-chip.s-confirmed{color:var(--ai)}
.cal-chip.s-done{opacity:.55}
.cal-chip:active{transform:scale(.95)}
.cal-earlier{background:none;border:none;color:var(--muted);font-family:var(--mono);font-size:11px;
  letter-spacing:.05em;text-transform:uppercase;padding:8px 2px 4px;cursor:pointer}
.cal-earlier.open{color:var(--text-2)}
.cal-earlier-list .cal-card{opacity:.7}
.cal-foot{color:var(--muted);font-size:12.5px;margin-top:16px}
.cal-menu{flex-wrap:wrap}
/* .cal-empty and its four children were folded into UI.stateEmpty size:'lg'. Nothing renders them. */

/* ---------- modal & toast ----------
   .modal-back and .modal are GONE, not renamed: UI.modal renders realuma-glass.css section 19 now,
   so nothing in the app can produce either class and a rule nothing renders is a rule the next
   person will assume is live. The overlay, the backdrop, the width, the radius and the padding all
   moved to the sheet, which already owned every one of them.
   THESE TWO DID NOT MOVE, because they are not chrome. They style what a CALLER puts inside a
   modal: an <h3> and a row of .actions, in 11 call sites that are still writing app.css markup.
   They are retargeted at .modal-body, the marker UI.modal puts on the sheet's body, and they are
   the whole reason those 11 still read correctly inside glass. Both die with the last port. */
.modal-body h3{font-family:var(--display);font-size:19px;margin-bottom:6px}
.modal-body .actions{display:flex;gap:10px;margin-top:20px;justify-content:flex-end}
.toast{position:fixed;bottom:calc(env(safe-area-inset-bottom,0px) + 100px);left:50%;transform:translateX(-50%);z-index:90;
  background:var(--text);color:var(--bg);padding:11px 18px;border-radius:var(--r-4);font-size:13.5px;
  box-shadow:var(--shadow);animation:toastin .2s}
@media(min-width:900px){.toast{bottom:28px}}
@keyframes toastin{from{opacity:0;transform:translate(-50%,8px)}to{opacity:1;transform:translate(-50%,0)}}

/* ---------- misc ---------- */
.photo-strip{display:flex;gap:8px;flex-wrap:wrap;margin-top:8px}
.photo-strip .ph{width:64px;height:64px;border-radius:var(--r-2);object-fit:cover;border:1px solid var(--line)}
.photo-strip .ph{cursor:zoom-in}
.row .thumb img{cursor:zoom-in}

/* ---------- lightbox (photo viewer) ---------- */
.lb-back{position:fixed;top:0;left:0;right:0;height:100dvh;z-index:100;background:rgba(8,12,14,.92);   /* dvh, not the ICB: see .rg-sheet */
  display:grid;place-items:center;padding:var(--p-screen)}
.lb-img{max-width:min(1100px,96vw);max-height:88vh;width:auto;height:auto;object-fit:contain;
  border-radius:var(--r-2);box-shadow:0 12px 44px rgba(0,0,0,.55);user-select:none;-webkit-user-drag:none}
.lb-close{position:fixed;top:16px;right:18px;width:42px;height:42px;border-radius:var(--r-circle);
  border:1px solid rgba(255,255,255,.25);background:rgba(0,0,0,.4);color:#fff;
  font-size:19px;line-height:1;cursor:pointer;display:grid;place-items:center}
.lb-nav{position:fixed;top:50%;transform:translateY(-50%);width:48px;height:48px;border-radius:var(--r-circle);
  border:1px solid rgba(255,255,255,.25);background:rgba(0,0,0,.4);color:#fff;
  font-size:26px;line-height:1;cursor:pointer;display:grid;place-items:center}
.lb-prev{left:14px}
.lb-next{right:14px}
.lb-close:hover,.lb-nav:hover{background:rgba(0,0,0,.65)}
/* ---------- THE LIGHTBOX'S THREE CONTROLS, AND WHY THEY GET AN AREA RATHER THAN A SIZE ----------
   MEASURED 16 Aug at 360 and 390: .lb-close 42x42 and .lb-nav 40x40 under the 520px media query
   below, against a 44px floor. Two pixels and four.
   THEY ARE FIXED OVER A FULL-SCREEN SCRIM WITH NOTHING NEAR THEM, so the area can grow in every
   direction and take a tap from nothing: the close button sits 16px from the top corner and the
   two arrows are pinned to the left and right edges at the vertical middle.
   A TRANSPARENT AREA AND NOT A BIGGER CIRCLE. These circles are drawn over somebody's photograph
   and their size is the whole of their restraint; growing them by four pixels each would be a
   visible change to a screen this pass is not allowed to restyle. Nothing here paints. -6px on
   every side takes 42 to 54 and 40 to 52. */
.lb-close::after,.lb-nav::after{content:'';position:absolute;inset:-6px}
.lb-count{position:fixed;bottom:20px;left:50%;transform:translateX(-50%);
  color:rgba(255,255,255,.85);font-family:var(--mono);font-size:13px;letter-spacing:.05em}
@media(max-width:520px){.lb-nav{width:40px;height:40px;font-size:22px}}
.kv{display:flex;justify-content:space-between;padding:7px 0;border-bottom:1px solid var(--line);font-size:14px}
.kv:last-child{border:none}
.kv b{font-weight:600}
.health{display:flex;gap:14px;flex-wrap:wrap;font-size:13px;color:var(--text-2)}
.health span{display:flex;align-items:center;gap:6px}
.dot{width:8px;height:8px;border-radius:var(--r-circle);display:inline-block}
.dot.g{background:var(--ok)}.dot.r{background:var(--danger)}.dot.y{background:var(--brand)}
/* .cap-meter was a filled bar under the copilot's composer, drawing used/cap a SECOND time directly
   beneath the sentence that already states the same two numbers. The sentence survives, verbatim
   and pending a ruling on its wording; the bar does not, because a graphic of a quantity is the
   loudest possible way to say a number the copy law says not to print at all. */
/* manual refresh control — full re-fetch backstop for a dropped realtime socket */
#refresh-top.spinning{animation:rf-spin .6s linear infinite}
@keyframes rf-spin{to{transform:rotate(360deg)}}
/* "needs you" surfaces — a lead the AI handed off (needs_human). Distinct from 🔥 hot. */
.sidenav a[data-nav]{position:relative}   /* made relative for the badge; the mobile badge now rides RGNav's own tab dot */
.nav-badge{position:absolute;top:2px;right:8px;min-width:16px;height:16px;padding:0 4px;box-sizing:border-box;
  background:var(--danger);color:#fff;font-size:10px;font-weight:700;line-height:16px;text-align:center;
  border-radius:var(--r-2);box-shadow:0 0 0 2px var(--bg);pointer-events:none}
.dash-needs{display:flex;align-items:center;gap:10px;width:100%;margin:0 0 12px;padding:var(--p-row);
  background:rgba(176,87,76,.12);border:1px solid var(--danger);border-radius:var(--r-2);color:var(--danger);
  font-weight:600;text-decoration:none;cursor:pointer}
.dash-needs .dn-ic{font-size:18px;line-height:1}
.dash-needs .dn-go{margin-left:auto;opacity:.7;font-size:13px}
[data-theme="dark"] .dash-needs{background:rgba(217,138,127,.12)}
@media (prefers-reduced-motion:reduce){*{animation:none!important;transition:none!important}}
