/* =========================================================================
   RoomLine — APP SHELL
   §4 layout, §5 information architecture, §6 navigation, §7 scroll system.

   Chrome is a tax. The previous build spent roughly two-thirds of the
   viewport on navigation before showing a single figure: a dark gradient
   band carrying a brand lockup, a property select, four icon buttons, a
   nine-item tab row that wrapped to two lines at laptop width, and a search
   field. The target here is one 52px band plus a 248px rail, and nothing
   else, ever.
   ========================================================================= */

/* -------------------------------------------------------------------------
   Neutralise the pre-spec theme. styles.css paints the body and #app with
   decorative radial gradients and redefines the bridged variables with its
   own dark values; §22 forbids both. These selectors match the originals'
   specificity so the token layer is the only thing choosing a colour.
   ------------------------------------------------------------------------- */
body,
html[data-theme="dark"] body,
html[data-theme="dark"] #app {
  --bg:          var(--rl-canvas);
  --bg-panel:    var(--rl-surface);
  --panel:       var(--rl-surface);
  --ink:         var(--rl-text);
  --ink-soft:    var(--rl-text-2);
  --line:        var(--rl-line);
  --line-strong: var(--rl-line-strong);
  background: var(--rl-canvas);
}
body { padding-bottom: 0; min-height: 0; }

/* -------------------------------------------------------------------------
   §4.1 STRUCTURE
   #app is the whole frame. It owns the viewport height and does not scroll;
   exactly one descendant does (§7.1).
   ------------------------------------------------------------------------- */
#app {
  display: grid;
  grid-template-columns: minmax(0, 1fr);
  grid-template-rows: auto auto minmax(0, 1fr);
  height: 100dvh;
  overflow: hidden;
}
#app[hidden] { display: none !important; }

.rl-skip {
  position: absolute; left: -9999px; top: 0; z-index: 100;
  background: var(--rl-surface); color: var(--rl-text);
  padding: 10px 14px; border-radius: var(--rl-r-sm);
  box-shadow: var(--rl-e-2); font-size: 13.5px;
}
.rl-skip:focus { left: 8px; top: 8px; }

/* =========================================================================
   THE TOP BAR  (replaced the left rail, 2026-09-10)

   Three columns: brand, navigation, tools. The navigation column is the
   point — it is itself a 1fr / auto / 1fr grid so that Resident 360 sits
   on the exact centre line of the window whatever else is beside it, with
   Today/Sales/Finance pushed up against it from the left and Operations/
   Inbox from the right. The centre item is the widest target and the only
   one that keeps a mark when it is not selected.
   ========================================================================= */
.rl-top {
  grid-row: 1; grid-column: 1;
  height: var(--rl-top-h);
  display: flex; align-items: center; justify-content: space-between;
  gap: var(--rl-s-4);
  padding: 0 var(--rl-s-5);
  background: var(--rl-surface);
  border-bottom: 1px solid var(--rl-line);
  position: relative; z-index: 60;
}
.rl-top-brand {
  display: flex; align-items: center; gap: 8px; min-width: 0;
  font-family: var(--rl-font-serif); font-size: 16px; font-weight: 600;
  letter-spacing: -0.01em; color: var(--rl-text);
  /* Now a <button> (2026-09-13) so the logo can open Today — reset the
     button chrome that would otherwise reintroduce a border, a grey
     background and the browser's UI font, none of which anything else
     in this bar has. The font/letter-spacing above already say what
     this element's type should be; only the button-specific resets
     belong here. */
  border: none; background: none; padding: 0; cursor: pointer;
}
.rl-top-brand:hover { color: var(--rl-i-600); }
.rl-top-brand svg { width: 18px; height: 18px; flex: none; color: var(--rl-i-600); }

/* CENTRED ON THE PAGE, NOT ON THE NAV.

   It used to be the middle column of the header's own grid, which put it
   in the middle of whatever was left after the brand and the tools — and
   those are different widths, so 360 sat left of centre and moved as the
   tools changed. Taken out of flow and pinned to 50% instead: the nav's
   own 1fr/auto/1fr makes the hub the centre of the nav, and this makes the
   nav the centre of the window. 360 is now on the window's centre line at
   every width, which is the whole point of putting it there. */
.rl-top-nav {
  position: absolute; inset: 0;
  display: grid;
  /* minmax(0,1fr), not 1fr. `1fr` floors at max-content, so the wider
     group (three items on the left, two on the right) claimed more than
     its half and pushed the hub 32px off centre. Flooring at 0 makes the
     two columns exactly equal, which is what puts the auto column — the
     hub — on the window's centre line. The groups spill outward from
     centre if they outgrow their half, which is the right direction. */
  grid-template-columns: minmax(0, 1fr) auto minmax(0, 1fr);
  align-items: center; gap: var(--rl-s-2);
  /* The nav spans the whole bar so it has a definite width to halve.
     It must not swallow the brand and the tools underneath it. */
  pointer-events: none;
}
/* .rl-top-group itself stays pointer-events:none (inherited from
   .rl-top-nav above) — it is sized to the whole minmax(0,1fr) grid
   column, wider than its right/left-aligned content, and giving IT
   pointer-events would swallow the brand and the tools under its empty
   half exactly the way the comment above says the nav must not. Only
   .rl-navwrap, sized to its one button, needs pointer-events restored. */
.rl-top-nav .rl-navwrap { pointer-events: auto; }
.rl-top-group { display: flex; align-items: center; gap: 2px; }
.rl-top-group.is-left  { justify-content: flex-end; }
.rl-top-group.is-right { justify-content: flex-start; }

.rl-navitem {
  position: relative;
  display: inline-flex; align-items: center; gap: 7px;
  height: 34px; padding: 0 12px;
  border: 1px solid transparent; border-radius: var(--rl-r-md);
  background: none; font-family: inherit; font-size: 13.5px; font-weight: 500;
  color: var(--rl-text-2); cursor: pointer; white-space: nowrap;
  transition: background var(--rl-d-fast) var(--rl-ease-standard),
              color var(--rl-d-fast) var(--rl-ease-standard);
}
.rl-navitem svg { width: 16px; height: 16px; flex: none; }
.rl-navitem:hover { background: var(--rl-surface-sunk); color: var(--rl-text); }
.rl-navitem:focus-visible { outline: none; box-shadow: var(--rl-focus); }
.rl-navitem.is-active { background: var(--rl-tint-accent-bg); color: var(--rl-text-accent); }
.rl-navitem.is-active svg { color: var(--rl-text-accent); }

/* THE CENTRE. 360 is the only nav item that is drawn rather than
   labelled: an outlined pill when you are elsewhere, filled when you are
   in it. It reads as the home of the product without needing a bigger
   font or a different colour from the rest of the bar. */
.rl-navitem.is-hub {
  height: 36px; padding: 0 18px;
  border-color: var(--rl-line-strong);
  color: var(--rl-text); font-weight: 600; letter-spacing: -0.005em;
}
.rl-navitem.is-hub:hover { border-color: var(--rl-i-400); background: var(--rl-tint-accent-bg); }
.rl-navitem.is-hub.is-active {
  background: var(--rl-i-600); border-color: var(--rl-i-600); color: var(--rl-n-0);
  box-shadow: var(--rl-e-1);
}
.rl-navitem.is-hub.is-active svg { color: var(--rl-n-0); }

.rl-navwrap { position: relative; display: inline-flex; }
.rl-nav-caret { width: 13px !important; height: 13px !important; opacity: .55; margin-left: -2px; }
.rl-navitem[aria-expanded="true"] .rl-nav-caret { transform: rotate(180deg); }
.rl-navpop { top: calc(100% + 6px); left: 50%; transform: scale(.98) translateX(-50%) translateY(-4px); transform-origin: top center; }
.rl-navpop[data-open="true"] { transform: translateX(-50%); }

.rl-navitem .rl-badge {
  min-width: 17px; height: 17px; padding: 0 5px;
  border-radius: var(--rl-r-full); font-family: var(--rl-font-mono);
  font-size: 10.5px; line-height: 17px; text-align: center;
  background: var(--rl-surface-sunk); color: var(--rl-text-3);
}
.rl-navitem .rl-badge[data-sev="fail"] { background: var(--rl-tint-red-bg);   color: var(--rl-tint-red-fg); }
.rl-navitem .rl-badge[data-sev="time"] { background: var(--rl-tint-amber-bg); color: var(--rl-tint-amber-fg); }
.rl-navitem .rl-badge[data-sev="info"] { background: var(--rl-tint-accent-bg);color: var(--rl-text-accent); }
.rl-navitem.is-hub.is-active .rl-badge { background: rgba(255,255,255,.22); color: var(--rl-n-0); }

.rl-top-tools {
  display: flex; align-items: center; justify-content: flex-end;
  gap: 2px; min-width: 0;
}
.rl-top-tools .rl-search-trigger {
  height: 32px; padding: 0 10px; margin-right: 4px;
  display: inline-flex; align-items: center; gap: 7px;
  border: 1px solid var(--rl-line); border-radius: var(--rl-r-md);
  background: var(--rl-surface-sunk); color: var(--rl-text-3);
  font-family: inherit; font-size: 13px; cursor: pointer;
}
.rl-top-tools .rl-search-trigger:hover { border-color: var(--rl-line-strong); color: var(--rl-text-2); }
.rl-top-tools .rl-search-trigger svg { width: 15px; height: 15px; }
.rl-top-tools .rl-user { height: 32px; padding: 0 6px 0 4px; border: none; background: none; }

.rl-top-tools {
  display: flex; align-items: center; justify-content: flex-end;
  gap: 2px; min-width: 0;
}
.rl-top-tools .rl-search-trigger {
  height: 32px; padding: 0 10px; margin-right: 4px;
  display: inline-flex; align-items: center; gap: 7px;
  border: 1px solid var(--rl-line); border-radius: var(--rl-r-md);
  background: var(--rl-surface-sunk); color: var(--rl-text-3);
  font-family: inherit; font-size: 13px; cursor: pointer;
}
.rl-top-tools .rl-search-trigger:hover { border-color: var(--rl-line-strong); color: var(--rl-text-2); }
.rl-top-tools .rl-search-trigger svg { width: 15px; height: 15px; }
.rl-top-tools .rl-user { height: 32px; padding: 0 6px 0 4px; border: none; background: none; }

/* -------------------------------------------------------------------------
   THE SUB-BAR — the active section's own destinations.
   Hidden entirely when a section has one place to go, so Today and 360 do
   not carry an empty band. This is the second row of ONE navigation, not
   a second navigation: same items the rail used to expand beneath a
   section, laid along instead of down.
   ------------------------------------------------------------------------- */
.rl-subnav {
  grid-row: 2; grid-column: 1;
  height: var(--rl-subnav-h);
  display: flex; align-items: center; gap: 2px;
  padding: 0 var(--rl-s-5);
  background: var(--rl-surface);
  border-bottom: 1px solid var(--rl-line);
  overflow-x: auto; scrollbar-width: none;
  position: relative; z-index: 50;
}
.rl-subnav::-webkit-scrollbar { display: none; }
.rl-subnav[hidden] { display: none; }
.rl-subitem {
  position: relative;
  height: var(--rl-subnav-h); padding: 0 11px;
  display: inline-flex; align-items: center; gap: 6px;
  border: none; background: none; font-family: inherit;
  font-size: 13px; color: var(--rl-text-3); cursor: pointer; white-space: nowrap;
}
.rl-subitem:hover { color: var(--rl-text); }
.rl-subitem:focus-visible { outline: none; box-shadow: var(--rl-focus); }
.rl-subitem[aria-current="page"] { color: var(--rl-text); font-weight: 500; }
.rl-subitem[aria-current="page"]::after {
  content: ""; position: absolute; left: 9px; right: 9px; bottom: 0;
  height: 2px; border-radius: 2px 2px 0 0; background: var(--rl-i-600);
}
.rl-subitem .n {
  font-family: var(--rl-font-mono); font-size: 10.5px; color: var(--rl-text-3);
}

/* =========================================================================
   §4.3 TOPBAR — 52px, one band
   Breadcrumb, spacer, live dot, bell, help. That is all.
   No background at scrollTop 0; it sits on the canvas. No shadow. Ever.
   ========================================================================= */
.rl-frame {
  grid-row: 3; grid-column: 1;
  display: flex; flex-direction: column;
  min-width: 0; min-height: 0;
  position: relative;
}


.rl-icon-btn {
  width: 32px; height: 32px;
  display: grid; place-items: center;
  border: none; background: transparent;
  border-radius: var(--rl-r-sm);
  color: var(--rl-text-2); cursor: pointer;
  position: relative;
  transition: background var(--rl-d-instant) var(--rl-ease-standard), color var(--rl-d-instant);
}
.rl-icon-btn svg { width: 17px; height: 17px; }
.rl-icon-btn:hover { background: var(--rl-n-50); color: var(--rl-text); }
[data-theme="dark"] .rl-icon-btn:hover { background: var(--rl-surface-raised); }
.rl-icon-btn:active { transform: scale(0.98); }
.rl-icon-btn:focus-visible { outline: none; box-shadow: var(--rl-focus); }
.rl-icon-btn .rl-dot {
  position: absolute; top: 4px; right: 4px;
  min-width: 15px; height: 15px; padding: 0 3px;
  border-radius: var(--rl-r-xs);
  background: var(--rl-r-500); color: #fff;
  font-size: 10px; font-weight: 600; line-height: 15px; text-align: center;
  font-variant-numeric: tabular-nums;
}

/* System health in the bar: a status dot, and a count only when something is wrong. */
.rl-health-dot { position: absolute; top: 5px; right: 5px; width: 8px; height: 8px; border-radius: var(--rl-r-full);
  background: var(--rl-n-300); box-shadow: 0 0 0 2px var(--rl-surface); }
.rl-health-dot[data-s="ok"]   { background: var(--rl-g-500); }
.rl-health-dot[data-s="warn"] { background: var(--rl-a-500); }
.rl-health-dot[data-s="fail"] { background: var(--rl-r-500); }
.rl-health-dot[data-n] { width: auto; height: 15px; min-width: 15px; top: 3px; right: 2px; padding: 0 3px; border-radius: var(--rl-r-xs);
  color: var(--rl-surface); font-size: 10px; font-weight: 600; line-height: 15px; text-align: center; font-variant-numeric: tabular-nums; }
.rl-health-dot[data-n]::after { content: attr(data-n); }

/* The one permitted looping animation in the product (§8.1 law 6). */
.rl-live {
  width: 8px; height: 8px; border-radius: var(--rl-r-full);
  background: var(--rl-n-300); flex: none; margin: 0 6px;
}
.rl-live[data-live="true"] { background: var(--rl-g-500); animation: rl-live-pulse 2.4s var(--rl-ease-standard) infinite; }
@keyframes rl-live-pulse { 0%,100% { opacity: 1; } 50% { opacity: .45; } }

/* §15.4 Global progress — 2px, indeterminate, transform only. It fades out
   on completion; it does not run to 100% first. */
.rl-progress {
  position: absolute; top: 0; left: 0; right: 0;
  height: 2px; overflow: hidden; z-index: 41;
  opacity: 0; transition: opacity var(--rl-d-fast) var(--rl-ease-standard);
  pointer-events: none;
}
.rl-progress[data-busy="true"] { opacity: 1; }
.rl-progress::after {
  content: ''; position: absolute; inset: 0;
  background: var(--rl-i-600);
  transform: translateX(-100%);
  animation: rl-progress-slide 1.1s var(--rl-ease-standard) infinite;
}
@keyframes rl-progress-slide {
  0%   { transform: translateX(-100%) scaleX(.4); }
  50%  { transform: translateX(0%)    scaleX(.7); }
  100% { transform: translateX(100%)  scaleX(.4); }
}

/* §11.3 Offline bar. */
.rl-netbar {
  flex: none; height: 0; overflow: hidden;
  display: flex; align-items: center; gap: 8px;
  padding: 0 var(--rl-s-6);
  font-size: 13px;
  background: var(--rl-tint-amber-bg); color: var(--rl-tint-amber-fg);
  transition: height var(--rl-d-mid) var(--rl-ease-standard);
}
.rl-netbar[data-state="offline"] { height: 28px; }
.rl-netbar[data-state="back"] { height: 28px; background: var(--rl-tint-green-bg); color: var(--rl-tint-green-fg); }

/* =========================================================================
   §7.1 THE ONE SCROLL CONTAINER
   scrollbar-gutter: stable is required — without it every navigation
   between a short page and a long page shifts the layout by the scrollbar
   width.
   ========================================================================= */
.rl-scroll {
  flex: 1; min-height: 0;
  overflow-y: auto;
  overscroll-behavior: contain;
  scrollbar-gutter: stable;
  position: relative;
}

/* =========================================================================
   §4.4 PAGE HEADER — REMOVED. See the note in js/shell.js and DECISIONS.md.

   Each view keeps its own <h2 class="view-title"> in the markup. It is not
   `display: none` — that would take the document's heading away from a
   screen reader along with the pixels. It is clipped instead, so the
   heading order is intact, the print rules below can put it back, and
   nothing is painted.
   ========================================================================= */
.rl-sr-only,
.view > h2.view-title,
.view > .view-head > div > h2.view-title {
  position: absolute; width: 1px; height: 1px;
  margin: -1px; padding: 0; overflow: hidden;
  clip: rect(0 0 0 0); clip-path: inset(50%); white-space: nowrap;
}

/* Static subtitles explained what a view was for — worth reading once,
   furniture every day after. Only the two that carry live state render,
   and they render at the top of the view, describing the content they
   were always about. */
.view > p.view-sub,
.view > .view-head > div > p.view-sub { display: none; }
.view > p.view-sub.view-sub-live {
  display: block;
  margin: 0 0 var(--rl-s-4);
  font-size: 13.5px; line-height: 19px;
  color: var(--rl-text-2); max-width: 68ch;
}

/* Today's greeting (2026-09-22) — the h2 above it is screen-reader-only
   (see §4.4 note above), so this is the actual first line of the home
   page, right under the top bar. Reuses the .rl-title-1 type scale rather
   than inventing a new size. */
.view > p.today-greeting {
  margin: 0 0 var(--rl-s-1);
}

/* The crumb is the fallback for widths where the rail cannot name the
   section — see rlRenderCrumbs(). At full width the rail says it already. */
@media (min-width: 1024px) { .rl-crumbs { display: none; } }

/* §6.4 Section tab bar. Underline slides; panels cross-fade only. */
.rl-tabs {
  max-width: var(--rl-content-max); margin: 0 auto;
  display: flex; align-items: center; gap: 2px;
  position: relative;
  overflow-x: auto; scrollbar-width: none;
}
.rl-tabs::-webkit-scrollbar { display: none; }
.rl-tabs-wrap {
  padding: 0 var(--rl-s-6);
  background: var(--rl-canvas);
  border-bottom: 1px solid var(--rl-line);
  /* Nothing sits above it inside the scroll region any more, so it sticks
     to the top of it. The topbar is a flex sibling OUTSIDE .rl-scroll, not
     a layer above it, so its height is not part of this offset. */
  position: sticky; top: 0; z-index: 29;
}
.rl-tabs-wrap:empty, .rl-tabs-wrap[hidden] { display: none; }
.rl-tab {
  border: none; background: transparent;
  padding: 9px 11px 10px;
  font-size: 13.5px; font-weight: 400;
  color: var(--rl-text-2); cursor: pointer;
  white-space: nowrap; position: relative;
  border-radius: var(--rl-r-sm) var(--rl-r-sm) 0 0;
  transition: color var(--rl-d-instant) var(--rl-ease-standard),
              background var(--rl-d-instant) var(--rl-ease-standard);
}
.rl-tab:hover { color: var(--rl-text); background: var(--rl-n-50); }
[data-theme="dark"] .rl-tab:hover { background: var(--rl-surface-raised); }
.rl-tab:focus-visible { outline: none; box-shadow: var(--rl-focus); }
.rl-tab[aria-selected="true"] { color: var(--rl-text); font-weight: 500; }
.rl-tab-underline {
  position: absolute; bottom: -1px; left: 0;
  height: 2px; background: var(--rl-i-600);
  border-radius: 1px 1px 0 0;
  opacity: 0;
  transition: transform var(--rl-d-fast) var(--rl-ease-standard),
              width var(--rl-d-fast) var(--rl-ease-standard),
              opacity var(--rl-d-fast) var(--rl-ease-standard);
}
.rl-tab-underline[data-visible="true"] { opacity: 1; }

/* =========================================================================
   §6.3 SECTION CHANGE — the shell never moves; only content transitions.
   Exit is opacity only: exiting content that slides reads as a page being
   shoved. Enter adds a 6px rise, which is a settle, not an arrival.
   ========================================================================= */
.rl-content { padding: 0 var(--rl-s-6) var(--rl-s-16); }
main.content, #app main.content {
  max-width: var(--rl-content-max);
  margin: 0 auto;
  padding: var(--rl-s-5) 0 0;
}
/* An ANIMATION, not a pair of transitioned states.

   The obvious implementation — set opacity:0, then hand the browser a
   requestAnimationFrame that sets it back to 1 — has a failure mode that
   matters: rAF does not fire in a backgrounded tab. Navigate, switch away
   before the frame lands, come back, and the callback queue replays into a
   content region that is still at zero opacity. The page is blank and
   nothing will ever fix it.

   With an un-filled animation, the resting style IS the visible one. The
   animation is the only thing that can hide the content, and only while it
   is actually running. If it never runs — throttled tab, reduced motion, a
   browser that dropped the frame — the content is simply there. */
@keyframes rl-view-in {
  from { opacity: 0; transform: translateY(6px); }
  to   { opacity: 1; transform: none; }
}
.rl-content { opacity: 1; }
.rl-content[data-phase="enter"] {
  animation: rl-view-in var(--rl-d-base) var(--rl-ease-enter);
}
/* §6.5 A property switch dims and holds its layout rather than unmounting,
   so there is no scrollbar flash while the new figures arrive. */
.rl-content[data-dim="true"] {
  opacity: .4; pointer-events: none;
  transition: opacity var(--rl-d-fast) var(--rl-ease-standard);
}

/* The title/subtitle rules that used to live here hid the in-view copies
   because the sticky page header held the real ones. Both are handled at
   §4.4 above now — the title clipped rather than display:none so it stays
   in the accessibility tree, and only the live subtitles rendering. */
.view > .view-head { justify-content: flex-end; margin-bottom: var(--rl-s-3); }
.view > .view-head > div:empty { display: none; }

/* =========================================================================
   §4.2 BREAKPOINTS — the bar narrows before it gives anything up.

   The centre item is the last thing to lose its label, because it is the
   one people aim at. Below 720px the bar keeps only the brand mark, the
   360 pill and the tools, and the sections move to the bottom tab bar
   where a thumb can reach them (§16).
   ========================================================================= */
/* 1320, not 1180: the nav is absolute and spills outward from the centre,
   so the right group (Operations, Inbox) meets the tools cluster long
   before the bar itself is short of room. Measured: at 1200px the full
   search trigger sat 40px under "Inbox". Collapsing it to its icon here
   buys 80px, which keeps daylight down to the next breakpoint. */
@media (max-width: 1320px) {
  .rl-top { gap: var(--rl-s-3); padding: 0 var(--rl-s-4); }
  .rl-top-brand span { display: none; }
  .rl-navitem { padding: 0 9px; }
  .rl-top-tools .rl-search-trigger span, .rl-top-tools .rl-search-trigger .rl-kbd { display: none; }
  .rl-top-tools .rl-search-trigger { width: 32px; padding: 0; justify-content: center; }
}
@media (max-width: 980px) {
  .rl-navitem .rl-nav-label { display: none; }
  .rl-navitem { padding: 0 10px; }
  .rl-navitem.is-hub .rl-nav-label { display: inline; }
  .rl-user-name { display: none; }
}

/* =========================================================================
   §16 MOBILE — below 720px this is a different application with the same
   data. Marcus is standing up, one hand free, on a bad signal in a basement
   post room. Do not squeeze the desktop layout.
   ========================================================================= */
.rl-tabbar { display: none; }

@media (max-width: 719px) {
  .rl-top-nav .rl-top-group { display: none; }
  /* With both groups gone the hub was the grid's only child, and auto-
     placement dropped it into column 1 — hard left, on top of the brand
     mark. Pinning it to the centre column instead put it on top of the
     tools: six icons and a pill do not both fit around the centre line
     of a 375px bar. So on a phone the nav stops being absolute and takes
     its place in the flow — brand, then the pill, then the tools — which
     is the only arrangement that overlaps nothing. */
  .rl-top-nav { position: static; inset: auto; display: flex; pointer-events: auto; }
  .rl-subnav { padding: 0 var(--rl-s-4); }
  .rl-content, .rl-tabs-wrap { padding-left: var(--rl-s-4); padding-right: var(--rl-s-4); }
  .rl-scroll { padding-bottom: calc(56px + env(safe-area-inset-bottom)); }

  .rl-tabbar {
    display: grid; grid-template-columns: repeat(5, 1fr);
    position: fixed; left: 0; right: 0; bottom: 0; z-index: 50;
    height: calc(56px + env(safe-area-inset-bottom));
    padding-bottom: env(safe-area-inset-bottom);
    background: var(--rl-surface);
    border-top: 1px solid var(--rl-line);
  }
  .rl-tabbar-item {
    display: flex; flex-direction: column; align-items: center; justify-content: center; gap: 3px;
    border: none; background: transparent; cursor: pointer;
    color: var(--rl-n-500); font-size: 11px; min-height: 44px;
  }
  .rl-tabbar-item svg { width: 22px; height: 22px; }
  .rl-tabbar-item[aria-current="page"] { color: var(--rl-i-600); }
  [data-theme="dark"] .rl-tabbar-item[aria-current="page"] { color: var(--rl-i-400); }

  /* §16.2 Tap targets ≥ 44px. Non-negotiable — and that includes the
     filter chips, which are 30px on a pointer and were the only controls
     still under the bar on a thumb. */
  .rl-icon-btn, .rl-tab { min-height: 44px; }
  .rl-icon-btn { width: 44px; }
  /* The chip rules live in screens.css, which loads after this file, so
     the mobile override has to live there too — see the tail of it. */
}

/* =========================================================================
   §19.4 PRINT — statements and reports. Force light, drop the chrome,
   repeat table headers, keep rows whole.
   ========================================================================= */
@media print {
  :root, [data-theme="dark"] {
    --rl-canvas: #fff; --rl-surface: #fff; --rl-text: #000;
    --rl-text-2: #333; --rl-text-3: #555; --rl-line: #bbb; --rl-line-soft: #ddd;
  }
  .rl-top, .rl-subnav, .rl-tabbar, .rl-progress, .rl-netbar,
  .view-toolbar, .rl-tabs-wrap, .team-chat { display: none !important; }
  #app { display: block; height: auto; overflow: visible; }
  .rl-frame, .rl-scroll { display: block; height: auto; overflow: visible; }
  /* On screen the top bar says which view this is. On paper there is no
     bar, so the heading comes back out of the clip and titles the
     printout. */
  .view > h2.view-title, .view > .view-head > div > h2.view-title {
    position: static; width: auto; height: auto; margin: 0 0 4px;
    overflow: visible; clip: auto; clip-path: none; white-space: normal;
    font-family: var(--rl-font-serif); font-size: 20px; font-weight: 600;
  }
  main.content, #app main.content { max-width: none; }
  thead { display: table-header-group; }
  tr, .card, .list-card { page-break-inside: avoid; }
}
