/* ============================================================
   Shop Manager Pro — shared shell + page design system.
   Dark top bar → grey content well, with a fixed bottom nav and
   "More" sheet on mobile.
   ============================================================ */

:root {
  --rg-bg:        #f4f6f9;
  --rg-surface:   #ffffff;
  --rg-border:    #e3e8ef;
  --rg-text:      #1a2332;
  --rg-muted:     #64748b;
  --rg-accent:    #2563eb;
  --rg-accent2:   #0ea5e9;
  --rg-success:   #16a34a;
  --rg-warn:      #d97706;
  /* An inspection's middle grade is called yellow by everyone who uses
     one, so it is yellow here rather than the amber the rest of the app
     warns in. Dark ink on it: white on yellow is unreadable. */
  --rg-dvi-yellow:     #eab308;
  --rg-dvi-yellow-ink: #4a2f08;
  --rg-danger:    #dc2626;
  --rg-sidebar:   260px;
  --rg-radius:    10px;
  --rg-shadow:    0 1px 3px rgba(0,0,0,.08), 0 1px 2px rgba(0,0,0,.04);
  --rg-shadow-md: 0 4px 12px rgba(0,0,0,.10);

  /* Display cutouts and home indicators, named once so every bar against
     an edge can account for them. Zero everywhere that has none.

     They only resolve to anything at all if index.php asks for the whole
     screen — viewport-fit=cover, and black-translucent for a home-screen
     launch. Either one missing and iOS hands the page a box that is
     already inset and reports 0 for both ends, which looks fine at the
     top and puts the bottom nav under the gesture handle. */
  --safe-t: env(safe-area-inset-top, 0px);
  --safe-b: env(safe-area-inset-bottom, 0px);
  --safe-l: env(safe-area-inset-left, 0px);
  --safe-r: env(safe-area-inset-right, 0px);

  /* The mobile bottom nav's height, named once. The page no longer
     reserves it — the bar is a flex child of the shell and takes its own
     room — so what is left reading this are the bar itself and the three
     things that float ABOVE it and have to clear it: the new-order
     button, the activity toast, and the version badge. */
  --nav-h: 64px;

  /* ── The scroll edge effect ──
     iOS 26 fades the first ~20px of the page under its status bar: a
     soft gradient at the top edge of the scroll view, INSIDE the
     viewport, saying "content passes under here". It has nothing to do
     with safe areas — measured on an iPhone 16 Pro it is there with
     viewport-fit=cover and without it, with safe-area-inset-top
     reporting 0px and the viewport already inset to 812 of 874. Nothing
     the page can set turns it off.

     So nothing important is put in that band. 22px, measured at ~20 and
     rounded up, held clear at the top of whichever bar is the topmost
     thing on a screen. Zero everywhere but iOS — -webkit-touch-callout
     is the narrowest honest test for "this is an iPhone or an iPad",
     and no other platform draws this.

     Used as max(--safe-t, --edge-fade), never as a sum. The fade is
     drawn at the top of the viewport, so where the page runs under the
     status bar the inset has already cleared it and adding the two
     together would just make the bar 22px taller than it needs to be.
     Where the viewport is already inset — a home-screen app, which is
     where this was found — the inset is 0 and the fade is the whole of
     the allowance. */
  --edge-fade: 0px;
}

@supports (-webkit-touch-callout: none) {
  :root { --edge-fade: 22px; }
}

[ng\:cloak], [ng-cloak], .ng-cloak { display: none !important; }

/* ============================================================
   EDGE PAINT — how iOS tints its own chrome
   ============================================================
   iOS draws its status strip and its bottom toolbar over the top and
   bottom edges of the page, and decides what colour to make them by
   sampling the CSS of any FIXED or STICKY element sitting near those
   edges: its background-color, and whether it carries a backdrop-filter.
   It does not read theme-color for this.

   Every bar in this app is fixed or sticky against an edge, so iOS was
   picking one of them and laying the shop's white page bar across the
   status area — over a black app bar. Making the app bar sticky so that
   it would be sampled first did not help, because the mechanism is the
   problem, not which bar wins: the answer is always a sheet of colour
   painted over a bar that is already the right colour.

   So no bar hands it a colour to sample. Each paints itself with an
   absolutely positioned ::before, which iOS ignores, and the sampler
   falls through to the root — set, explicitly, to the colour of the
   chrome that screen actually wears.

   Two rules for anything new that sits against an edge:
     1. Background on ::before, never on the fixed or sticky element.
     2. display:none when closed. An overlay parked off screen at
        opacity 0 still tints the toolbar.
   ============================================================ */

/* A transparent root falls back to white, which is the bug on a dark
   screen. Black, because every signed-in office screen wears the black
   app bar at the top and the black nav at the bottom. The three screens
   with no chrome of their own — the technician's portal, sign-in, and
   the customer's report — are light at both ends and say so. Getting
   this backwards is not subtle: it is a black band across the top of
   somebody's inspection report. */
html,
body { background-color: #111; }
body.smp-body-light { background-color: var(--rg-bg); }

/* The paint. Absolutely positioned, so it covers the bar and is
   invisible to the sampler; out of flow, so it is not a flex item in
   any of these flex containers. Each host carries a z-index already,
   which is what keeps a z-index:-1 child inside its own box instead of
   behind the page. */
.smp-appbar::before,
.rg-topbar::before,
.smp-portal-bar::before,
.rg-bottom-nav::before,
.smp-clockbar::before,
.rg-more-sheet::before,
.smp-toast::before,
.rg-fab::before {
  content: '';
  position: absolute;
  inset: 0;
  z-index: -1;
  border-radius: inherit;
}
.smp-appbar::before     { background: #111; }
.rg-topbar::before      { background: var(--rg-surface); }
.smp-portal-bar::before { background: var(--rg-surface); }
.rg-bottom-nav::before  { background: #111; }
.rg-more-sheet::before  { background: #181818; }
.smp-toast::before      { background: var(--rg-surface); }
.rg-fab::before         { background: var(--rg-accent); transition: background .12s; }
/* The one that also carried a backdrop-filter, which iOS reads as "this
   bar is glass — tint my toolbar to match". */
.smp-clockbar::before {
  background: rgba(255,255,255,.97);
  backdrop-filter: blur(8px);
}

/* ============================================================
   TOP BAR (desktop)
   Brand, primary destinations, an overflow menu, and the one action
   worth a permanent button — all on a single 56px bar.
   ============================================================ */
.smp-nav {
  align-items: center;
  gap: 18px;
  /* min-height, not height: the bar grows by the top inset when the app
     runs full-screen from the home screen. */
  min-height: 56px;
  padding: max(var(--safe-t), var(--edge-fade)) calc(20px + var(--safe-r)) 0 calc(20px + var(--safe-l));
  background: #111;
  border-bottom: 1px solid rgba(255,255,255,.08);
  flex-shrink: 0;
}

.smp-brand {
  display: flex;
  align-items: center;
  gap: 9px;
  text-decoration: none;
  flex-shrink: 0;
  margin-right: 4px;
}
.smp-mark {
  display: flex;
  align-items: center;
  justify-content: center;
  width: 30px; height: 30px;
  border-radius: 7px;
  background: var(--rg-accent);
  color: #fff;
  font-size: .66rem;
  font-weight: 700;
  letter-spacing: .02em;
}
.smp-word {
  color: #e5e7eb;
  font-size: .95rem;
  font-weight: 500;
  letter-spacing: -.01em;
  white-space: nowrap;
}
.smp-word b { font-weight: 700; color: #fff; }

.smp-links { display: flex; align-items: center; gap: 2px; flex: 1; min-width: 0; }

.smp-link {
  display: inline-flex;
  align-items: center;
  gap: 7px;
  padding: 8px 12px;
  border-radius: 8px;
  border: none;
  background: none;
  color: #9ca3af;
  font-size: .875rem;
  font-weight: 500;
  text-decoration: none;
  white-space: nowrap;
  transition: color .12s, background .12s;
}
.smp-link i { font-size: .82rem; }
.smp-link:hover { color: #fff; background: rgba(255,255,255,.07); }
.smp-link.active { color: #fff; background: rgba(255,255,255,.12); }
/* Bootstrap's caret would crowd the label. */
.smp-link.dropdown-toggle::after { margin-left: 5px; opacity: .6; }

.smp-badge {
  background: var(--rg-danger);
  color: #fff;
  font-family: 'DM Mono', monospace;
  font-size: .68rem;
  font-weight: 500;
  border-radius: 20px;
  padding: 1px 6px;
}

.smp-cta {
  display: inline-flex;
  align-items: center;
  gap: 7px;
  flex-shrink: 0;
  background: var(--rg-accent);
  color: #fff;
  border-radius: 8px;
  padding: 8px 15px;
  font-size: .85rem;
  font-weight: 600;
  text-decoration: none;
  transition: background .12s;
}
.smp-cta:hover { background: #1d4ed8; color: #fff; }

/* Overflow menu */
.smp-menu {
  background: #1a1a1d;
  border: 1px solid rgba(255,255,255,.1);
  border-radius: 10px;
  padding: 6px;
  min-width: 232px;
  box-shadow: 0 10px 30px rgba(0,0,0,.35);
}
.smp-menu-label {
  font-size: .62rem;
  font-weight: 700;
  letter-spacing: .14em;
  text-transform: uppercase;
  color: #6b7280;
  padding: 7px 10px 4px;
}
.smp-menu .dropdown-item {
  display: flex;
  align-items: center;
  gap: 10px;
  border-radius: 6px;
  padding: 8px 10px;
  color: #d1d5db;
  font-size: .875rem;
}
.smp-menu .dropdown-item:hover,
.smp-menu .dropdown-item:focus { background: rgba(255,255,255,.09); color: #fff; }
.smp-menu .dropdown-item i { width: 15px; text-align: center; color: #9ca3af; font-size: .8rem; }
.smp-menu .dropdown-divider { border-color: rgba(255,255,255,.1); margin: 5px 0; }

/* Wide-enough screens get the labels; narrower desktops keep the icons. */
@media (max-width: 1180px) {
  .smp-link span, .smp-cta span { display: none; }
  .smp-link { padding: 8px 10px; }
  .smp-word { display: none; }
}

/* ============================================================
   APP BAR (phone)
   The other half of .smp-nav. That bar is d-lg-flex, so below 992px
   the app had no chrome of its own at all: no shop name, no account,
   no search, and the activity bell two taps down inside a sheet. The
   bottom nav carries the destinations; this carries everything that
   is not one.
   ============================================================ */
.smp-appbar {
  display: flex;
  align-items: center;
  gap: 8px;
  /* min-height so the bar grows by the cutout rather than eating into
     its own 52px — the same reason .smp-nav is written this way. */
  min-height: 52px;
  /* --edge-fade keeps the brand and the icons out of the band iOS fades
     across the top of the page. See :root. */
  padding: max(var(--safe-t), var(--edge-fade)) calc(10px + var(--safe-r)) 0 calc(10px + var(--safe-l));
  /* The black is on ::before — see EDGE PAINT. */
  border-bottom: 1px solid rgba(255,255,255,.08);
  flex-shrink: 0;
  position: relative;
  z-index: 210;
}
.smp-appbar-brand { flex: none; text-decoration: none; }
.smp-appbar-brand .smp-mark { width: 28px; height: 28px; font-size: .6rem; }

/* The shop name takes the room, and gives it back to the buttons when
   the name is long. */
.smp-appbar-shop { flex: 1; min-width: 0; }
.smp-appbar-shopbtn {
  display: block;
  width: 100%;
  border: 0;
  background: none;
  padding: 4px 0;
  text-align: left;
  color: #e5e7eb;
  font-family: inherit;
  font-size: .9rem;
  font-weight: 600;
  letter-spacing: -.01em;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}
.smp-appbar-shopbtn.dropdown-toggle::after { margin-left: 6px; opacity: .55; }
.smp-appbar-shopfixed { pointer-events: none; }

/* 40px targets: this row is thumbs only. */
.smp-appbar-btn {
  position: relative;
  flex: none;
  display: flex;
  align-items: center;
  justify-content: center;
  width: 40px; height: 40px;
  border: 0;
  border-radius: 10px;
  background: transparent;
  color: #9ca3af;
  font-size: 1rem;
}
.smp-appbar-btn:active,
.smp-appbar-btn.open { background: rgba(255,255,255,.1); color: #fff; }

/* Count, not a dot: "three people are waiting" and "somebody is" are
   different sizes of interruption. Two characters at most — past nine
   the exact number stops changing what anyone does about it. */
.smp-appbar-count {
  position: absolute;
  top: 4px; right: 3px;
  min-width: 16px;
  padding: 0 4px;
  border-radius: 20px;
  background: var(--rg-danger);
  color: #fff;
  font-family: 'DM Mono', monospace;
  font-size: .6rem;
  line-height: 16px;
  text-align: center;
}
.smp-appbar-account { flex: none; }
.smp-appbar-account .smp-account-btn { width: 34px; height: 34px; }

/* ===== BOTTOM NAV (mobile) ===== */
.rg-bottom-nav {
  /* In the column, not over it. Fixed, it floated on top of the page and
     every screen had to reserve exactly its height at the foot of the
     scroller — an arithmetic the scroller had no business knowing, and
     which was wrong on every screen the bar does not render on. As the
     third child of a flex column it takes its own room, and a bar that
     is not there gives that room back without anybody being told. */
  flex-shrink: 0;

  height: calc(var(--nav-h) + var(--safe-b));
  /* The insets still belong here — this is the element against the
     edge, and the gesture handle is underneath it. */
  padding-bottom: var(--safe-b);
  /* Landscape on a notched phone puts the cutout beside the bar. */
  padding-left: var(--safe-l);
  padding-right: var(--safe-r);

  border-top: 1px solid rgba(255,255,255,.08);

  display: flex;
  justify-content: space-around;
  align-items: center;

  /* relative, not static, and the z-index is not decoration: the bar's
     black is painted by a ::before at z-index:-1 (see EDGE PAINT), and a
     negative child only stays inside its parent's box if the parent is a
     stacking context. Static, it would paint behind the shell's white
     and the bar would vanish. */
  position: relative;
  z-index: 1200;
}
.rg-bottom-nav a {
  flex: 1;
  text-align: center;
  text-decoration: none;

  color: #9ca3af;
  font-size: 11px;

  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;

  gap: 2px;
}
.rg-bottom-nav i {
  font-size: 17px;
  /* The pill is on the icon, not the whole cell: a full-cell highlight
     on a four-across bar reads as a pressed button rather than as where
     you are. */
  display: flex;
  align-items: center;
  justify-content: center;
  height: 26px;
  min-width: 44px;
  border-radius: 13px;
  transition: background .12s;
}
.rg-bottom-nav a.active { color: #fff; }
.rg-bottom-nav a.active i { background: rgba(255,255,255,.14); }
.rg-bottom-nav a:active i { background: rgba(255,255,255,.2); }

/* ===== NEW REPAIR ORDER (phone) =====
   Above the nav rather than in it. Four destinations and one action are
   different kinds of thing, and a tab that isn't a place is a tab people
   stop trusting. Clear of the bar and of the home indicator; the version
   badge owns the other corner. */
.rg-fab {
  position: fixed;
  z-index: 1190;                     /* under the nav (1200) and its sheet */
  right: calc(16px + var(--safe-r));
  bottom: calc(var(--nav-h) + var(--safe-b) + 16px);
  display: flex;
  align-items: center;
  justify-content: center;
  width: 56px; height: 56px;
  border-radius: 18px;
  color: #fff;
  font-size: 1.3rem;
  text-decoration: none;
  box-shadow: 0 6px 20px rgba(37,99,235,.42), 0 2px 6px rgba(16,24,40,.2);
  transition: transform .12s;
}
.rg-fab:active { transform: scale(.93); color: #fff; }
.rg-fab:active::before { background: #1d4ed8; }

/* ===== "MORE" SHEET (bottom-nav overflow) ===== */
.rg-more-overlay {
  display: none;
  position: fixed; inset: 0;
  background: rgba(0,0,0,.4);
  z-index: 1250;
}
.rg-more-overlay.open { display: block; }
.rg-more-sheet {
  position: fixed;
  left: 0; right: 0; bottom: 0;
  z-index: 1300;
  border-radius: 16px 16px 0 0;
  padding: 8px var(--safe-r) calc(10px + var(--safe-b)) var(--safe-l);
  transform: translateY(100%);
  /* Off screen is not gone: a fixed sheet parked at translateY(100%) is
     still a fixed element against the bottom edge, and iOS samples it
     for its toolbar's colour. Hidden when closed — and the slide still
     runs, because visibility is held back until the transform has
     finished. */
  visibility: hidden;
  transition: transform .2s ease, visibility 0s linear .2s;
}
.rg-more-sheet.open {
  transform: translateY(0);
  visibility: visible;
  transition: transform .2s ease, visibility 0s;
}
.rg-more-sheet-handle {
  width: 36px; height: 4px; border-radius: 2px;
  background: rgba(255,255,255,.25);
  margin: 6px auto 8px;
}
.rg-more-sheet a {
  display: flex; align-items: center; gap: 12px;
  padding: 13px 20px;
  color: #e5e7eb; text-decoration: none; font-size: 14px;
}
.rg-more-sheet a i { width: 20px; text-align: center; color: #9ca3af; font-size: 16px; }
.rg-more-sheet a.active,
.rg-more-sheet a.active i { color: #fff; }

/* Grouped sheet: labels break the list into Operations / Configuration
   so it reads as a menu rather than a pile of links. */
.rg-sheet-label {
  font-size: .62rem;
  font-weight: 700;
  letter-spacing: .14em;
  text-transform: uppercase;
  color: #6b7280;
  padding: 12px 20px 4px;
}
.rg-more-sheet a.rg-sheet-cta {
  margin: 4px 14px 8px;
  border-radius: 8px;
  background: var(--rg-accent);
  color: #fff;
  font-weight: 600;
  justify-content: center;
  padding: 12px 20px;
}
.rg-more-sheet a.rg-sheet-cta i { color: #fff; }

/* ===== SCROLL CONTAINMENT =====
   The shell is one flex column — app bar, this, bottom nav — and only
   the middle of it scrolls. Nothing here reserves room for the bars:
   they are siblings that take their own, so a screen without a bottom
   nav (the portal, sign-in, a customer's report, an Accountant) simply
   has a taller scroller. That used to be a padding rule with four
   exceptions hanging off it, one per screen that had to opt out. */
body,
body > div { overflow: hidden; }
#contentContainer {
  display: block !important;
  overflow-y: auto;
  overflow-x: hidden;
}
/* The two rules that key off #contentContainer's mode classes live with
   the portal, further down — they are about that screen, not about the
   shell. */

/* ============================================================
   PAGE DESIGN SYSTEM — used by every ng-view template
   ============================================================ */
.rg-page {
  font-family: 'DM Sans', sans-serif;
  background: var(--rg-bg);
  color: var(--rg-text);
  min-height: 100%;
  display: flex;
  flex-direction: column;
}

/* ── Topbar ── */
.rg-topbar {
  /* Surface colour is on ::before — see EDGE PAINT. */
  border-bottom: 1px solid var(--rg-border);
  /* Grows by the top inset when the app runs full-screen from a phone's
     home screen — same treatment .smp-portal-bar already has; this is
     the office side's equivalent and was just missing it. min-height,
     not height, for the same reason. */
  padding: var(--safe-t) calc(20px + var(--safe-r)) 0 calc(20px + var(--safe-l));
  min-height: 58px;
  display: flex;
  align-items: center;
  gap: 14px;
  position: sticky;
  top: 0;
  z-index: 200;
}
/* The page bar on a phone.

   It no longer pads for the cutout: .smp-appbar sits above it and owns
   the inset, which is also what stops iOS's blurred status strip from
   landing on a page title — it lands on the app bar's own black.

   Three things live here and nothing else: where you came from, where
   you are, and what you can do about it. The bar wraps rather than
   compresses, so a search box drops to a full-width second row instead
   of fighting the title for 60px. */
@media (max-width: 991.98px) {
  .rg-topbar {
    padding: 0 calc(12px + var(--safe-r)) 0 calc(12px + var(--safe-l));
    min-height: 50px;
    gap: 6px;
    flex-wrap: wrap;
    align-content: center;
  }
  /* One line, ellipsised. A wrapped RO number pushes the actions off
     the bar and takes the page's height with it. */
  .rg-topbar-title {
    flex: 1;
    min-width: 0;
    font-size: 1.02rem;
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
  }
  /* order + 100% basis is the whole of the second row: no template on
     the phone side has to know it moved. */
  .rg-topbar .rg-search {
    order: 10;
    flex: 1 0 100%;
    max-width: none;
    padding-bottom: 8px;
  }
  /* 16px, not 14. Anything under 16 and iOS zooms the page the moment
     the field takes focus, and leaves it zoomed — the commonest way a
     phone layout "breaks" with nothing actually wrong with it. The
     placeholder keeps the smaller size, where it costs nothing. */
  .rg-topbar .rg-search input { height: 40px; font-size: 16px; }
  .rg-topbar .rg-search input::placeholder { font-size: .875rem; }

  /* Icon-sized targets: a 36px-tall button with a word in it is a
     36px-tall button with a truncated word in it. */
  .rg-topbar .rg-btn-ghost,
  .rg-topbar .rg-btn-primary { height: 40px; padding: 0 12px; }
  .rg-topbar-sub { display: none; }

  /* 20px of margin on each side of a 390px screen is a tenth of the
     phone spent on nothing. The content well keeps its air on a monitor
     and gives it back here, where every column is already too narrow. */
  .rg-content,
  .rg-content-wide,
  .rg-content-side {
    padding: 12px calc(12px + var(--safe-r)) 24px calc(12px + var(--safe-l));
  }
}

/* ── The action the floating button already carries ──
   Four page bars open a new repair order, and every one of those pages
   is a page .rg-fab renders on. Two buttons for one action inside the
   same 60px of screen is one button too many, and the bar's copy is the
   one further from a thumb. It stays at desktop widths, where there is
   no floating button at all. */
@media (max-width: 991.98px) {
  .rg-topbar .rg-newro { display: none; }
}

/* The primary action stays on the bar at every width; everything else
   folds behind the ⋯ (see [rg-overflow]). It is written AFTER the
   cluster in the markup so it stays the rightmost control on the bar —
   the same order as the desktop row, and the corner a thumb reaches
   without moving the hand. */
.rg-topbar-primary { flex: none; }

/* ── The ⋯ and what it opens ──
   The button is drawn by the directive, not by any template, so a page
   opts in with one attribute and gets the same control every other page
   has. Hidden at desktop widths, where the cluster is a row again. */
.rg-ovf-btn {
  display: flex;
  align-items: center;
  justify-content: center;
  flex: none;
  margin-left: auto;
  width: 40px; height: 40px;
  border: 1px solid var(--rg-border);
  border-radius: 9px;
  background: var(--rg-surface);
  color: var(--rg-text);
  font-size: .95rem;
}
.rg-ovf-btn[aria-expanded="true"] { background: var(--rg-bg); }

@media (max-width: 767.98px) {
  /* Closed is the default state, and "closed" is the cluster itself. */
  .rg-topbar-actions[rg-overflow] { display: none; }

  .rg-topbar-actions.rg-ovf-open {
    display: flex;
    flex-direction: column;
    align-items: stretch;
    gap: 2px;
    position: absolute;
    top: calc(100% - 6px);
    right: calc(12px + var(--safe-r));
    z-index: 210;
    min-width: 210px;
    max-width: calc(100vw - 24px);
    padding: 6px;
    background: var(--rg-surface);
    border: 1px solid var(--rg-border);
    border-radius: 12px;
    box-shadow: var(--rg-shadow-md);
  }
  .rg-ovf-open > *,
  .rg-ovf-open .rg-btn-ghost,
  .rg-ovf-open .rg-btn-primary {
    width: 100%;
    height: 44px;
    justify-content: flex-start;
    border: 0;
    background: none;
    box-shadow: none;
    font-size: .92rem;
  }
  .rg-ovf-open .rg-btn-primary { color: var(--rg-accent); }
  .rg-ovf-open .rg-btn-ghost:active,
  .rg-ovf-open .rg-btn-primary:active { background: var(--rg-bg); transform: none; }
  .rg-ovf-open i { width: 18px; text-align: center; }
  /* Bar buttons drop their labels at narrow widths and keep the icon.
     In a menu that is backwards: a list of six anonymous glyphs is not
     a menu. Bootstrap's utility is what hid them, so this is what
     un-hides them. */
  .rg-ovf-open span.d-none { display: inline !important; }
  /* And a whole row that only exists at desktop widths — the staff
     page's "show inactive" was a d-lg utility, which is to say it did
     not exist on a phone. In here it does. */
  .rg-ovf-open > .d-none { display: flex !important; align-items: center; gap: 8px; }
}
.rg-topbar-title {
  font-size: 1.15rem;
  font-weight: 700;
  letter-spacing: -.3px;
  white-space: nowrap;
}
.rg-search { flex: 1; max-width: 400px; position: relative; }
.rg-search input {
  width: 100%;
  height: 36px;
  border: 1px solid var(--rg-border);
  border-radius: 8px;
  background: var(--rg-bg);
  padding: 0 12px 0 36px;
  font-family: inherit;
  font-size: .875rem;
  color: var(--rg-text);
  outline: none;
  transition: border-color .15s, box-shadow .15s;
}
.rg-search input:focus {
  border-color: var(--rg-accent);
  box-shadow: 0 0 0 3px rgba(37,99,235,.12);
  background: #fff;
}
.rg-search-icon {
  position: absolute;
  left: 10px; top: 50%;
  transform: translateY(-50%);
  color: var(--rg-muted);
  font-size: .8rem;
  pointer-events: none;
}
.rg-topbar-actions { margin-left: auto; display: flex; align-items: center; gap: 8px; }

/* ── Buttons ── */
.rg-btn-primary {
  display: inline-flex; align-items: center; gap: 6px;
  background: var(--rg-accent);
  color: #fff;
  border: none;
  border-radius: 8px;
  padding: 0 16px;
  height: 36px;
  font-family: inherit;
  font-size: .85rem;
  font-weight: 600;
  cursor: pointer;
  white-space: nowrap;
  transition: background .15s, box-shadow .15s, transform .1s;
}
.rg-btn-primary:hover:not(:disabled) { background: #1d4ed8; box-shadow: var(--rg-shadow-md); transform: translateY(-1px); }
.rg-btn-primary:active:not(:disabled) { transform: translateY(0); }

/* A disabled button has to look disabled. Save is disabled whenever
   there's nothing to save, and without this it still lifted on hover —
   so "nothing happened" read as a broken button rather than as an
   answer. */
.rg-btn-primary:disabled,
.rg-btn-ghost:disabled {
  opacity: .45;
  cursor: not-allowed;
  box-shadow: none;
  transform: none;
}

.rg-btn-ghost {
  display: inline-flex; align-items: center; gap: 6px;
  background: var(--rg-surface);
  color: var(--rg-text);
  border: 1px solid var(--rg-border);
  border-radius: 8px;
  padding: 0 14px;
  height: 36px;
  font-family: inherit;
  font-size: .85rem;
  font-weight: 600;
  cursor: pointer;
  white-space: nowrap;
  transition: background .15s, border-color .15s;
}
.rg-btn-ghost:hover:not(:disabled) { background: var(--rg-bg); border-color: #cbd5e1; }

.rg-topbar-sub { font-size: .72rem; color: var(--rg-muted); margin-top: 1px; }

/* ── Content well ──
   Centred rather than left-justified: on a wide monitor a left-hugging
   column leaves the page lopsided. The cap is generous so tables still
   get room. */
.rg-content {
  padding: 18px calc(20px + var(--safe-r)) 0 calc(20px + var(--safe-l));
  flex: 1;
  width: 100%;
  max-width: 1500px;
  margin-inline: auto;
}
/* Editing surfaces read better in a narrower measure. */
.rg-content-wide { max-width: 1180px; }

/* The repair order runs full width. It is the one page that is mostly
   a table — nine columns of line items with a conversation beside them
   — and capping it left the columns cramped while the monitor sat
   half empty. Everything else keeps its measure. */
.rg-content-side { max-width: none; }

/* Everything outside the grid lines up with it rather than sitting to
   one side of a page that no longer has margins to centre within. */
.rg-content-side > .rg-alert,
.rg-content-side > .rg-form-actions {
  max-width: none;
  margin-inline: 0;
}

/* The topbar deliberately stays full-bleed — it reads as page chrome,
   like the navbar above it, rather than part of the centred column. */

/* Row-level action column */
.rg-col-action { width: 206px; text-align: right; padding-left: 0 !important; }
.rg-icon-btn {
  width: 30px; height: 30px;
  /* Flex-centred: a bare button leaves the glyph riding on the text
     baseline, which reads as off-centre in the box. */
  display: inline-flex;
  align-items: center;
  justify-content: center;
  border-radius: 7px;
  border: 1px solid transparent;
  background: transparent;
  color: var(--rg-muted);
  font-size: .78rem;
  line-height: 1;
  padding: 0;
  cursor: pointer;
  text-decoration: none;
  transition: background .12s, color .12s, border-color .12s;
}
.rg-icon-btn i { display: block; }
.rg-icon-btn:hover { background: var(--rg-surface); border-color: var(--rg-border); color: var(--rg-accent); }

/* A small pencil beside a name in a table row — a row-scale action, not
   a page-scale one, so it earns a fraction of .rg-icon-btn's footprint
   rather than crowding the line it sits on. */
.rg-inline-edit {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 20px;
  height: 20px;
  margin-left: 4px;
  vertical-align: middle;
  border: none;
  border-radius: 5px;
  background: transparent;
  color: var(--rg-border);
  font-size: .68rem;
  padding: 0;
  cursor: pointer;
  flex: none;
}
.rg-inline-edit:hover { background: var(--rg-bg); color: var(--rg-accent); }

/* ── Line items ── */
.rg-col-type { width: 132px; }
.rg-col-num  { width: 120px; }
.rg-col-cost { width: 104px; }
.rg-col-qty  { width: 92px; }
.rg-col-grip { width: 54px; padding-right: 0 !important; }

/* Editable grid cells: inputs that read as table text until focused. */
.rg-line-table { border-bottom: 1px solid var(--rg-border); }
.rg-line-table td { padding: 6px 10px; }
.rg-line-table th { padding: 10px; }
.rg-cell-input {
  min-height: 32px;
  padding: 5px 8px;
  border-color: transparent;
  font-size: .85rem;
  transition: background-color .15s, border-color .15s;
}
/* .rg-input (the base field style) sets its own opaque white
   background at equal specificity; .rg-input.rg-cell-input beats it
   regardless of source order so rest/hover/focus stay in control here. */
.rg-input.rg-cell-input { background: transparent; }
/* A translucent white wash lightens whatever's underneath rather than
   replacing it — a plain white row reads barely brighter, a selected
   (blue-tinted) row reads as a lighter blue, instead of one hover
   colour flattening every row state to the same shade. */
.rg-input.rg-cell-input:hover  { border-color: var(--rg-border); background: rgba(255,255,255,.5); }
.rg-input.rg-cell-input:focus  { border-color: var(--rg-accent); background: #fff; }
select.rg-cell-input  { cursor: pointer; }
.rg-line-busy { opacity: .55; }

/* Compact mode: no breathing room around each cell, and fields with
   square, touching edges so the grid reads as one continuous sheet
   rather than a stack of separate rounded boxes. */
.rg-line-table.rg-compact td { padding: 0; }
.rg-line-table.rg-compact .rg-cell-input {
  border-radius: 0;
  border-color: transparent;
}
.rg-line-table.rg-compact .rg-cell-input:hover,
.rg-line-table.rg-compact .rg-cell-input:focus { position: relative; z-index: 1; }
/* The rest-state rule above resets border-color: transparent at the
   same specificity as .rg-input.rg-cell-input:focus and comes later in
   the file, so it wins the tie and swallows the focus border — this
   restates it so a focused field is still outlined in compact mode. */
.rg-line-table.rg-compact .rg-input.rg-cell-input:focus { border-color: var(--rg-accent); }

/* Drag handle + keyboard/touch fallback */
.rg-col-grip { white-space: nowrap; }
.rg-drag-handle {
  display: inline-block;
  padding: 4px 5px;
  color: var(--rg-muted);
  cursor: grab;
  opacity: .45;
  transition: opacity .12s, color .12s;
}
tr:hover .rg-drag-handle { opacity: 1; color: var(--rg-accent); }
.rg-drag-handle:active { cursor: grabbing; }
.rg-move-btns { display: inline-flex; flex-direction: column; vertical-align: middle; gap: 1px; }
.rg-move-btn {
  border: none;
  background: transparent;
  color: var(--rg-muted);
  font-size: .55rem;
  line-height: 1;
  padding: 2px 3px;
  cursor: pointer;
  border-radius: 3px;
  opacity: .5;
}
tr:hover .rg-move-btn { opacity: 1; }
.rg-move-btn:hover:not(:disabled) { color: var(--rg-accent); background: var(--rg-bg); }
.rg-move-btn:disabled { opacity: .2; cursor: default; }

.rg-dragging { opacity: .4; }
.rg-drop-above td { box-shadow: inset 0 2px 0 var(--rg-accent); }
.rg-drop-below td { box-shadow: inset 0 -2px 0 var(--rg-accent); }
/* Red at rest, not only on hover — deleting is the one thing on the
   row that can't be taken back, and it should be recognisable before
   the pointer is on it. */
.rg-icon-btn.rg-icon-danger { color: var(--rg-danger); }
.rg-icon-btn.rg-icon-danger:hover:not(:disabled) {
  color: var(--rg-danger);
  border-color: rgba(220,38,38,.35);
  background: rgba(220,38,38,.08);
}
.rg-table tfoot td {
  padding: 8px 16px;
  border: none;
  font-size: .85rem;
}
.rg-table tfoot tr:first-child td { padding-top: 14px; }
.rg-label-note { font-weight: 400; text-transform: none; letter-spacing: 0; }

/* Type colour, carried by the row */
.rg-line-row.rg-t-labor  { --line-accent: var(--rg-accent); }
.rg-line-row.rg-t-part   { --line-accent: #0ea5e9; }
.rg-line-row.rg-t-sublet { --line-accent: var(--rg-warn); }
.rg-line-row.rg-t-fee    { --line-accent: #8b5cf6; }
.rg-line-row td:first-child { box-shadow: inset 3px 0 0 var(--line-accent); }
.rg-line-row:hover td { background: #fafbfd; }

/* ── Sub-estimate tabs ── */
.rg-est-head { flex-wrap: wrap; row-gap: 8px; }
.rg-est-tabs {
  display: flex;
  align-items: center;
  gap: 4px;
  overflow-x: auto;
}
.rg-est-tabs-drag { display: flex; align-items: center; gap: 4px; }
.rg-est-tab {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  white-space: nowrap;
  border: 1px solid transparent;
  background: transparent;
  color: var(--rg-muted);
  font-size: .84rem;
  font-weight: 600;
  padding: 7px 12px;
  border-radius: 8px;
  cursor: pointer;
  user-select: none;
}
.rg-est-tab:hover { background: var(--rg-bg); color: var(--rg-text); }
.rg-est-tab.active {
  background: var(--rg-bg);
  border-color: var(--rg-border);
  color: var(--rg-accent);
}
.rg-est-tab.rg-dragging { opacity: .4; }
.rg-est-tab-close {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 16px; height: 16px;
  border-radius: 50%;
  color: var(--rg-muted);
  font-size: .9rem;
  line-height: 1;
  opacity: .6;
}
.rg-est-tab-close:hover { opacity: 1; background: rgba(220,38,38,.12); color: var(--rg-danger); }
.rg-est-tab-add {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 30px; height: 30px;
  padding: 0;
  border: 1px dashed var(--rg-border);
  background: transparent;
  border-radius: 8px;
  color: var(--rg-muted);
  cursor: pointer;
  flex-shrink: 0;
  align-self: center;
}
.rg-est-tab-add i {
  display: block;
  line-height: 1;
  font-size: .8rem;
}
.rg-est-tab-add:hover:not(:disabled) { color: var(--rg-accent); border-color: var(--rg-accent); }
.rg-est-tab-add:disabled { opacity: .4; cursor: default; }

/* ── Line selection + copy/move toolbar ── */
/* The whole cell is the click target — the checkbox alone is too small
   to hit reliably, so it's just the visual state (pointer-events: none)
   and the <td> around it carries the click and the cursor. */
.rg-col-select,
.rg-col-selectpad {
  width: 42px;
  padding: 0 !important;
  text-align: center;
  vertical-align: middle;
}
.rg-line-row:hover .rg-col-select { background: var(--rg-bg); }
/* The header cell is select-all. */
th.rg-col-select { cursor: pointer; }
th.rg-col-select:hover { background: var(--rg-bg); }

/* The row itself selects, so it reads as clickable throughout. The
   cells are mostly inputs, which keep their own text cursor — this is
   for the gaps between them. */
.rg-line-row { cursor: pointer; }
/* display:block + auto margins rather than text-align, which leaves the
   box off-centre — browsers give a checkbox an asymmetric default
   margin of its own. */
.rg-col-select input[type="checkbox"] {
  display: block;
  width: 18px;
  height: 18px;
  margin: 0 auto;
  pointer-events: none;
  accent-color: var(--rg-accent);
}
/* Clicking a row selects it, so the state has to be obvious at a
   glance — and it wins over the child-row tint further down. */
.rg-line-row.rg-line-selected > td { background: rgba(37,99,235,.2); }
.rg-line-row.rg-line-selected:hover > td { background: rgba(37,99,235,.25); }

.rg-selection-toolbar {
  display: flex;
  align-items: center;
  gap: 10px;
  padding: 8px 14px;
  margin: 10px 0;
  background: var(--rg-bg);
  border: 1px solid var(--rg-border);
  border-radius: var(--rg-radius);
  font-size: .84rem;
  color: var(--rg-muted);
}
.rg-selection-toolbar > span:first-child { font-weight: 600; color: var(--rg-text); }
.rg-selection-toolbar .rg-btn-ghost { margin-left: auto; }
.rg-selection-toolbar .rg-dropdown + .rg-dropdown { margin-left: 0; }

.rg-dropdown { position: relative; }
.rg-dropdown-toggle { display: inline-flex; align-items: center; gap: 6px; }
.rg-dropdown-menu {
  position: absolute;
  top: calc(100% + 4px);
  left: 0;
  min-width: 160px;
  background: var(--rg-surface);
  border: 1px solid var(--rg-border);
  border-radius: 8px;
  box-shadow: var(--rg-shadow-md);
  padding: 4px;
  z-index: 20;
}
/* The toolbar sits under the list, so its menus open upward rather
   than off the bottom of the card. */
.rg-dropdown-menu.rg-dropdown-up { top: auto; bottom: calc(100% + 4px); }
.rg-dropdown-menu button {
  display: block;
  width: 100%;
  text-align: left;
  padding: 7px 10px;
  border: none;
  background: transparent;
  border-radius: 6px;
  font-size: .84rem;
  color: var(--rg-text);
  cursor: pointer;
}
.rg-dropdown-menu button:hover { background: var(--rg-bg); color: var(--rg-accent); }

/* Count chip beside a card title */
.rg-count-chip {
  font-family: 'DM Mono', monospace;
  font-size: .72rem;
  background: var(--rg-bg);
  border: 1px solid var(--rg-border);
  border-radius: 20px;
  padding: 1px 8px;
  color: var(--rg-muted);
}

/* "This order overrides pricing" marker on the pricing button */
.rg-dot-flag {
  width: 6px; height: 6px;
  border-radius: 50%;
  background: var(--rg-warn);
  display: inline-block;
  margin-left: 2px;
}

/* ── Quick add bar ── */
.rg-quick-add {
  border-top: 1px solid var(--rg-border);
  background: var(--rg-bg);
  padding: 12px 16px 14px;
}

.rg-col-item { width: 150px; }

/* Who is doing the line, beside what it is. Narrow: it holds a name,
   and the numbers to its right are what the column widths are really
   competing for. */
.rg-col-tech { width: 130px; }
.rg-col-tech .rg-cell-input { font-size: .8rem; }

/* ── Catalog: a part's own columns ── */
.rg-col-star   { width: 44px; padding-right: 0 !important; }
.rg-col-toggle { width: 52px; padding-right: 0 !important; }
.rg-col-vendor { width: 150px; }
.rg-col-year   { width: 92px; }
.rg-col-make   { width: 150px; }

/* The number that lands on the invoice, marked in the row and again in
   the list, so the same star means the same thing in both places. */
.rg-star-btn {
  background: none; border: 0; padding: 4px 6px; cursor: pointer;
  color: var(--rg-muted); font-size: .9rem; line-height: 1;
}
.rg-star-btn:hover { color: var(--rg-warn); }
.rg-num-preferred > td .rg-star-btn { color: var(--rg-warn); }
.rg-num-preferred > td { background: rgba(217,119,6,.05); }

.rg-sub-line {
  margin-top: 3px;
  font-size: .74rem;
  font-weight: 500;
  color: var(--rg-muted);
  display: flex; align-items: center; flex-wrap: wrap; gap: 4px;
}
.rg-num-chip {
  border: 1px solid var(--rg-border);
  border-radius: 4px;
  padding: 0 5px;
  font-size: .7rem;
  background: var(--rg-bg);
}
.rg-num-chip-star { border-color: var(--rg-warn); color: var(--rg-warn); }

/* A switched-off rule is still a rule — dimmed, not hidden, so nobody
   writes a second copy of one that is sitting right there turned off. */
.rg-row-muted > td { opacity: .55; }
.rg-on { color: var(--rg-ok, #16a34a); }

.rg-textarea { min-height: 150px; resize: vertical; font-family: inherit; line-height: 1.5; }

/* ── Picking who a test goes to ── */
.rg-picked {
  display: flex; align-items: center; gap: 8px;
  border: 1px solid var(--rg-border); border-radius: 8px;
  padding: 8px 10px; background: var(--rg-bg);
  font-size: .86rem;
}
.rg-picked .rg-mono { color: var(--rg-muted); font-size: .8rem; }

.rg-picklist {
  border: 1px solid var(--rg-border); border-top: 0;
  border-radius: 0 0 8px 8px; overflow: hidden; background: var(--rg-surface);
}
.rg-picklist button {
  display: flex; align-items: center; gap: 8px; width: 100%;
  background: none; border: 0; border-bottom: 1px solid var(--rg-border);
  padding: 8px 10px; text-align: left; font-size: .86rem; cursor: pointer;
}
.rg-picklist button:last-child { border-bottom: 0; }
.rg-picklist button:hover { background: var(--rg-bg); }
.rg-picklist .rg-mono { color: var(--rg-muted); font-size: .8rem; }

/* ── The ticket, and the conversation beside it ──
   The ticket runs the full width of the page: it is nine columns of
   line items, and every one of them wants the room. The conversation
   keeps a fixed 360px beside it, and the work takes the rest.
   Narrowing the work to make room for the chat would be the wrong
   trade — the line items are the page.

   Three children in DOM order: the top of the ticket, the conversation,
   then the rest of it. Stacked, that reads exactly as it should — the
   messages land back between the document and the line items. Wide, the
   placement below lifts the middle child into its own column, which is
   why the split exists at all.

   minmax(0, 1fr) rather than 1fr: a grid column defaults to min-content
   width, so without the zero floor the wide line-items table refuses to
   shrink and pushes the sidebar off the screen. */
.rg-layout {
  display: grid;
  grid-template-columns: minmax(0, 1fr) 360px;
  gap: 8px;
  align-items: start;
}
.rg-main { min-width: 0; }
.rg-main-top    { grid-column: 1; grid-row: 1; }
.rg-main-bottom { grid-column: 1; grid-row: 2; }
.rg-side        { grid-column: 2; grid-row: 1 / span 2; }

/* Sticky, below the topbar rather than under it — the topbar is sticky
   too and sits above everything, so a panel pinned to 0 slides behind
   it. And short enough to clear the save bar at the bottom: the two
   pieces of fixed chrome are what the panel has to live between. */
.rg-side {
  position: sticky;
  top: calc(58px + 18px);
  height: calc(100vh - 58px - 76px - 74px);
  z-index: 1;
}
.rg-side .rg-card {
  margin-bottom: 0 !important;
  display: flex;
  flex-direction: column;
  height: 100%;
}
/* min-height: 0 on every flex parent of the scroller, or the thread
   grows the card instead of scrolling inside it. */
.rg-side .rg-card-body {
  display: flex;
  flex-direction: column;
  min-height: 0;
  flex: 1;
}
.rg-side .rg-thread {
  flex: 1;
  max-height: none;
  min-height: 0;
}
/* Below this a 360px column beside the work is neither one thing nor the
   other, so the conversation goes back to being a card between the
   document and the line items — which is where the DOM already has it. */
@media (max-width: 1199px) {
  /* gap: 0, not 16. Stacked, every card already carries its own mb-3,
     so a grid gap on top of that gave the conversation 32px of air
     while its neighbours had 16 — the card looked like it belonged to
     a different page. */
  .rg-layout {
    grid-template-columns: 1fr;
    gap: 0;
  }
  .rg-main-top, .rg-main-bottom, .rg-side {
    grid-column: 1;
    grid-row: auto;
  }
  .rg-side {
    position: static;
    height: auto;
  }
  .rg-side .rg-card { margin-bottom: 16px !important; height: auto; }
  .rg-side .rg-thread { max-height: 340px; }
}

/* ── The conversation as a bottom sheet (phones) ──
   Same panel as the sidebar, mounted at the bottom of the screen and
   dragged away by its grip. The sidebar rules above are all scoped to
   .rg-side, so it arrives here already knowing how to be a full-height
   column with a scrolling thread. */
.rg-sheet-back {
  position: fixed;
  inset: 0;
  z-index: 1250;
  background: rgba(16,24,40,.5);
  animation: rg-dialog-fade .2s ease-out;
}

@media (max-width: 1199px) {
  /* Out of the page flow entirely until asked for: on a phone the card
     used to sit in the middle of the ticket, between the document and
     the line items, which is a long way from a thumb. */
  .rg-side { display: none; }

  .rg-side.rg-side-sheet {
    display: block;
    position: fixed;
    /* top: auto matters. The sidebar rule sets a top offset, and a fixed
       element with both top and height ignores bottom — which pinned the
       sheet under the topbar instead of to the floor. */
    top: auto;
    left: 0; right: 0; bottom: 0;
    z-index: 1300;
    height: 82vh;
    /* No bottom padding: the sheet runs into the skirt below it, and a
       gap there is the seam that made it look like a floating card
       rather than something rising from the edge. The safe-area inset
       moves inside, onto the compose row, so the home indicator still
       has its space. */
    /* No side padding on the sheet itself. The card inside already has
       its own, and two sets of it cost 26px of a 390px screen — a chat
       bubble is narrow enough without paying for the same margin
       twice. */
    padding: 6px 0 0;
    background: var(--rg-surface);
    /* Rounded at the top only — the bottom has nothing to round against. */
    border-radius: 20px 20px 0 0;
    border-bottom: 0;
    box-shadow: 0 -12px 40px rgba(16,24,40,.28);
    animation: rg-sheet-up .34s cubic-bezier(.22, 1, .36, 1);

    /* The sheet owns vertical gestures. Without this the browser decides
       the drag is a page scroll, takes the pointer, and fires
       pointercancel — which reads as the sheet letting go of your finger
       the moment you start pulling. */
    touch-action: none;
    will-change: transform;
    /* A drag shouldn't select the text it starts on. */
    user-select: none;
    -webkit-user-select: none;
  }

  /* Dragged upward the sheet resists, but it still moves — and moving a
     panel off the bottom edge shows the page underneath it, which reads
     as the sheet coming loose from the screen. This skirt is the same
     surface, hanging below the fold, so an upward pull reveals more of
     the sheet rather than a gap under it. */
  .rg-side.rg-side-sheet::after {
    content: '';
    position: absolute;
    top: 100%;
    left: 0;
    right: 0;
    height: 50vh;
    background: var(--rg-surface);
  }

  /* Except where a vertical gesture already means something. The thread
     scrolls; the compose box behaves like a text box. */
  .rg-side.rg-side-sheet .rg-thread { touch-action: pan-y; }
  .rg-side.rg-side-sheet textarea,
  .rg-side.rg-side-sheet input,
  .rg-side.rg-side-sheet select {
    touch-action: auto;
    user-select: text;
    -webkit-user-select: text;
  }
  .rg-side.rg-side-sheet .rg-card {
    height: calc(100% - 14px);
    margin-bottom: 0 !important;
    border: 0;
    border-radius: 0;
    box-shadow: none;
    background: transparent;
  }
  /* Where the safe-area inset went. The rule above the compose box goes
     too: in the sheet it lands a few pixels from the bottom edge, which
     reads as a stray border on the sheet rather than a divider inside
     it. The gap alone separates them. */
  .rg-side.rg-side-sheet .rg-chat-compose {
    /* Real padding plus the inset, not the inset alone: --safe-b is 0 on
       every device without a home indicator, which left the Send button
       sitting on the bottom edge of the screen. */
    padding-bottom: calc(14px + var(--safe-b));
    padding-top: 0;
    border-top: 0;
    margin-top: 8px;
  }

  /* Nothing in the sheet draws its own bottom edge — the sheet has no
     bottom, it runs off the screen into the skirt. */
  .rg-side.rg-side-sheet .rg-card,
  .rg-side.rg-side-sheet .rg-card-body { border-bottom: 0; }
  /* The body spans the sheet, with just enough inset to keep text off
     the edge of the screen. */
  .rg-side.rg-side-sheet .rg-card-body {
    padding: 12px 12px 0;
  }
  .rg-side.rg-side-sheet .rg-chat-head { padding-inline: 12px; }
  .rg-side.rg-side-sheet .rg-thread { max-height: none; }
}

@keyframes rg-sheet-up {
  from { transform: translateY(100%); }
  to   { transform: none; }
}

/* The handle you drag. Wide enough to find without looking. */
.rg-sheet-grip {
  width: 42px;
  height: 4px;
  margin: 4px auto 8px;
  border-radius: 999px;
  background: var(--rg-border);
}

/* Unread count on the Chat button, where the badge has to survive a
   button that is mostly icon. */
.rg-chat-btn { position: relative; }
.rg-chat-dot {
  position: absolute;
  top: 4px;
  right: 50%;
  transform: translateX(22px);
  min-width: 16px;
  padding: 0 4px;
  border-radius: 999px;
  background: var(--rg-danger);
  color: #fff;
  font-size: .62rem;
  font-weight: 700;
  line-height: 16px;
}

/* ── The conversation's header ──
   Two rows, on a grid rather than a wrapping flex row: the name and the
   controls share the first, the address gets the second to itself. Built
   for the narrowest place it appears — a 360px sidebar — and the same
   shape everywhere else, because a header that rearranges itself between
   breakpoints is one more thing to check. */
.rg-chat-head {
  display: grid;
  grid-template-columns: minmax(0, 1fr) auto;
  grid-template-areas:
    "name tools"
    "to   to";
  align-items: center;
  column-gap: 8px;
  row-gap: 2px;
  padding: 11px 14px;
}

.rg-chat-head-name {
  grid-area: name;
  display: flex;
  align-items: center;
  gap: 8px;
  min-width: 0;
  font-size: .95rem;
  font-weight: 700;
}
.rg-chat-head-name > span:first-of-type {
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.rg-chat-head-name i { color: var(--rg-muted); font-size: .9rem; }
.rg-chat-head-name .rg-count-chip { flex: none; }

.rg-chat-head-tools {
  grid-area: tools;
  display: flex;
  align-items: center;
  gap: 6px;
}

/* The whole reason for the second row: a long address truncates itself
   instead of truncating the title. */
.rg-chat-head-to {
  grid-area: to;
  font-size: .72rem;
  font-weight: 500;
  color: var(--rg-muted);
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}

/* ── Conversation with a customer ──
   Theirs on the left, ours on the right: every messaging app has taught
   everyone to read a thread that way, and this is a thread. */
.rg-thread {
  max-height: 340px;
  overflow-y: auto;
  padding: 4px 2px 8px;
  display: flex;
  flex-direction: column;
  gap: 10px;
}
.rg-bubble-row { display: flex; }
.rg-bubble-row.rg-mine   { justify-content: flex-end; }
.rg-bubble-row.rg-theirs { justify-content: flex-start; }

.rg-bubble {
  max-width: min(88%, 620px);
  border: 1px solid var(--rg-border);
  border-radius: 12px;
  padding: 8px 12px 7px;
  background: var(--rg-surface);
}
.rg-mine .rg-bubble {
  background: #eff6ff;
  border-color: #bfdbfe;
}

.rg-bubble-who {
  font-size: .7rem; font-weight: 700; text-transform: uppercase;
  letter-spacing: .04em; color: var(--rg-muted); margin-bottom: 3px;
}
.rg-bubble-when { font-weight: 500; text-transform: none; letter-spacing: 0; margin-left: 6px; }

/* pre-wrap, so the line breaks somebody typed survive. */
.rg-bubble-body { white-space: pre-wrap; font-size: .9rem; line-height: 1.5; }

.rg-bubble-state { font-size: .68rem; color: var(--rg-muted); margin-top: 4px; text-align: right; }
.rg-bubble-state .rg-failed { color: var(--rg-danger, #dc2626); font-weight: 600; }

/* The box gets the whole width; the controls go under it. Beside a
   360px column a text box sharing its row with a Send button is two
   words wide, and on a phone it is worse. */
.rg-chat-compose {
  display: flex;
  flex-direction: column;
  gap: 8px;
  margin-top: 12px;
  padding-top: 12px;
  border-top: 1px solid var(--rg-border);
}
.rg-chat-compose textarea {
  resize: vertical;
  font-family: inherit;
  min-height: 76px;
}

.rg-chat-tools { display: flex; align-items: center; gap: 10px; }

/* Quiet: checking for a reply is a thing you do when impatient, not the
   point of the panel. */
.rg-chat-tool {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  background: none;
  border: 0;
  padding: 4px 2px;
  color: var(--rg-muted);
  font-size: .78rem;
  font-weight: 600;
  cursor: pointer;
}
.rg-chat-tool:hover:not(:disabled) { color: var(--rg-accent); }
.rg-chat-tool:disabled { opacity: .6; cursor: default; }

/* Pushes Send to the right, and is the first thing dropped when there
   is no room for it. */
.rg-chat-note {
  margin-left: auto;
  font-size: .72rem;
  color: var(--rg-muted);
}
@media (max-width: 400px) {
  .rg-chat-note { display: none; }
  .rg-chat-send { margin-left: auto; }
}

.rg-chat-send { padding-inline: 18px; }

/* ── The two channels ──
   Email and Texting, above everything else in the panel: they decide
   which conversation is on screen, so they sit where a tab strip is
   expected rather than beside the compose box. Borrowed wholesale from
   the filter tabs used on the list pages — a second tab style would be a
   second thing to keep looking right. */
.rg-chat-tabs {
  gap: 2px;
  margin-bottom: 12px;
  border-bottom: 1px solid var(--rg-border);
  padding-bottom: 8px;
}
.rg-chat-tabs .rg-filter-tab {
  display: inline-flex;
  align-items: center;
  gap: 7px;
}
.rg-chat-tabs .rg-count-chip { flex: none; }

/* The tab says the door is shut without being opened. Muted, not red:
   a customer who hasn't agreed to texts is the normal state of a
   customer, not a fault. */
.rg-tab-lock { font-size: .72rem; opacity: .7; }

/* ── Not opted in ──
   Where the compose box would be, and it has to read as an explanation
   rather than an error: nothing has gone wrong, the customer simply
   hasn't said yes. Same footprint as the box it replaces, so switching
   tabs doesn't jump the panel. */
.rg-consent {
  margin-top: 12px;
  padding: 12px 14px;
  border: 1px solid var(--rg-border);
  border-radius: 10px;
  background: var(--rg-bg);
}
.rg-consent-head {
  display: flex;
  align-items: center;
  gap: 8px;
  font-size: .85rem;
  font-weight: 700;
}
.rg-consent-head i { color: var(--rg-muted); font-size: .8rem; }
.rg-consent-note {
  margin-top: 6px;
  font-size: .8rem;
  line-height: 1.5;
  color: var(--rg-muted);
}
.rg-consent-ask { margin-top: 10px; }

/* Only shown near the limit, so it is a warning and not a readout. */
.rg-chat-count {
  font-size: .72rem;
  font-weight: 700;
  color: var(--rg-muted);
  font-variant-numeric: tabular-nums;
}
.rg-chat-count.rg-over { color: var(--rg-danger, #dc2626); }

/* An unread reply is the one chip on the page that should catch an eye. */
.rg-chip-alert {
  background: var(--rg-danger, #dc2626);
  color: #fff;
  border-color: transparent;
}

.rg-empty-tight { padding: 22px 12px; }

/* A hint that is a warning: about to touch a real customer. */
.rg-hint-warn { color: var(--rg-warn); font-weight: 500; }

/* ── Filter tabs ── */
.rg-filter-tabs { display: flex; flex-wrap: wrap; gap: 4px; }
.rg-filter-tab {
  background: none;
  border: 1px solid transparent;
  border-radius: 6px;
  padding: 5px 12px;
  font-size: .8rem;
  font-weight: 600;
  color: var(--rg-muted);
  cursor: pointer;
}
.rg-filter-tab:hover { color: var(--rg-text); background: var(--rg-bg); }
.rg-filter-tab.active {
  color: var(--rg-accent);
  border-color: var(--rg-border);
  background: var(--rg-surface);
}

/* ── Quick-add chips ── */
.rg-chips { display: flex; align-items: center; flex-wrap: wrap; gap: 6px; margin-bottom: 10px; }
.rg-chips-label {
  font-size: .68rem; font-weight: 700; letter-spacing: .1em;
  text-transform: uppercase; color: var(--rg-muted); margin-right: 2px;
}
.rg-chip {
  display: inline-flex; align-items: center; gap: 6px;
  background: var(--rg-surface);
  border: 1px solid var(--rg-border);
  border-radius: 20px;
  padding: 5px 13px;
  font-family: inherit;
  font-size: .8rem;
  font-weight: 500;
  color: var(--rg-text);
  cursor: pointer;
  transition: border-color .12s, color .12s, background .12s;
  max-width: 320px;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}
.rg-chip i { font-size: .68rem; color: var(--rg-accent); }
.rg-chip:hover { border-color: var(--rg-accent); color: var(--rg-accent); background: rgba(37,99,235,.05); }

/* ── Catalog typeahead ── */
.rg-typeahead { position: relative; }
.rg-typeahead-menu {
  position: absolute;
  z-index: 400;
  top: calc(100% + 4px);
  left: 0; right: 0;
  background: var(--rg-surface);
  border: 1px solid var(--rg-border);
  border-radius: var(--rg-radius);
  box-shadow: var(--rg-shadow-md);
  padding: 4px;
  max-height: 280px;
  overflow-y: auto;
}
.rg-typeahead-menu button {
  display: flex;
  align-items: center;
  gap: 9px;
  width: 100%;
  text-align: left;
  background: transparent;
  border: none;
  border-radius: 6px;
  padding: 7px 9px;
  font-family: inherit;
  font-size: .85rem;
  color: var(--rg-text);
  cursor: pointer;
}
.rg-typeahead-menu button:hover,
.rg-typeahead-menu button.rg-ta-active { background: var(--rg-bg); }
.rg-chip-type { flex-shrink: 0; }
.rg-ta-desc  { flex: 1; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.rg-ta-num   { font-size: .74rem; color: var(--rg-muted); flex-shrink: 0; }
.rg-ta-price { font-size: .8rem; color: var(--rg-muted); flex-shrink: 0; }
.rg-ta-on-order {
  display: inline-flex;
  align-items: center;
  gap: 4px;
  flex-shrink: 0;
  font-size: .68rem;
  font-weight: 600;
  color: var(--rg-success);
  background: rgba(22,163,74,.10);
  border-radius: 20px;
  padding: 2px 8px;
}
/* ── The row's controls ──
   Fixed grid slots, one per possible action, so a button that isn't
   rendering on a given row (a child has no sub-line button, an unnamed
   parent has no bookmark) leaves its column empty rather than letting
   its neighbours slide into the gap — every action lands in the same
   place on every row. Group and ungroup share a slot: they're mutually
   exclusive per row, so only one is ever in it at a time. */
.rg-row-actions {
  display: grid;
  /* A 6th, unlabelled column is the wider gap before delete — its
     width sits between the two grid gaps instead of a button margin,
     which used to widen the column it was applied to and knock delete
     out of alignment with the header cell above it. */
  grid-template-columns: repeat(4, 30px) 10px 30px 10px;
  align-items: center;
  justify-content: flex-end;
  gap: 6px;
}
.rg-row-actions .rg-act-note { grid-column: 1; }
.rg-row-actions .rg-act-group { grid-column: 2; }
.rg-row-actions .rg-act-sub { grid-column: 3; }
.rg-row-actions .rg-act-bookmark { grid-column: 4; }
.rg-row-actions .rg-act-delete { grid-column: 6; }

/* Delete sits apart from the rest — a wider gap and a hairline, so it
   isn't in the same sweep as the reversible ones. Drawn as a
   pseudo-element in the spacer column so it doesn't touch the
   button's own box (padding it would knock the glyph off centre). */
.rg-row-actions .rg-icon-last { position: relative; }
.rg-row-actions .rg-icon-last::before {
  content: '';
  position: absolute;
  left: -14px; top: 5px; bottom: 5px;
  border-left: 1px solid var(--rg-border);
}

/* An icon button that is showing state rather than offering an action —
   this line already has a note. */
.rg-icon-btn.rg-icon-on {
  color: var(--rg-accent);
  border-color: rgba(37,99,235,.35);
  background: rgba(37,99,235,.08);
}

/* Room to actually write in, unlike the one-line prompt. */
.rg-dialog-textarea { resize: vertical; min-height: 84px; font-family: inherit; }

/* ── rg-tip ──
   Appended to <body> and positioned fixed by the directive, so the
   scrolling table can't clip it. Above the highest fixed chrome, since
   a control near the top bar would otherwise be explained by a tooltip
   hidden behind it. */
.rg-tip {
  position: fixed;
  z-index: 3000;
  max-width: 280px;
  padding: 6px 10px;
  border-radius: 7px;
  background: #111;
  color: #f3f4f6;
  font-size: .76rem;
  font-weight: 500;
  line-height: 1.35;
  white-space: pre-wrap;
  box-shadow: 0 6px 18px rgba(0,0,0,.22);
  pointer-events: none;
  animation: rg-tip-in .1s ease-out;
}
@keyframes rg-tip-in { from { opacity: 0; } to { opacity: 1; } }

/* Currency inputs keep the symbol out of the value */
.rg-input-money { position: relative; display: block; }
.rg-money-sym {
  position: absolute;
  left: 9px; top: 50%;
  transform: translateY(-50%);
  font-family: 'DM Mono', monospace;
  font-size: .8rem;
  color: var(--rg-muted);
  pointer-events: none;
}
.rg-input-money .rg-input { padding-left: 20px; }

/* ── Money fields ──
   No spinner arrows. Stepping a price a cent at a time is not how
   anyone prices a job, and in the narrow line-item cells the arrows
   sat on top of the digits. The pair of decimals they always show is
   the rg-money directive's doing. */
.rg-money::-webkit-outer-spin-button,
.rg-money::-webkit-inner-spin-button {
  -webkit-appearance: none;
  appearance: none;
  margin: 0;
}
.rg-money {
  -moz-appearance: textfield;   /* Firefox */
  appearance: textfield;
}

/* ── Totals block ── */
.rg-totals {
  display: flex;
  justify-content: flex-end;
  padding: 14px 16px 16px;
  border-top: 1px solid var(--rg-border);
}
.rg-totals-box { width: 320px; max-width: 100%; }
.rg-sum-row {
  display: flex;
  justify-content: space-between;
  gap: 12px;
  padding: 5px 0;
  font-size: .86rem;
  color: var(--rg-muted);
}
.rg-sum-row em { font-style: normal; font-size: .76rem; opacity: .75; }
.rg-sum-row .rg-mono { color: var(--rg-text); }
.rg-sum-total {
  display: flex;
  justify-content: space-between;
  gap: 12px;
  border-top: 2px solid var(--rg-text);
  margin-top: 7px;
  padding-top: 10px;
  font-size: 1.05rem;
  font-weight: 700;
}

/* ============================================================
   FLOOR PLAN
   A fixed 32x18 grid held at 16:9, so the shop's arrangement scales
   with the window instead of reflowing.
   ============================================================ */
.smp-floor {
  position: relative;
  display: grid;
  grid-template-columns: repeat(32, 1fr);
  grid-template-rows: repeat(18, 1fr);
  gap: 2px;
  aspect-ratio: 16 / 9;
  width: 100%;
  padding: 10px;
  background:
    linear-gradient(rgba(0,0,0,.035) 1px, transparent 1px) 0 0 / 100% calc(100% / 18),
    linear-gradient(90deg, rgba(0,0,0,.035) 1px, transparent 1px) 0 0 / calc(100% / 32) 100%,
    var(--rg-surface);
  border: 1px solid var(--rg-border);
  border-radius: var(--rg-radius);
  box-shadow: var(--rg-shadow);
}
.smp-floor-edit { background-color: #fbfcfe; border-style: dashed; }

.smp-tile {
  position: relative;
  display: flex;
  flex-direction: column;
  gap: 1px;
  overflow: hidden;
  min-width: 0;
  padding: 6px 8px;
  border: 1px solid var(--rg-border);
  border-left: 3px solid var(--rg-border);
  border-radius: 7px;
  background: var(--rg-surface);
  box-shadow: var(--rg-shadow);
}
.smp-floor-edit .smp-tile { cursor: grab; }
.smp-floor-edit .smp-tile:active { cursor: grabbing; }
.smp-tile-dragging { opacity: .35; }

/* Status colours, matched to the pills elsewhere */
.smp-open     { border-left-color: var(--rg-success); }
.smp-occupied { border-left-color: var(--rg-accent); }
.smp-blocked  { border-left-color: var(--rg-danger);  background: rgba(220,38,38,.04); }
.smp-inactive { border-left-color: #cbd5e1; background: var(--rg-bg); }

/* Fixtures read as building, not equipment: no border accent, hatched */
.smp-fixture {
  border: 1px solid #cbd5e1;
  border-radius: 4px;
  background: repeating-linear-gradient(45deg, #e2e8f0, #e2e8f0 4px, #eef2f7 4px, #eef2f7 8px);
  align-items: center;
  justify-content: center;
  text-align: center;
  color: var(--rg-muted);
  box-shadow: none;
}
.smp-fixture .smp-tile-name { font-size: .66rem; letter-spacing: .04em; text-transform: uppercase; }

.smp-tile-top { display: flex; align-items: center; gap: 6px; min-width: 0; }
.smp-dot { width: 7px; height: 7px; border-radius: 50%; background: var(--rg-border); flex-shrink: 0; }
.smp-open     .smp-dot { background: var(--rg-success); }
.smp-occupied .smp-dot { background: var(--rg-accent); }
.smp-blocked  .smp-dot { background: var(--rg-danger); }
.smp-tile-name {
  font-size: .8rem; font-weight: 700; line-height: 1.15;
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.smp-tile-body { min-width: 0; overflow: hidden; }
.smp-tile-ro   { font-size: .82rem; font-weight: 700; color: var(--rg-accent); text-decoration: none; }
.smp-tile-sub  { font-size: .72rem; color: var(--rg-muted); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.smp-tile-time { font-family: 'DM Mono', monospace; font-size: .64rem; color: var(--rg-muted); }
.smp-tile-block { font-size: .74rem; font-weight: 600; color: var(--rg-danger); }
.smp-tile-next {
  font-size: .64rem; color: var(--rg-muted);
  margin-top: auto; padding-top: 3px;
  border-top: 1px dashed var(--rg-border);
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}

/* Row of actions, out of the way until the tile is hovered */
.smp-tile-acts {
  position: absolute;
  right: 3px; bottom: 3px;
  display: flex;
  gap: 1px;
  background: rgba(255,255,255,.92);
  border-radius: 6px;
  opacity: 0;
  transition: opacity .12s;
}
.smp-tile:hover .smp-tile-acts { opacity: 1; }
.smp-tile-acts .rg-icon-btn { width: 24px; height: 24px; font-size: .68rem; }

.smp-tile-size {
  display: flex;
  align-items: center;
  gap: 3px;
  margin-top: auto;
  font-size: .64rem;
  color: var(--rg-muted);
}
.smp-tile-size button {
  width: 19px; height: 19px;
  border: 1px solid var(--rg-border);
  border-radius: 4px;
  background: var(--rg-surface);
  color: var(--rg-muted);
  font-size: .6rem;
  line-height: 1;
  padding: 0;
  cursor: pointer;
}
.smp-tile-size button:hover { border-color: var(--rg-accent); color: var(--rg-accent); }

/* Drop preview drawn by rg-floor */
.smp-ghost {
  border: 2px dashed var(--rg-accent);
  border-radius: 7px;
  background: rgba(37,99,235,.07);
  pointer-events: none;
}

/* ============================================================
   IDENTITY CARDS (customer / vehicle on a repair order)
   A heading block, then details — rather than a column of
   label/value rows, which reads as a form and buries the name.
   ============================================================ */
.rg-ident {
  display: flex;
  align-items: center;
  gap: 11px;
  padding-bottom: 9px;
  margin-bottom: 9px;
  border-bottom: 1px solid var(--rg-border);
}
/* Vehicle: identity left, plate/VIN right, on one row. */
.rg-ident-veh { justify-content: space-between; flex-wrap: wrap; gap: 8px 14px; }
/* Customer: identity left, phone/email right — same layout as the
   vehicle card so the two cards read as a matched pair. The mark and
   name are grouped in .rg-ident-who so space-between only splits two
   items (who / contact), not three — otherwise the name would drift
   toward the middle, away from the mark beside it. */
.rg-ident-cust { justify-content: space-between; flex-wrap: wrap; gap: 8px 14px; }
.rg-ident-who { display: flex; align-items: center; gap: 11px; min-width: 0; }
.rg-ident-mark {
  width: 38px; height: 38px;
  flex-shrink: 0;
  border-radius: 50%;
  display: flex;
  align-items: center;
  justify-content: center;
  background: rgba(37,99,235,.10);
  color: var(--rg-accent);
  font-size: .9rem;
  font-weight: 700;
  letter-spacing: .02em;
}
.rg-ident-text { min-width: 0; }
.rg-ident-name {
  font-size: 1rem;
  font-weight: 700;
  line-height: 1.2;
  overflow: hidden;
  text-overflow: ellipsis;
}
.rg-ident-sub { font-size: .8rem; color: var(--rg-muted); margin-top: 1px; }

/* Contact lines double as actions — tap to dial, tap to mail. */
.rg-contact {
  display: flex;
  align-items: flex-start;
  gap: 10px;
  padding: 5px 8px;
  margin: 0 -8px;
  border-radius: 7px;
  font-size: .85rem;
  color: var(--rg-text);
  text-decoration: none;
  transition: background .12s;
}
a.rg-contact:hover { background: var(--rg-bg); color: var(--rg-accent); }
.rg-contact i { width: 16px; text-align: center; color: var(--rg-muted); font-size: .82rem; margin-top: 2px; }
a.rg-contact:hover i { color: var(--rg-accent); }
.rg-contact-static { color: var(--rg-muted); }
.rg-contact-right { justify-content: flex-end; text-align: right; }
.rg-contact-wrap { word-break: break-all; }
.rg-muted-text { color: var(--rg-muted); }

/* Plate and VIN: the two things read aloud to a parts counter. Both
   sit inline with the vehicle name rather than on rows of their own. */
.rg-vid { display: flex; align-items: center; flex-wrap: wrap; gap: 6px; min-width: 0; }
.rg-plate {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  max-width: 100%;
  border: 1px solid var(--rg-border);
  border-radius: 5px;
  background: var(--rg-bg);
  padding: 3px 8px;
  font-size: .72rem;
  color: var(--rg-text);
  cursor: pointer;
  min-width: 0;
  flex-shrink: 0;
}
.rg-plate:hover { border-color: var(--rg-accent); color: var(--rg-accent); }
.rg-plate:hover i { color: var(--rg-accent); }
.rg-plate-copied { border-color: var(--rg-success); color: var(--rg-success); }
.rg-plate-copied i { color: var(--rg-success); }
.rg-plate-label {
  font-size: .55rem; font-weight: 700; letter-spacing: .12em;
  text-transform: uppercase; color: var(--rg-muted);
  flex-shrink: 0;
}
.rg-plate-num { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.rg-plate i   { font-size: .64rem; color: var(--rg-muted); flex-shrink: 0; }

.rg-vin {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  max-width: 100%;
  border: 1px solid var(--rg-border);
  border-radius: 5px;
  background: var(--rg-bg);
  padding: 3px 8px;
  font-size: .72rem;
  color: var(--rg-text);
  cursor: pointer;
  min-width: 0;
}
.rg-vin:hover { border-color: var(--rg-accent); color: var(--rg-accent); }
.rg-vin-copied { border-color: var(--rg-success); color: var(--rg-success); }
.rg-vin-copied i { color: var(--rg-success); }
.rg-vin-label {
  font-size: .55rem; font-weight: 700; letter-spacing: .12em;
  text-transform: uppercase; color: var(--rg-muted);
  flex-shrink: 0;
}
.rg-vin-num { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.rg-vin i { font-size: .64rem; color: var(--rg-muted); flex-shrink: 0; }

/* Phone/email badges beside the customer name — same box as plate/VIN. */
.rg-contact-badge {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  max-width: 100%;
  border: 1px solid var(--rg-border);
  border-radius: 5px;
  background: var(--rg-bg);
  padding: 3px 8px;
  font-size: .72rem;
  color: var(--rg-text);
  min-width: 0;
  text-decoration: none;
}
.rg-contact-badge:hover { border-color: var(--rg-accent); color: var(--rg-accent); }
.rg-contact-badge:hover i { color: var(--rg-accent); }
.rg-contact-badge i { font-size: .64rem; color: var(--rg-muted); flex-shrink: 0; }
.rg-contact-badge .rg-mono { font-size: inherit; }
.rg-contact-badge .rg-contact-wrap { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; font-size: inherit; }

/* Spec pairs — three across so six specs cost two rows, not three.
   Long values (transmission styles) clip with a title tooltip. */
.rg-spec {
  display: grid;
  grid-template-columns: repeat(3, minmax(0, 1fr));
  gap: 8px 12px;
}
.rg-spec-item { min-width: 0; }
.rg-spec-label {
  font-size: .58rem;
  font-weight: 700;
  letter-spacing: .1em;
  text-transform: uppercase;
  color: var(--rg-muted);
}
.rg-spec-value {
  font-size: .82rem;
  font-weight: 600;
  line-height: 1.25;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
@media (max-width: 575px) {
  .rg-spec { grid-template-columns: repeat(2, minmax(0, 1fr)); }
}

/* ── Corner grip: drag to resize a tile ── */
.smp-resize {
  position: absolute;
  right: 0; bottom: 0;
  width: 16px; height: 16px;
  cursor: nwse-resize;
  /* Two hairlines, the usual grip idiom. */
  background:
    linear-gradient(135deg, transparent 0 45%, var(--rg-muted) 45% 55%, transparent 55%) no-repeat,
    linear-gradient(135deg, transparent 0 70%, var(--rg-muted) 70% 80%, transparent 80%) no-repeat;
  background-size: 100% 100%;
  opacity: .5;
}
.smp-tile:hover .smp-resize { opacity: 1; }

/* ── Bay assignment on a repair order ──
   Stacked, not side by side: this card lives in the sidebar column
   beside the order, so the plan gets the full width of the card and the
   times sit under it. */
.smp-assign { display: block; }
.smp-assign-plan { min-width: 0; }

/* Times sit under the floor plan, one field per row. Two
   datetime-locals side by side in the sidebar column were squeezed to
   the point where neither could be read. */
.smp-assign-when {
  margin-top: 14px;
  padding-top: 14px;
  border-top: 1px solid var(--rg-border);
}
.smp-assign-when .rg-input { width: 100%; }
.smp-assign-when .rg-hint  { margin-top: 6px; }
.smp-assign-when .rg-hint i { color: var(--rg-accent); margin-right: 4px; }

/* ── Mini plan: the bay picker on a repair order ── */
.smp-floor-mini {
  padding: 6px;
  gap: 1px;
  margin-bottom: 6px;
  /* Tall enough that an 18-row grid still gives each tile readable
     height in the narrow sidebar column. */
  min-height: 260px;
}
.smp-floor-mini .smp-tile {
  padding: 3px 5px;
  border-radius: 5px;
  border-left-width: 2px;
  cursor: default;
  opacity: .4;
  justify-content: center;
}
.smp-floor-mini .smp-tile-name {
  font-size: .66rem;
  line-height: 1.1;
}
.smp-floor-mini .smp-tile-sub {
  font-size: .6rem;
  line-height: 1.1;
}
/* A drain run is one or two cells wide; at picker scale its label just
   spills as clipped letters. The hatch already reads as "not a bay",
   and the tile keeps its title attribute for hover. */
.smp-floor-mini .smp-fixture .smp-tile-name { display: none; }
/* Only a bay that can actually take the car is live. */
.smp-floor-mini .smp-tile.smp-pick { opacity: 1; cursor: pointer; }
.smp-floor-mini .smp-tile.smp-pick:hover { border-color: var(--rg-accent); background: rgba(37,99,235,.06); }
.smp-floor-mini .smp-tile.smp-picked {
  border-color: var(--rg-accent);
  background: rgba(37,99,235,.12);
  box-shadow: 0 0 0 2px rgba(37,99,235,.25);
}
.smp-check {
  position: absolute;
  right: 3px; top: 3px;
  font-size: .6rem;
  color: var(--rg-accent);
}

/* ── Day schedule strip ── */
.rg-sched-row {
  display: flex;
  gap: 14px;
  padding: 10px 0;
  border-bottom: 1px solid var(--rg-border);
}
.rg-sched-row:last-child { border-bottom: none; }
.rg-sched-bay { width: 130px; flex-shrink: 0; font-weight: 600; font-size: .88rem; }
.rg-sched-items { flex: 1; display: flex; flex-wrap: wrap; gap: 6px; }
.rg-sched-item {
  display: inline-flex;
  align-items: center;
  gap: 8px;
  border-radius: 6px;
  padding: 5px 10px;
  font-size: .8rem;
  border: 1px solid transparent;
}
.rg-sched-order { background: rgba(37,99,235,.07); border-color: rgba(37,99,235,.22); }
.rg-sched-block { background: rgba(220,38,38,.07); border-color: rgba(220,38,38,.22); color: #991b1b; }
.rg-sched-time  { font-size: .72rem; color: var(--rg-muted); }
/* Released / already ended — visible but clearly not live. */
.rg-sched-done {
  opacity: .5;
  background: var(--rg-bg);
  border-color: var(--rg-border);
  text-decoration: line-through;
}
.rg-sched-empty { font-size: .8rem; color: var(--rg-muted); padding: 5px 0; }

/* Role section — the one decision in the staff editor that determines
   everything else about the person, so it gets a container of its own
   rather than blending into the rest of the form. */
.rg-role-section {
  margin: -2px -2px 20px;
  padding: 16px 16px 18px;
  border: 1px solid var(--rg-border);
  border-radius: calc(var(--rg-radius) + 2px);
  background: linear-gradient(180deg, rgba(37,99,235,.05), rgba(37,99,235,0) 60%);
}
.rg-role-section-head { margin-bottom: 12px; }
.rg-role-section-title {
  font-size: 1.02rem;
  font-weight: 800;
  letter-spacing: .01em;
}
.rg-role-section-sub { font-size: .82rem; color: var(--rg-muted); margin-top: 2px; }

/* Role picker — cards rather than bare checkboxes, because the choice
   decides what the person can do. Technician sits in the same grid as
   the office roles (it's just fixed once saved, not a different kind
   of control); the others stay changeable any time. */
.rg-role-pick {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(260px, 1fr));
  gap: 10px;
}
.rg-role-opt {
  position: relative;
  display: grid;
  grid-template-columns: 22px 1fr;
  grid-template-areas:
    "icon name"
    "icon desc";
  gap: 2px 10px;
  padding: 13px 15px;
  border: 1px solid var(--rg-border);
  border-radius: var(--rg-radius);
  background: var(--rg-surface);
  cursor: pointer;
  transition: border-color .12s, background .12s, box-shadow .12s, opacity .12s;
}
.rg-role-opt:hover { border-color: #cbd5e1; }
/* The native control is replaced, not removed: it still owns the
   keyboard and screen-reader behaviour. */
.rg-role-opt input {
  position: absolute;
  opacity: 0;
  width: 0;
  height: 0;
}
.rg-role-opt i {
  grid-area: icon;
  align-self: start;
  margin-top: 2px;
  color: var(--rg-muted);
  font-size: .95rem;
}
.rg-role-name { grid-area: name; font-weight: 700; font-size: .92rem; }
.rg-role-desc { grid-area: desc; font-size: .78rem; color: var(--rg-muted); line-height: 1.45; }

/* Four roles, one row: narrower cards than the base picker, so all of
   them sit side by side instead of wrapping. */
.rg-role-pick-lg {
  grid-template-columns: repeat(4, minmax(0, 1fr));
  gap: 8px;
}
.rg-role-pick-lg .rg-role-opt {
  grid-template-columns: 16px 1fr;
  gap: 1px 7px;
  padding: 10px 11px;
}
.rg-role-pick-lg .rg-role-name { font-size: .82rem; }
.rg-role-pick-lg .rg-role-desc { font-size: .68rem; line-height: 1.35; }
.rg-role-pick-lg i { font-size: .82rem; margin-top: 1px; }
.rg-role-pick-lg .rg-role-fixed-tag { top: 8px; right: 9px; font-size: .58rem; }

@media (max-width: 767.98px) {
  .rg-role-pick-lg { grid-template-columns: repeat(2, minmax(0, 1fr)); }
}

.rg-role-on {
  border-color: var(--rg-accent);
  background: rgba(37,99,235,.07);
  box-shadow: 0 0 0 3px rgba(37,99,235,.14);
}
.rg-role-on i { color: var(--rg-accent); }
/* Keyboard focus has to be visible when the checkbox itself isn't. */
.rg-role-opt input:focus-visible ~ .rg-role-name { text-decoration: underline; }

/* Technician, once saved: still shown so the card grid doesn't shift
   shape, but no longer a live control — the record it stands for can't
   change kind after creation. */
.rg-role-fixed { cursor: default; opacity: .7; }
.rg-role-fixed:hover { border-color: var(--rg-border); }
.rg-role-fixed.rg-role-on:hover { border-color: var(--rg-accent); }
.rg-role-fixed-tag {
  position: absolute;
  top: 10px;
  right: 12px;
  font-size: .64rem;
  font-weight: 700;
  text-transform: uppercase;
  letter-spacing: .04em;
  color: var(--rg-muted);
}

/* Implied by Office admin — dimmed rather than hidden, so it's clear
   the permission is still there, just not worth granting twice. */
.rg-role-implied { opacity: .45; }
.rg-role-implied input { pointer-events: none; }

/* Credential type: PIN, password, or nothing yet. */
.rg-cred-pick { display: flex; flex-wrap: wrap; gap: 6px; margin-bottom: 14px; }
.rg-cred {
  display: inline-flex;
  align-items: center;
  gap: 7px;
  border: 1px solid var(--rg-border);
  border-radius: 8px;
  background: var(--rg-surface);
  padding: 8px 14px;
  font-family: inherit;
  font-size: .85rem;
  font-weight: 600;
  color: var(--rg-muted);
  cursor: pointer;
  transition: border-color .12s, color .12s, background .12s;
}
.rg-cred:hover { border-color: #cbd5e1; color: var(--rg-text); }
.rg-cred i { font-size: .78rem; }
.rg-cred-on {
  border-color: var(--rg-accent);
  background: rgba(37,99,235,.06);
  color: var(--rg-accent);
}

/* Four digits, spaced out so they're readable at a glance. */
.rg-pin {
  font-family: 'DM Mono', monospace;
  font-size: 1.3rem;
  letter-spacing: .4em;
  text-align: center;
  padding-right: 0;
}

/* ── Login box (staff page) ── */
.rg-login-box {
  margin-top: 16px;
  padding: 14px 16px;
  border: 1px dashed var(--rg-border);
  border-radius: var(--rg-radius);
  background: var(--rg-bg);
}

/* ============================================================
   TECHNICIAN PORTAL
   Same design system as the office side — .rg-page/.rg-card/.rg-pill/
   --rg-* tokens, not a bespoke skin — with bigger touch targets since
   this runs on a phone in the bay.
   ============================================================ */
.smp-login-mode  { background: var(--rg-bg) !important; }

/* Standalone/PWA mode has no browser chrome left to pull down, and
   #contentContainer (not the document) is the actual scroller here
   either way — so native pull-to-refresh never reaches this app.
   rg-pull-refresh rebuilds the gesture by hand; this just stops the
   scroller's own edge-bounce from fighting it while dragging. */
.smp-portal-mode { overscroll-behavior-y: contain; }

/* ── Pull to refresh ──
   The system spinner, drawn by hand: twelve tapered spokes on nothing.
   No disc, no shadow, no arrow, no accent — a filled circle with an
   arrow in it is another platform's idiom, and beside iOS chrome it
   reads as somebody else's control pasted in.

   Sizing is the whole of the centring. The old version put a 38px svg
   inside a 38px BORDER box, which is a 36px padding box, so the ring
   drew from the padding origin and hung a pixel past the far edge —
   centred by a rule that was one pixel out of two. Nothing here has a
   border, and the svg is the element, so there is no second box to
   agree with. */
.smp-ptr {
  position: absolute;
  /* Just above the page, which on an office screen starts below the app
     bar — the bar has already taken the inset, so there is none left to
     climb over here. */
  top: -34px;
  left: 50%;
  width: 28px;
  height: 28px;
  margin-left: -14px;
  opacity: 0;
  pointer-events: none;
  z-index: 5;
}
.smp-ptr svg {
  display: block;
  width: 28px;
  height: 28px;
}
.smp-ptr-spoke {
  fill: var(--rg-muted);
  opacity: 0;
}

/* Committed. A spoke at a time, not a sweep: the step is what makes it
   the system's spinner rather than a rotating graphic. */
.smp-ptr-spin svg { animation: smp-ptr-spin 1s steps(12, end) infinite; }
@keyframes smp-ptr-spin { to { transform: rotate(360deg); } }

/* The portal's page starts at the very top of the window instead, so its
   indicator has the cutout to clear as well. */
.smp-portal .smp-ptr { top: calc(-34px - var(--safe-t)); }

/* The sign-in screen is the shop application's own surface, not the
   portal's dark one — it's the front door to both, and a tech arriving
   at a white card sees the same software the office does.

   It fills the window: a percentage height only resolves if every
   ancestor has one, so this measures against the viewport instead. dvh
   follows mobile browser chrome as it hides, which vh does not. */
.smp-login {
  background: var(--rg-bg);
  color: var(--rg-text);
  min-height: 100vh;
  min-height: 100dvh;
  display: flex;
  flex-direction: column;
  justify-content: center;
}
.smp-login .smp-signin { min-height: 0; flex: 0 0 auto; }

/* First-time setup, reached from the sign-in form itself. Same info
   callout the office pages use. */
.rg-setup-head {
  display: flex;
  align-items: flex-start;
  gap: 10px;
  margin-bottom: 16px;
  padding: 11px 13px;
  border: 1px solid rgba(37,99,235,.25);
  border-radius: 8px;
  background: rgba(37,99,235,.06);
  font-size: .82rem;
  color: var(--rg-text);
  line-height: 1.5;
}
.rg-setup-head i { margin-top: 2px; color: var(--rg-accent); }

/* Sign in */
.smp-signin {
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  padding: 40px 16px;
  min-height: 70vh;
}

/* An ordinary card, sized for one job and lifted a little further off
   the page than the ones inside the app — nothing else competes with it
   on this screen. */
.smp-signin-card {
  width: 100%;
  max-width: 400px;
  background: var(--rg-surface);
  border: 1px solid var(--rg-border);
  border-radius: 14px;
  padding: 28px 26px 24px;
  box-shadow: 0 10px 30px rgba(16,24,40,.08), 0 2px 6px rgba(16,24,40,.04);
}

/* Brand block, stacked and centred: this is a front door, not a header. */
.smp-signin-brand {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 12px;
  margin-bottom: 22px;
  padding-bottom: 20px;
  border-bottom: 1px solid var(--rg-border);
  text-align: center;
}
.smp-signin-brand .smp-mark {
  width: 46px;
  height: 46px;
  font-size: .8rem;
  border-radius: 12px;
}
.smp-signin-title { font-size: 1.15rem; font-weight: 700; color: var(--rg-text); letter-spacing: -.2px; }
.smp-signin-title b { color: var(--rg-accent); }
.smp-signin-sub   { font-size: .82rem; color: var(--rg-muted); margin-top: 2px; }

.smp-signin-card .rg-label { font-size: .74rem; }
.smp-signin-card .rg-input { background: #fff; }
.smp-signin-card .rg-input:focus {
  border-color: var(--rg-accent);
  box-shadow: 0 0 0 3px rgba(37,99,235,.12);
}
.smp-signin-card .rg-btn-primary { padding: 11px; font-size: .9rem; }

/* The hint sits under the card, not in it — it's orientation, not part
   of the form. */
.smp-login .rg-hint {
  max-width: 400px;
  margin: 16px auto 0;
  text-align: center;
  line-height: 1.6;
}

/* Portal bar — the same sticky-header formula as .rg-topbar (white
   surface, bottom border, safe-area-aware padding), just under its own
   name since it also carries the live stopwatch on the job view. */
.smp-portal-bar {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 12px;
  /* Grows by the top inset when the portal is run full-screen from a
     phone's home screen. */
  padding: calc(14px + max(var(--safe-t), var(--edge-fade))) calc(20px + var(--safe-r)) 14px calc(20px + var(--safe-l));
  border-bottom: 1px solid var(--rg-border);
  position: sticky;
  top: 0;
  z-index: 200;
}
.smp-portal-hi  { font-size: 1.05rem; font-weight: 700; color: var(--rg-text);
                  overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.smp-portal-sub { font-size: .78rem; color: var(--rg-muted); display: flex; align-items: center; gap: 4px; }

/* Elapsed time, kept at the top of the job and always with seconds —
   a tech glancing at the phone wants to see it moving. */
.smp-bartime {
  display: flex;
  align-items: center;
  gap: 7px;
  flex-shrink: 0;
  font-family: 'DM Mono', monospace;
  font-size: 1.15rem;
  font-weight: 500;
  color: var(--rg-muted);
  font-variant-numeric: tabular-nums;   /* digits don't jitter as they tick */
}
.smp-bartime i { font-size: .85rem; }
.smp-bartime-live { color: var(--rg-success); }
.smp-bartime-live i { font-size: .45rem; animation: smpPulse 1.6s ease-in-out infinite; }

@media (max-width: 400px) {
  .smp-bartime { font-size: 1rem; }
}

/* ── Job list ──
   Rows are navigation only. Everything a tech does happens inside a
   job, so there's one screen to aim at rather than controls scattered
   down a scrolling list. Same surface/border/shadow formula as .rg-card. */
.smp-row {
  display: flex;
  align-items: center;
  gap: 12px;
  width: 100%;
  text-align: left;
  background: var(--rg-surface);
  border: 1px solid var(--rg-border);
  border-radius: var(--rg-radius);
  box-shadow: var(--rg-shadow);
  padding: 15px 16px;
  margin-bottom: 10px;
  cursor: pointer;
  font-family: inherit;
  min-height: 76px;
}
.smp-row:hover, .smp-row:active { background: var(--rg-bg); border-color: var(--rg-accent); }
.smp-row-main  { flex: 1; min-width: 0; }
.smp-row-title { font-size: 1rem; font-weight: 700; color: var(--rg-text); }
.smp-row-ro    { font-size: .78rem; color: var(--rg-muted); margin-left: 6px; }
.smp-row-sub   { font-size: .82rem; color: var(--rg-muted); margin-top: 2px;
                 overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.smp-row-tags  { display: flex; flex-wrap: wrap; align-items: center; gap: 6px; margin-top: 8px; }
.smp-row-caret { color: var(--rg-border); font-size: .85rem; flex-shrink: 0; }

/* Banner: what the clock is on, from anywhere in the list. Same recipe
   as .rg-alert-success. */
.smp-onclock {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 12px;
  padding: 13px 16px;
  margin-bottom: 12px;
  border: 1px solid rgba(22,163,74,.25);
  border-radius: var(--rg-radius);
  background: rgba(22,163,74,.08);
  cursor: pointer;
}
.smp-onclock-label {
  display: flex; align-items: center; gap: 7px;
  font-size: .6rem; font-weight: 700; letter-spacing: .14em;
  text-transform: uppercase; color: #166534;
}
.smp-onclock-label i { font-size: .42rem; animation: smpPulse 1.6s ease-in-out infinite; }
.smp-onclock-job  { font-size: .9rem; font-weight: 600; color: var(--rg-text); margin-top: 3px; }
.smp-onclock-time { font-family: 'DM Mono', monospace; font-size: 1.05rem; color: var(--rg-success); }

/* ── One job ── */
.smp-job-view { display: flex; flex-direction: column; min-height: 100%; }
/* An actual element, not padding on the scrolling container — a
   padding-bottom here has to fight box-sizing and every other rule
   already touching .rg-content/.smp-job-content, and it's not obvious
   from the DOM whether it won. A spacer with its own height can't lose
   that fight; it just takes up room. --smp-clockbar-h is the clock
   bar's real measured height (portal.js), which already includes its
   own safe-area padding, since that's baked into what actually got
   measured; 104px is only a fallback for the instant before that first
   measurement lands. */
.smp-clockbar-spacer { height: calc(var(--smp-clockbar-h, 104px) + 16px); }
.smp-job-view .rg-input {
  min-height: 48px;             /* thumb-sized, for gloved hands */
}
.smp-job-view .rg-btn-ghost {
  min-height: 46px;
}

/* ── The clock, pinned where a thumb lands ── */
.smp-clockbar {
  position: fixed;
  left: 0; right: 0; bottom: 0;
  z-index: 1200;
  display: flex;
  align-items: center;
  gap: 12px;
  padding: 10px calc(16px + var(--safe-r)) calc(10px + var(--safe-b)) calc(16px + var(--safe-l));
  border-top: 1px solid var(--rg-border);
  box-shadow: var(--rg-shadow-md);
}
.smp-clockbtn {
  flex: 1;
  min-height: 60px;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: 11px;
  border: none;
  border-radius: 12px;
  background: var(--rg-success);
  color: #fff;
  font-family: inherit;
  font-size: 1.05rem;
  font-weight: 700;
  cursor: pointer;
  transition: background .12s, transform .08s;
}
.smp-clockbtn:active:not(:disabled) { transform: scale(.985); }
.smp-clockbtn:disabled { opacity: .65; cursor: default; }
/* Running: the button now ends the punch, so it reads as stop. */
.smp-clockbtn-on { background: var(--rg-danger); color: #fff; }

/* ── Clock confirmation ──
   A bottom sheet rather than a centred dialog: it opens where the
   thumb already is, right over the button that summoned it. */
.smp-confirm-back {
  position: fixed;
  inset: 0;
  z-index: 1250;
  background: rgba(15,23,32,.45);
  animation: smpScrimIn .34s cubic-bezier(.22, 1, .36, 1);
}
@keyframes smpScrimIn { from { opacity: 0; } to { opacity: 1; } }

.smp-confirm {
  position: fixed;
  left: 0; right: 0; bottom: 0;
  z-index: 1300;
  background: var(--rg-surface);
  border-top: 1px solid var(--rg-border);
  border-radius: 22px 22px 0 0;
  padding: 8px calc(20px + var(--safe-r)) calc(20px + var(--safe-b)) calc(20px + var(--safe-l));
  box-shadow: var(--rg-shadow-md);
  /* Emphasised decelerate: quick away from the edge, soft landing. */
  animation: smpSheetUp .34s cubic-bezier(.22, 1, .36, 1);
  /* The sheet owns vertical gestures; the page must not scroll under
     the finger while it's being dragged. */
  touch-action: none;
  will-change: transform;
  /* A drag shouldn't select the label text it starts on. */
  user-select: none;
  -webkit-user-select: none;
}
@keyframes smpSheetUp {
  from { transform: translate3d(0, 100%, 0); }
  to   { transform: translate3d(0, 0, 0); }
}

/* Bigger than it looks — the grip is the obvious thing to grab, so it
   gets a generous target around it. */
.smp-confirm-grip {
  width: 40px; height: 5px;
  border-radius: 3px;
  background: var(--rg-border);
  margin: 0 auto;
  padding: 0;
  position: relative;
  cursor: grab;
}
.smp-confirm-grip::after {
  content: '';
  position: absolute;
  left: -40px; right: -40px; top: -12px; bottom: -16px;
}
.smp-confirm:active .smp-confirm-grip { background: var(--rg-muted); }
.smp-confirm-title { margin-top: 22px; }
.smp-confirm-title  { font-size: 1.2rem; font-weight: 700; color: var(--rg-text); }
.smp-confirm-detail { font-size: .88rem; color: var(--rg-muted); margin-top: 3px; }

.smp-confirm-logged,
.smp-confirm-warn {
  display: flex;
  align-items: center;
  gap: 8px;
  margin-top: 12px;
  padding: 9px 12px;
  border-radius: 8px;
  font-size: .82rem;
}
.smp-confirm-logged {
  background: var(--rg-bg);
  border: 1px solid var(--rg-border);
  color: var(--rg-text);
}
/* Same recipe as .rg-alert-warn. */
.smp-confirm-warn {
  background: rgba(217,119,6,.08);
  border: 1px solid rgba(217,119,6,.25);
  color: #92400e;
}
.smp-confirm .rg-btn-ghost {
  min-height: 48px;
}

.smp-job-facts {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(140px, 1fr));
  gap: 12px 16px;
  padding: 4px 0 16px;
}
.smp-fact-wide { grid-column: 1 / -1; }
.smp-job-facts span {
  display: block; font-size: .58rem; font-weight: 700; letter-spacing: .12em;
  text-transform: uppercase; color: var(--rg-muted);
}
.smp-job-facts b { font-size: .88rem; color: var(--rg-text); font-weight: 600; }
.smp-vin { font-size: .76rem; word-break: break-all; }

.smp-job-lines { padding-bottom: 6px; }
.smp-line {
  display: flex;
  align-items: baseline;
  gap: 10px;
  padding: 7px 0;
  border-bottom: 1px solid var(--rg-border);
  font-size: .85rem;
}
.smp-line:last-child { border-bottom: none; }
.smp-line-type {
  font-size: .55rem; font-weight: 700; letter-spacing: .1em;
  color: var(--rg-muted); width: 46px; flex-shrink: 0;
}
.smp-line-desc { flex: 1; min-width: 0; color: var(--rg-text); }
.smp-line-qty  { color: var(--rg-muted); font-size: .78rem; }

/* A shared ticket: the lines this technician is actually on are marked,
   and the ones that aren't say whose they are. Without it a tech opens
   an order pulled in by one line and sees somebody else's whole job. */
.smp-line-mine { border-left: 2px solid var(--rg-accent); padding-left: 8px; margin-left: -10px; }
.smp-line-mine .smp-line-desc { color: var(--rg-text); font-weight: 600; }
.smp-line-who {
  font-size: .7rem;
  color: var(--rg-muted);
  background: var(--rg-bg);
  border-radius: 20px;
  padding: 1px 8px;
  flex-shrink: 0;
}

/* ── Time clock ── */
.smp-running {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  font-family: 'DM Mono', monospace;
  font-size: .82rem;
  color: var(--rg-success);
  flex-shrink: 0;
}
.smp-running i { font-size: .42rem; animation: smpPulse 1.6s ease-in-out infinite; }
@keyframes smpPulse { 0%, 100% { opacity: 1; } 50% { opacity: .25; } }

/* Office-side "clock running" marker on the Time Logged card */
.smp-live-dot {
  font-size: .4rem;
  color: var(--rg-success);
  vertical-align: middle;
  margin-left: 5px;
  animation: smpPulse 1.6s ease-in-out infinite;
}

/* Debounced-save indicator */
.smp-autosave {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  font-size: .62rem;
  font-weight: 700;
  letter-spacing: .1em;
  text-transform: uppercase;
  color: var(--rg-muted);
}
.smp-autosave i { font-size: .5rem; }
.smp-autosave-done { color: var(--rg-success); }
.smp-autosave-done i { font-size: .62rem; }

/* Status buttons — big enough to hit without looking */
.smp-status-row { display: flex; gap: 8px; flex-wrap: wrap; }
.smp-status-row button {
  flex: 1;
  min-width: 118px;
  min-height: 46px;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: 8px;
  border: 1px solid var(--rg-border);
  border-radius: 9px;
  background: var(--rg-bg);
  color: var(--rg-text);
  font-family: inherit;
  font-size: .85rem;
  font-weight: 600;
  cursor: pointer;
  transition: border-color .12s, background .12s, color .12s;
}
.smp-status-row button:hover:not(:disabled) { border-color: var(--rg-accent); }
.smp-status-row button.smp-on {
  background: rgba(37,99,235,.1);
  border-color: var(--rg-accent);
  color: var(--rg-accent);
}
.smp-status-row button:disabled { opacity: .5; cursor: default; }

/* ── Letterhead preview (settings) ── */
.rg-letterhead {
  display: flex;
  justify-content: space-between;
  align-items: flex-start;
  gap: 24px;
  padding: 18px 20px 14px;
  background: #fff;
  border: 1px solid var(--rg-border);
  border-radius: var(--rg-radius) var(--rg-radius) 0 0;
}
.rg-lh-name { font-size: 1.4rem; font-weight: 700; letter-spacing: -.02em; }
.rg-lh-tag  { font-size: .82rem; color: var(--rg-muted); margin-top: 1px; }
.rg-lh-meta { font-size: .78rem; color: var(--rg-muted); margin-top: 8px; line-height: 1.5; }
.rg-lh-doc  { text-align: right; flex-shrink: 0; }
.rg-lh-title {
  font-size: 1.25rem; font-weight: 700; letter-spacing: .06em;
  text-transform: uppercase; color: var(--rg-accent);
}
.rg-lh-num { font-family: 'DM Mono', monospace; font-size: .95rem; margin-top: 2px; }
.rg-lh-rule {
  height: 2px;
  background: var(--rg-text);
  margin-bottom: 12px;
}

/* ── Print split-button ── */
.rg-print-group { position: relative; display: inline-flex; }
.rg-print-group .rg-btn-ghost { border-radius: 8px 0 0 8px; }
.rg-print-caret { border-radius: 0 8px 8px 0 !important; border-left: none !important; padding: 0 9px !important; }
.rg-print-menu {
  position: absolute;
  top: calc(100% + 4px);
  right: 0;
  z-index: 400;
  min-width: 210px;
  background: var(--rg-surface);
  border: 1px solid var(--rg-border);
  border-radius: var(--rg-radius);
  box-shadow: var(--rg-shadow-md);
  padding: 4px;
}
.rg-print-menu button {
  display: flex;
  align-items: center;
  gap: 9px;
  width: 100%;
  text-align: left;
  background: transparent;
  border: none;
  border-radius: 6px;
  padding: 8px 10px;
  font-family: inherit;
  font-size: .85rem;
  color: var(--rg-text);
  cursor: pointer;
}
.rg-print-menu button:hover { background: var(--rg-bg); }
.rg-print-menu button i { color: var(--rg-muted); font-size: .78rem; }
.rg-preview {
  background: var(--rg-bg);
  border: 1px solid var(--rg-border);
  border-radius: var(--rg-radius);
  padding: 14px 16px;
}
.rg-relink { border-bottom: 1px solid var(--rg-border); background: var(--rg-bg); }

/* Sticky action bar under a long form */
.rg-form-actions {
  position: sticky;
  bottom: var(--safe-b);
  z-index: 150;
  display: flex;
  align-items: center;
  justify-content: flex-end;
  gap: 10px;
  margin-top: 16px;
  padding: 12px 4px;
  background: linear-gradient(to top, var(--rg-bg) 68%, rgba(244,246,249,0));
}
/* Which controls belong to which screen. Bootstrap's d-none/d-sm-inline
   pair can't express "block on phones only" without fighting the
   button's own display, so these say it directly. */
.rg-phone-only { display: none; }

@media (max-width: 767px) {
  .rg-desk-only  { display: none !important; }
  .rg-phone-only { display: inline-flex; }

  /* Four equal columns rather than four stacked rows: stacked, the bar
     ate a third of a phone screen and pushed the work off it. Equal
     columns keep every target the same size and the same distance from
     the thumb. */
  .rg-form-actions {
    display: grid;
    grid-template-columns: repeat(4, 1fr);
    gap: 8px;
    padding-inline: 0;
  }
  /* Icons alone. Four labels across a phone are four truncated words,
     and these four — print, chat, revert, save — are the icons everyone
     already knows. The titles carry the words for anyone who wants
     them. */
  .rg-form-actions > button {
    width: 100%;
    justify-content: center;
    padding-block: 20px;
    font-size: 0;
  }
  .rg-form-actions > button i { font-size: 1.15rem; }
}

/* This used to have to stick --nav-h above the scroller's bottom edge,
   because the edge was --nav-h below the screen and the nav floated over
   the difference. The nav is in the column now: the scroller ends where
   the nav begins, and the bar sticks to that. Zero, not --safe-b — the
   handle is the nav's problem, and the nav is underneath. */
@media (max-width: 991px) {
  .rg-form-actions { bottom: 0; }
}

.rg-form-actions kbd {
  font-family: 'DM Mono', monospace;
  font-size: .7rem;
  background: var(--rg-surface);
  border: 1px solid var(--rg-border);
  border-bottom-width: 2px;
  border-radius: 4px;
  padding: 1px 5px;
  color: var(--rg-muted);
}
.rg-section-label {
  font-size: .7rem;
  font-weight: 700;
  letter-spacing: .08em;
  text-transform: uppercase;
  color: var(--rg-muted);
  margin-bottom: 10px;
}

/* ── Cards ── */
.rg-card {
  background: var(--rg-surface);
  border: 1px solid var(--rg-border);
  border-radius: var(--rg-radius);
  box-shadow: var(--rg-shadow);
  overflow: hidden;
}
/* Cards clip so table corners stay rounded — but a card holding an
   <rg-combo> has to let the dropdown escape. */
.rg-card.rg-card-open { overflow: visible; }
.rg-card-head {
  display: flex;
  align-items: center;
  gap: 10px;
  padding: 14px 16px;
  border-bottom: 1px solid var(--rg-border);
}
.rg-card-title { font-size: .95rem; font-weight: 700; }
/* A line of explanation beside a card's title, for a card whose name
   doesn't say what to do with it. */
.rg-card-sub { font-size: .78rem; color: var(--rg-muted); }
.rg-card-body { padding: 16px; }
/* Customer/Vehicle cards: same idea as the line-item table's compact
   mode — tighter padding and gaps, same content. */
.rg-card-compact .rg-card-body { padding: 10px 14px; }
.rg-card-compact .rg-ident { padding-bottom: 6px; margin-bottom: 6px; gap: 8px; }
.rg-card-compact .rg-contact { padding: 3px 8px; }
.rg-card-compact .rg-spec { gap: 6px 10px; }

/* ── Stat tiles ── */
.rg-stats {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(180px, 1fr));
  gap: 12px;
  margin-bottom: 22px;
}
.rg-stat {
  background: var(--rg-surface);
  border: 1px solid var(--rg-border);
  border-radius: var(--rg-radius);
  box-shadow: var(--rg-shadow);
  padding: 16px;
  display: flex;
  align-items: center;
  gap: 14px;
  transition: box-shadow .15s, transform .1s;
}
.rg-stat.rg-tappable { cursor: pointer; }
.rg-stat.rg-tappable:hover { box-shadow: var(--rg-shadow-md); transform: translateY(-1px); }
.rg-stat-icon {
  width: 40px; height: 40px;
  border-radius: 8px;
  display: flex; align-items: center; justify-content: center;
  font-size: 1rem;
  flex-shrink: 0;
  background: rgba(37,99,235,.10);
  color: var(--rg-accent);
}
.rg-stat-icon.rg-i-warn    { background: rgba(217,119,6,.12);  color: var(--rg-warn); }
.rg-stat-icon.rg-i-success { background: rgba(22,163,74,.12);  color: var(--rg-success); }
.rg-stat-icon.rg-i-danger  { background: rgba(220,38,38,.12);  color: var(--rg-danger); }
.rg-stat-num {
  font-family: 'DM Mono', monospace;
  font-size: 1.6rem;
  font-weight: 500;
  line-height: 1.1;
  letter-spacing: -.02em;
}
.rg-stat-label {
  font-size: .72rem;
  font-weight: 600;
  letter-spacing: .06em;
  text-transform: uppercase;
  color: var(--rg-muted);
  margin-top: 2px;
}

/* Five ageing tiles is one more than auto-fit's usual four, and on
   anything past a phone it packs all five into a single cramped row.
   Capped at three across so it wraps 3+2 instead — plenty of room for
   a page that adds a note under one of them. */
.rg-stats.rg-stats-cap3 { grid-template-columns: repeat(auto-fit, minmax(180px, 1fr)); }
@media (min-width: 576px) {
  .rg-stats.rg-stats-cap3 { grid-template-columns: repeat(2, 1fr); }
}
@media (min-width: 992px) {
  .rg-stats.rg-stats-cap3 { grid-template-columns: repeat(3, 1fr); }
}

/* ── Table ── */
.rg-table { width: 100%; border-collapse: collapse; font-size: .875rem; }
.rg-table th {
  text-align: left;
  font-size: .7rem;
  font-weight: 700;
  letter-spacing: .07em;
  text-transform: uppercase;
  color: var(--rg-muted);
  padding: 10px 16px;
  border-bottom: 1px solid var(--rg-border);
  white-space: nowrap;
  background: var(--rg-surface);
}
.rg-table td {
  padding: 12px 16px;
  border-bottom: 1px solid var(--rg-border);
  vertical-align: middle;
}
.rg-table tbody tr:last-child td { border-bottom: none; }
.rg-table tbody tr.rg-row-link { cursor: pointer; }
.rg-table tbody tr.rg-row-link:hover { background: var(--rg-bg); }
.rg-table-wrap { overflow-x: auto; }

/* Every row the same height: a long value makes its column wider — the
   wrapper above already scrolls sideways for that — rather than
   wrapping onto a second line and pushing that one row taller than its
   neighbours. The line-item grids inside an editor (part numbers,
   fitment, grouped lines) are their own thing and keep wrapping; a
   note column can opt back in with .rg-wrap where wrapping is actually
   wanted. */
.rg-table:not(.rg-line-table) th,
.rg-table:not(.rg-line-table) td { white-space: nowrap; }
.rg-table:not(.rg-line-table) td.rg-wrap { white-space: normal; }

/* A name, an email, a description — anything with no natural ceiling on
   length — clips with an ellipsis at a fixed width instead of forcing
   its column wider and the whole table into a horizontal scroll. The
   full value still lives in a title attribute wherever this is used, so
   nothing is actually lost, just not spelled out on the row. */
.rg-clip {
  display: inline-block;
  max-width: 200px;
  overflow: hidden;
  text-overflow: ellipsis;
  vertical-align: bottom;
}
.rg-clip-sm { max-width: 130px; }
.rg-clip-lg { max-width: 280px; }

.rg-mono { font-family: 'DM Mono', monospace; font-size: .8rem; color: var(--rg-muted); }
.rg-strong { font-weight: 600; }

/* ── Status pills ── */
.rg-pill {
  display: inline-block;
  font-size: .68rem;
  font-weight: 700;
  letter-spacing: .06em;
  text-transform: uppercase;
  padding: 3px 9px;
  border-radius: 20px;
  white-space: nowrap;
  border: 1px solid transparent;
}
.rg-pill-blue    { background: rgba(37,99,235,.10);  color: var(--rg-accent);  border-color: rgba(37,99,235,.22); }
.rg-pill-warn    { background: rgba(217,119,6,.12);  color: var(--rg-warn);    border-color: rgba(217,119,6,.25); }
.rg-pill-success { background: rgba(22,163,74,.12);  color: var(--rg-success); border-color: rgba(22,163,74,.25); }
.rg-pill-danger  { background: rgba(220,38,38,.12);  color: var(--rg-danger);  border-color: rgba(220,38,38,.25); }
.rg-pill-muted   { background: #eef2f7;              color: var(--rg-muted);   border-color: var(--rg-border); }

/* ── Empty state ── */
.rg-empty {
  padding: 44px 20px;
  text-align: center;
  color: var(--rg-muted);
}
.rg-empty i { font-size: 1.6rem; opacity: .5; display: block; margin-bottom: 10px; }
.rg-empty-title { font-weight: 700; color: var(--rg-text); margin-bottom: 4px; }

/* ── Two-column list/detail (vehicles, customers) ── */
.rg-split { display: grid; grid-template-columns: 1fr; gap: 14px; }
@media (min-width: 992px) {
  .rg-split { grid-template-columns: 1fr 340px; align-items: start; }
}

/* ── Form controls ── */
.rg-label {
  display: block;
  font-size: .7rem;
  font-weight: 700;
  letter-spacing: .06em;
  text-transform: uppercase;
  color: var(--rg-muted);
  margin-bottom: 4px;
}
.rg-input {
  width: 100%;
  min-height: 38px;
  border: 1px solid var(--rg-border);
  border-radius: 8px;
  background: #fff;
  padding: 8px 12px;
  font-family: inherit;
  font-size: .875rem;
  color: var(--rg-text);
  outline: none;
  transition: border-color .15s, box-shadow .15s;
}
.rg-input:focus {
  border-color: var(--rg-accent);
  box-shadow: 0 0 0 3px rgba(37,99,235,.12);
}
.rg-input[disabled] { background: var(--rg-bg); color: var(--rg-muted); }

/* ── Fields on a phone ──
   iOS zooms the entire page the instant a field smaller than 16px takes
   focus, and does not zoom back out. Every field in this app was
   .875rem, so every form on the phone ended with the page scaled up and
   the right-hand third of it off screen — which reads as a broken
   layout and is really just a font size.

   The cells in the line-item grid get the size but not the height: that
   table already scrolls sideways, and 44px rows would make a nine-column
   sheet taller than the screen. */
@media (max-width: 991.98px) {
  .rg-input,
  select.rg-input,
  textarea.rg-input { font-size: 16px; }

  .rg-input:not(.rg-cell-input) { min-height: 44px; }
}
textarea.rg-input { resize: vertical; }
.rg-mono-input { font-family: 'DM Mono', monospace; letter-spacing: .06em; text-transform: uppercase; }
.rg-check { display: inline-flex; align-items: center; gap: 7px; font-size: .8rem; color: var(--rg-muted); cursor: pointer; }
.rg-check input { accent-color: var(--rg-accent); }
.rg-hint { font-size: .78rem; color: var(--rg-muted); margin-top: 6px; }
.rg-hint a { color: var(--rg-accent); }
.rg-muted-icon { color: var(--rg-muted); font-size: .75rem; }

/* ── <rg-combo> searchable select ── */
rg-combo { display: block; }
.rg-combo { position: relative; }
.rg-combo-input { padding-right: 30px; }
.rg-combo-caret {
  position: absolute;
  right: 11px; top: 50%;
  transform: translateY(-50%);
  font-size: .7rem;
  color: var(--rg-muted);
  cursor: pointer;
}
.rg-combo-menu {
  position: absolute;
  z-index: 400;
  top: calc(100% + 4px);
  left: 0; right: 0;
  background: var(--rg-surface);
  border: 1px solid var(--rg-border);
  border-radius: var(--rg-radius);
  box-shadow: var(--rg-shadow-md);
  min-width: 210px;
  max-height: 260px;
  overflow-y: auto;
  padding: 4px;
}
.rg-combo-item {
  width: 100%;
  display: flex;
  align-items: baseline;
  gap: 8px;
  text-align: left;
  background: transparent;
  border: none;
  border-radius: 6px;
  padding: 8px 10px;
  font-family: inherit;
  font-size: .85rem;
  color: var(--rg-text);
  cursor: pointer;
}
/* Native :hover as well as the JS highlight, so the pointer feedback
   doesn't wait on a digest. */
.rg-combo-item:hover,
.rg-combo-item.rg-combo-active { background: var(--rg-bg); }
.rg-combo-item.rg-combo-current { color: var(--rg-accent); font-weight: 600; }
/* Retired-but-still-linkable: a technician who's left, kept in the list
   so an old invoice can still show who it was assigned to, without
   reading as a normal choice among the active staff above it. */
.rg-combo-item.rg-combo-item-muted .rg-combo-label { color: var(--rg-muted); opacity: .65; }
.rg-combo-item.rg-combo-item-muted .rg-combo-hint   { opacity: .65; }
.rg-combo-item.rg-combo-item-muted.rg-combo-current { color: var(--rg-muted); font-weight: 400; }
.rg-combo-label { flex-shrink: 0; }
.rg-combo-hint {
  font-size: .75rem;
  color: var(--rg-muted);
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

/* ── Owner row (vehicle edit) ── */
.rg-owner-row {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 10px;
  border: 1px solid var(--rg-border);
  border-radius: 8px;
  padding: 8px 8px 8px 12px;
  background: var(--rg-bg);
}

/* ── Tabs (search vs. create) ── */
.rg-tabs {
  display: flex;
  gap: 6px;
  padding: 10px 16px 0;
  border-bottom: 1px solid var(--rg-border);
}
.rg-tab {
  border: 1px solid transparent;
  border-bottom: none;
  background: transparent;
  color: var(--rg-muted);
  font-family: inherit;
  font-size: .8rem;
  font-weight: 600;
  padding: 8px 14px;
  border-radius: 8px 8px 0 0;
  cursor: pointer;
  margin-bottom: -1px;
  transition: color .15s, background .15s;
}
.rg-tab:hover { color: var(--rg-text); background: var(--rg-bg); }
.rg-tab.rg-tab-active {
  color: var(--rg-accent);
  background: var(--rg-surface);
  border-color: var(--rg-border);
  border-bottom: 1px solid var(--rg-surface);
}

/* ── Search result list ── */
.rg-result-list {
  border: 1px solid var(--rg-border);
  border-radius: var(--rg-radius);
  overflow: hidden;
  max-height: 320px;
  overflow-y: auto;
}
.rg-result {
  width: 100%;
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 12px;
  text-align: left;
  background: var(--rg-surface);
  border: none;
  border-bottom: 1px solid var(--rg-border);
  padding: 11px 14px;
  font-family: inherit;
  font-size: .875rem;
  color: var(--rg-text);
  cursor: pointer;
  transition: background .12s;
}
.rg-result:last-child { border-bottom: none; }
.rg-result:hover { background: var(--rg-bg); }

/* ── Linked customer / vehicle summary ── */
.rg-linked { display: flex; align-items: center; gap: 14px; }
.rg-linked-icon {
  width: 42px; height: 42px;
  border-radius: 8px;
  display: flex; align-items: center; justify-content: center;
  background: rgba(37,99,235,.10);
  color: var(--rg-accent);
  flex-shrink: 0;
}
.rg-linked-body { min-width: 0; }
.rg-linked-name { font-size: 1rem; font-weight: 700; }
.rg-linked-meta { font-size: .8rem; color: var(--rg-muted); margin-top: 2px; word-break: break-word; }

/* ── Alerts ── */
.rg-alert {
  display: flex;
  align-items: flex-start;
  gap: 9px;
  border-radius: 8px;
  padding: 10px 13px;
  font-size: .83rem;
  line-height: 1.45;
  border: 1px solid transparent;
  margin-bottom: 12px;
}
.rg-alert i { margin-top: 2px; flex-shrink: 0; }
.rg-alert a { font-weight: 600; }
.rg-alert-danger  { background: rgba(220,38,38,.07);  border-color: rgba(220,38,38,.25);  color: #991b1b; }
.rg-alert-warn    { background: rgba(217,119,6,.08);  border-color: rgba(217,119,6,.25);  color: #92400e; }
.rg-alert-success { background: rgba(22,163,74,.08);  border-color: rgba(22,163,74,.25);  color: #166534; }
.rg-alert-info    { background: rgba(37,99,235,.07);  border-color: rgba(37,99,235,.22);  color: #1e40af; }

/* A fieldset used only to switch a whole form off at once — a read-only
   checklist, say. Browsers give <fieldset> a border, padding and a
   min-width that won't shrink inside a grid; none of that is wanted, and
   the disabled look comes from the controls themselves. */
.rg-fieldset { border: 0; padding: 0; margin: 0; min-width: 0; }

/* ── DVI: upload progress and reading units ──
   The bar is the width of the row so the tech can read it at arm's
   length under a lift, and the label sits on top of it rather than
   beside it, because a phone in portrait has no room for both. */
.smp-dvi-progress {
  position: relative;
  height: 22px;
  margin: 6px 0 2px;
  border-radius: 6px;
  background: rgba(0,0,0,.07);
  overflow: hidden;
}
.smp-dvi-progress-bar {
  height: 100%;
  background: var(--rg-accent);
  /* Not too smooth: the bar should look like it is reporting, not
     animating on its own after the connection has stalled. */
  transition: width .15s linear;
}
.smp-dvi-progress-pct {
  position: absolute;
  inset: 0;
  display: flex;
  align-items: center;
  justify-content: center;
  font-size: .7rem;
  font-weight: 700;
  color: #1f2937;
  /* Legible over both the filled and unfilled halves. */
  text-shadow: 0 0 3px rgba(255,255,255,.85);
}

/* The customer's answers, rolled up on the inspection header. Accent
   tint rather than a grade colour: it is a status about the conversation,
   not a judgment about the car. */
.rg-dvi-decisions {
  margin-bottom: 8px;
  padding: 7px 10px;
  border-radius: 8px;
  background: rgba(37,99,235,.07);
  border: 1px solid rgba(37,99,235,.18);
  font-size: .8rem;
  font-weight: 600;
  color: #1e40af;
}

/* Writing a note where the technician left none. Dashed, because it is
   an empty slot asking to be filled rather than a control that acts. */
.rg-dvi-addnote {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  margin-top: 6px;
  padding: 5px 10px;
  border: 1px dashed var(--rg-border, #d1d5db);
  border-radius: 8px;
  background: transparent;
  color: var(--rg-muted, #6b7280);
  font-size: .78rem;
  font-weight: 600;
}
.rg-dvi-addnote:hover { border-color: var(--rg-accent); color: var(--rg-accent); }

/* ── Move-to picker ──
   A dialog rather than a dropdown, because the list it is choosing from
   is a hundred rows long and needs a search box and room to group. */
.rg-picker {
  position: fixed;
  inset: 0;
  z-index: 2100;
  display: flex;
  align-items: center;
  justify-content: center;
  padding: 20px;
  background: rgba(9,11,16,.55);
}
.rg-picker-box {
  width: 100%;
  max-width: 460px;
  max-height: min(78vh, 640px);
  display: flex;
  flex-direction: column;
  border-radius: 14px;
  background: #fff;
  box-shadow: 0 20px 50px rgba(0,0,0,.3);
  overflow: hidden;
}
.rg-picker-head {
  display: flex;
  align-items: flex-start;
  gap: 10px;
  padding: 14px 14px 10px;
}
.rg-picker-title { font-weight: 700; font-size: .95rem; }
.rg-picker-from  { font-size: .76rem; color: var(--rg-muted, #6b7280); }
.rg-picker-head .rg-icon-btn { margin-left: auto; }

.rg-picker-search {
  position: relative;
  padding: 0 14px 10px;
}
.rg-picker-search i {
  position: absolute;
  left: 24px; top: 9px;
  color: var(--rg-muted, #9ca3af);
  font-size: .8rem;
}
.rg-picker-search .rg-input { padding-left: 30px; }

/* The list scrolls; the search box does not go with it. */
.rg-picker-list {
  flex: 1;
  min-height: 0;
  overflow-y: auto;
  border-top: 1px solid var(--rg-border, #e5e7eb);
}
.rg-picker-cat {
  position: sticky;
  top: 0;
  padding: 7px 14px 4px;
  background: var(--rg-surface, #f9fafb);
  border-bottom: 1px solid var(--rg-border, #e5e7eb);
  font-size: .68rem; font-weight: 700; letter-spacing: .05em;
  text-transform: uppercase; color: var(--rg-muted, #6b7280);
}
.rg-picker-row {
  display: flex;
  align-items: baseline;
  gap: 8px;
  width: 100%;
  padding: 9px 14px;
  border: 0;
  border-bottom: 1px solid var(--rg-border, #f3f4f6);
  background: transparent;
  text-align: left;
}
.rg-picker-row:hover { background: rgba(37,99,235,.06); }
.rg-picker-name    { flex: 1; font-size: .87rem; font-weight: 600; }
.rg-picker-cat-sub { font-size: .68rem; color: var(--rg-muted, #6b7280); }
/* How many photos are already on that row — useful when deciding. */
.rg-picker-n {
  min-width: 18px;
  padding: 0 6px;
  border-radius: 999px;
  background: rgba(0,0,0,.07);
  font-size: .66rem;
  color: var(--rg-muted, #6b7280);
  text-align: center;
}
.rg-picker-empty { padding: 24px 14px; text-align: center; color: var(--rg-muted, #6b7280); font-size: .85rem; }

/* A photo and everything a writer does to it, together. */
.rg-shot { width: 150px; }
/* No fixed height. The column sets the width; the media's own aspect
   ratio sets the height, so a portrait clip is portrait and nothing is
   cropped to fit a box that was picked before anyone knew its shape. */
.rg-shot .rg-dvi-thumb {
  width: 100%;
  height: auto;
  aspect-ratio: 4 / 3;   /* only until the real ratio is known */
}
/* Fill the button entirely — the button is the frame, the media is the
   picture, and neither carries dimensions of its own. */
.rg-shot .rg-dvi-thumb img,
.rg-shot .rg-dvi-thumb video {
  width: 100%;
  height: 100%;
  object-fit: cover;
}
.rg-shot-cap {
  width: 100%;
  margin-top: 4px;
  padding: 4px 7px;
  border: 1px solid var(--rg-border, #e5e7eb);
  border-radius: 6px;
  font-size: .72rem;
  line-height: 1.3;
}
.rg-shot-cap::placeholder { color: #b6bcc6; }
.rg-shot-cap:focus { border-color: var(--rg-accent); outline: none; }
.rg-shot-cap-ro { margin-top: 4px; font-size: .72rem; color: var(--rg-muted, #6b7280); }

.rg-shot-acts { display: flex; gap: 4px; margin-top: 3px; }
.rg-shot-acts button {
  flex: 1;
  padding: 3px 0;
  border: 1px solid var(--rg-border, #e5e7eb);
  border-radius: 6px;
  background: #fff;
  color: var(--rg-muted, #6b7280);
  font-size: .68rem;
}
.rg-shot-acts button:hover { color: var(--rg-accent); border-color: var(--rg-accent); }
.rg-shot-del:hover { color: var(--rg-danger, #dc2626) !important; border-color: var(--rg-danger, #dc2626) !important; }


/* Office media tools, inside the viewer: caption, move, delete. Laid on
   the dark stage, so the controls are light. */
.rg-media-tools {
  display: flex;
  gap: 8px;
  align-items: center;
  margin-top: 8px;
}
.rg-media-cap {
  flex: 1;
  min-width: 0;
  background: rgba(255,255,255,.08);
  border-color: rgba(255,255,255,.2);
  color: #f3f4f6;
}
.rg-media-cap::placeholder { color: rgba(255,255,255,.45); }
.rg-media-tools .rg-btn-ghost { color: #e5e7eb; border-color: rgba(255,255,255,.22); flex: 0 0 auto; }
.rg-media-del { color: #fca5a5 !important; }

/* Trim controls, on the dark viewer stage beneath the video. */
.rg-media-trim { margin-top: 10px; }
.rg-media-trim .rg-btn-ghost { color: #e5e7eb; border-color: rgba(255,255,255,.22); }
.rg-media-trim-acts { display: flex; gap: 8px; margin-top: 10px; }
.rg-media-trim-acts .rg-btn-primary { flex: 1; }

/* ── The trim timeline ──
   On the viewer's dark stage. The bar is the whole clip; the lit region
   is what survives. Generous height because the handles are dragged, and
   a 6px target is a target nobody hits on the first go. */
.rg-trim { user-select: none; }
.rg-trim-times {
  display: flex;
  justify-content: space-between;
  margin-bottom: 5px;
  font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
  font-size: .72rem;
  color: rgba(255,255,255,.65);
}
.rg-trim-now { color: #fff; font-weight: 700; }

.rg-trim-bar {
  position: relative;
  height: 34px;
  border-radius: 8px;
  background: rgba(255,255,255,.12);
  cursor: pointer;
  touch-action: none;      /* or a drag scrolls the page on a tablet */
}
.rg-trim-keep {
  position: absolute;
  top: 0; bottom: 0;
  background: rgba(37,99,235,.45);
  border-left: 2px solid #60a5fa;
  border-right: 2px solid #60a5fa;
  pointer-events: none;
}
/* Where the video actually is. Thin and bright — it moves, so it does
   not need to be big to be found. */
.rg-trim-head {
  position: absolute;
  top: -3px; bottom: -3px;
  width: 2px;
  margin-left: -1px;
  background: #fff;
  box-shadow: 0 0 6px rgba(0,0,0,.6);
  pointer-events: none;
}
.rg-trim-grip {
  position: absolute;
  top: 50%;
  width: 22px; height: 42px;
  margin: -21px 0 0 -11px;
  border: 0;
  border-radius: 6px;
  background: #60a5fa;
  box-shadow: 0 1px 4px rgba(0,0,0,.4);
  cursor: ew-resize;
  touch-action: none;
}
/* The two grooves that say "this is a thing you drag". */
.rg-trim-grip::after {
  content: '';
  position: absolute;
  inset: 13px 8px;
  border-left: 1px solid rgba(0,0,0,.35);
  border-right: 1px solid rgba(0,0,0,.35);
}
.rg-trim-hint {
  margin-top: 6px;
  font-size: .7rem;
  color: rgba(255,255,255,.5);
}

/* One waiting inspection, on the dashboard. A whole row is the target,
   because the point is to get somebody into it in one click. */
.smp-dviq-row {
  display: flex;
  align-items: center;
  gap: 10px;
  padding: 11px 14px;
  border-top: 1px solid var(--rg-border, #e5e7eb);
  color: inherit;
  text-decoration: none;
}
.smp-dviq-row:first-child { border-top: 0; }
.smp-dviq-row:hover       { background: var(--rg-surface, #f9fafb); }
.smp-dviq-title           { font-size: .9rem; font-weight: 600; }

/* A note that failed to save. Loud enough to notice, because the
   alternative is a tech believing they wrote something down. */
.smp-note-failed {
  display: flex;
  align-items: center;
  gap: 7px;
  width: 100%;
  margin-top: 5px;
  padding: 7px 10px;
  border: 1px solid rgba(220,38,38,.3);
  border-radius: 8px;
  background: rgba(220,38,38,.07);
  color: #991b1b;
  font-size: .78rem;
  font-weight: 600;
  text-align: left;
}

/* What's left, under the submit button. Amber rather than red: this is
   work outstanding, not a failure — the tech has not done anything wrong
   by not having finished yet. */
.smp-left {
  margin-top: 10px;
  border: 1px solid rgba(217,119,6,.28);
  border-radius: 10px;
  background: rgba(217,119,6,.06);
  overflow: hidden;
}
.smp-left-head {
  padding: 9px 12px;
  font-size: .82rem;
  font-weight: 700;
  color: #92400e;
}
.smp-left-row {
  display: flex;
  align-items: center;
  gap: 8px;
  width: 100%;
  padding: 10px 12px;
  border: 0;
  border-top: 1px solid rgba(217,119,6,.18);
  background: transparent;
  text-align: left;
  font-size: .88rem;
  font-weight: 600;
  color: #1f2937;
}
.smp-left-row:active { background: rgba(217,119,6,.12); }
.smp-left-pin   { color: #b45309; font-size: .78rem; }
.smp-left-count {
  min-width: 22px;
  padding: 1px 7px;
  border-radius: 999px;
  background: rgba(217,119,6,.18);
  color: #92400e;
  font-size: .74rem;
  font-variant-numeric: tabular-nums;
  text-align: center;
}

/* The progress pill doubles as "take me back to where I stopped". Looks
   like the pill it replaced until there is somewhere to go. */
.smp-resume { border: 0; font: inherit; cursor: pointer; }
.smp-resume:disabled { cursor: default; opacity: 1; }

/* The door placard, before anything is graded. */
.smp-placard {
  margin-bottom: 14px;
  padding: 14px;
  border: 1px solid var(--rg-border, #e5e7eb);
  border-left: 3px solid var(--rg-accent);
  border-radius: 12px;
  background: #fff;
}
/* The figure once it is set — a line, not a panel. It has to be visible
   (every tyre on the car is judged against it) without taking the room a
   form takes, and it has to carry a way back in. */
.smp-placard-set {
  display: flex;
  align-items: center;
  gap: 10px;
  margin: 0 0 12px;
  padding: 8px 12px;
  border: 1px solid var(--rg-border);
  border-radius: 10px;
  background: var(--rg-surface);
  font-size: .85rem;
}
.smp-placard-set i { color: var(--rg-muted); }

/* Said once after a correction, rather than re-prompting about readings
   already taken. */
.smp-placard-stale {
  margin: -6px 0 12px;
  padding: 8px 12px;
  border-radius: 10px;
  background: rgba(217,119,6,.12);
  color: #92400e;
  font-size: .82rem;
  font-weight: 600;
}

.smp-placard-head { font-weight: 700; margin-bottom: 4px; }
.smp-placard-why  { font-size: .82rem; color: var(--rg-muted, #6b7280); margin: 0 0 10px; }
.smp-placard-row  { display: flex; gap: 10px; }
.smp-placard-row label { flex: 1; }
.smp-placard-row span {
  display: block;
  font-size: .74rem; font-weight: 600;
  color: var(--rg-muted, #6b7280);
  margin-bottom: 3px;
}
.smp-placard-row em { font-style: normal; font-weight: 400; opacity: .75; }
.smp-placard-row .rg-input { text-align: center; font-size: 1.15rem; font-weight: 700; }

/* ── The correction prompt ──
   Over everything, because it is asked at the moment the tech is standing
   at that wheel with a gauge — and five rows later they are under the car
   and it does not get done. */
.smp-fix {
  position: fixed;
  inset: 0;
  z-index: 2200;
  display: flex;
  align-items: flex-end;
  justify-content: center;
  background: rgba(9,11,16,.6);
}
.smp-fix-box {
  width: 100%;
  max-width: 460px;
  padding: 18px 16px calc(18px + env(safe-area-inset-bottom, 0px));
  border-radius: 16px 16px 0 0;
  background: #fff;
}
.smp-fix-head { font-weight: 800; font-size: 1.02rem; margin-bottom: 12px; }
.smp-fix-nums {
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 18px;
  margin-bottom: 12px;
}
.smp-fix-nums > div { text-align: center; }
.smp-fix-n {
  display: block;
  font-size: 2rem; font-weight: 800; line-height: 1;
  font-variant-numeric: tabular-nums;
  color: #b45309;
}
.smp-fix-want { color: #15803d; }
.smp-fix-lab {
  font-size: .68rem; font-weight: 700; letter-spacing: .06em;
  text-transform: uppercase; color: var(--rg-muted, #6b7280);
}
.smp-fix-arrow { color: var(--rg-muted, #9ca3af); }
.smp-fix-say { font-size: .86rem; line-height: 1.45; margin-bottom: 14px; }

/* The tool list, before the first grade. Card rather than a modal: a
   modal on a phone wants dismissing before it can be read properly, and
   this is a list somebody may want to keep looking at on the walk back
   from the toolbox. */
.smp-intro {
  margin-bottom: 14px;
  padding: 14px;
  border: 1px solid var(--rg-border, #e5e7eb);
  border-left: 3px solid var(--rg-accent);
  border-radius: 12px;
  background: #fff;
}
.smp-intro-head { font-weight: 700; margin-bottom: 8px; }
.smp-intro-body p  { margin: 0 0 8px; font-size: .86rem; }
.smp-intro-body ul { list-style: none; margin: 0 0 10px; padding: 0; }
.smp-intro-body li {
  display: flex; align-items: center; gap: 9px;
  padding: 6px 0;
  font-size: .9rem; font-weight: 600;
}
.smp-intro-body li i { width: 18px; text-align: center; color: var(--rg-accent); }
.smp-intro-note {
  padding: 8px 10px;
  border-radius: 8px;
  background: rgba(0,0,0,.04);
  font-size: .8rem !important;
  color: var(--rg-muted, #6b7280);
}

/* Torch, in the checklist bar. Lit when on, so a lamp left burning is
   visible rather than just hot. */
.smp-torch {
  width: 36px; height: 36px;
  border: 1px solid transparent;
  border-radius: 9px;
  background: transparent;
  color: rgba(255,255,255,.75);
}
.smp-torch.on {
  background: #fde68a;
  border-color: #fcd34d;
  color: #78350f;
}

/* A scope with no place on the car — inner/outer pad. Same tap target
   as the lamp buttons so the two read as the same kind of answer. */
.smp-plainscope { display: flex; gap: 8px; margin-top: 6px; }
.smp-plainscope-opt {
  flex: 1;
  padding: 10px 8px;
  border: 1px solid var(--rg-border, #e5e7eb);
  border-radius: 9px;
  background: #fff;
  font-size: .85rem;
  font-weight: 600;
}
.smp-plainscope-opt.active {
  border-color: var(--rg-accent);
  background: rgba(37,99,235,.1);
  color: var(--rg-accent);
}

/* Rotors or drums, struts or shocks — asked on the item, answered for
   the axle. Above the grades because it changes what the grade is about. */
.smp-variant {
  display: flex;
  align-items: center;
  gap: 6px;
  margin-bottom: 7px;
}
.smp-variant-q {
  font-size: .72rem;
  font-weight: 600;
  color: var(--rg-muted, #6b7280);
}
.smp-variant button {
  padding: 5px 12px;
  border: 1px solid var(--rg-border, #e5e7eb);
  border-radius: 999px;
  background: #fff;
  font-size: .8rem;
  font-weight: 600;
}
.smp-variant button.active {
  border-color: var(--rg-accent);
  background: rgba(37,99,235,.1);
  color: var(--rg-accent);
}

/* The hint: what to look at, under the item's name. Quiet enough to
   ignore once you know the job, present enough to read when you don't. */
.smp-dvi-hint {
  display: block;
  margin-top: 3px;
  font-size: .72rem;
  font-weight: 400;
  line-height: 1.35;
  color: var(--rg-muted, #6b7280);
}

/* ── Full-screen media viewer ──
   Fixed over everything, dark, because the thing being looked at is a
   photograph taken under a car and every pixel of surrounding chrome is
   competing with it. */
.smp-viewer {
  position: fixed;
  inset: 0;
  z-index: 2000;
  display: flex;
  flex-direction: column;
  background: rgba(9,11,16,.96);
}
.smp-viewer-bar {
  display: flex;
  align-items: center;
  gap: 10px;
  padding: 10px 12px;
  color: #e5e7eb;
  font-size: .82rem;
  /* Clear of a phone's notch. */
  padding-top: calc(10px + env(safe-area-inset-top, 0px));
}
.smp-viewer-count { font-variant-numeric: tabular-nums; opacity: .65; }
.smp-viewer-item  { font-weight: 600; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.smp-viewer-close {
  margin-left: auto;
  width: 34px; height: 34px;
  border: 0; border-radius: 8px;
  background: rgba(255,255,255,.1);
  color: #fff;
}
.smp-viewer-stage {
  flex: 1;
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 6px;
  min-height: 0;
  padding: 0 6px;
}
/* Fit, never crop: a cropped inspection photo is a photo of the wrong
   thing. */
.smp-viewer-stage img,
.smp-viewer-stage video {
  max-width: 100%;
  max-height: 100%;
  object-fit: contain;
}
.smp-viewer-step {
  flex: 0 0 auto;
  width: 40px; height: 64px;
  border: 0; border-radius: 8px;
  background: rgba(255,255,255,.08);
  color: #fff;
}
.smp-viewer-acts {
  display: flex;
  gap: 8px;
  justify-content: center;
  padding: 10px 12px calc(10px + env(safe-area-inset-bottom, 0px));
}
.smp-viewer-acts .rg-btn-ghost { color: #e5e7eb; border-color: rgba(255,255,255,.22); }
.smp-viewer-del { color: #fca5a5 !important; }

/* The move list. Scrolls, because a checklist has a hundred rows. */
.smp-viewer-move {
  max-height: 46vh;
  overflow-y: auto;
  background: #fff;
  border-radius: 12px 12px 0 0;
  padding-bottom: env(safe-area-inset-bottom, 0px);
}
.smp-viewer-move-head {
  padding: 12px 14px 6px;
  font-size: .78rem;
  font-weight: 700;
  color: var(--rg-muted, #6b7280);
  text-transform: uppercase;
  letter-spacing: .04em;
}
.smp-viewer-move-row {
  display: block;
  width: 100%;
  padding: 11px 14px;
  border: 0;
  border-top: 1px solid var(--rg-border, #e5e7eb);
  background: transparent;
  text-align: left;
  font-size: .88rem;
}
.smp-viewer-move-cat {
  display: block;
  font-size: .68rem;
  color: var(--rg-muted, #6b7280);
}

/* A still frame of a video looks exactly like a photo without this. */
.smp-dvi-thumb { position: relative; cursor: pointer; }
.smp-dvi-thumb-play {
  position: absolute;
  inset: 0;
  display: flex;
  align-items: center;
  justify-content: center;
  color: #fff;
  font-size: .8rem;
  text-shadow: 0 1px 4px rgba(0,0,0,.6);
  pointer-events: none;
}

/* The unit, next to the item name — quieter than the name, because it
   is a qualifier and not the thing being read. */
.smp-dvi-unit {
  margin-left: 5px;
  padding: 1px 5px;
  border-radius: 4px;
  background: rgba(0,0,0,.06);
  font-size: .66rem;
  font-weight: 600;
  color: var(--rg-muted, #6b7280);
  white-space: nowrap;
}
.rg-fieldset:disabled { opacity: .72; }

/* ── "Do they want text updates?" ──
   Louder than the alerts above it, on purpose. It's one question, asked
   once in the life of a customer, and every future "your car is ready"
   depends on the answer — but it's also only worth asking while they're
   stood at the counter, so it has to survive being seen every day
   without being learned as scenery. A card with a filled icon and a real
   primary button reads as a task; a tinted strip reads as a notice, and
   service writers scroll past notices. */
.rg-sms-ask {
  display: flex; align-items: center; gap: 14px;
  margin-bottom: 12px; padding: 14px 16px;
  border: 1px solid rgba(37,99,235,.35);
  border-left: 4px solid var(--rg-accent);
  border-radius: 10px;
  background: linear-gradient(rgba(37,99,235,.09), rgba(37,99,235,.045));
  box-shadow: 0 1px 3px rgba(15,23,42,.06);
}
.rg-sms-ask-icon {
  display: flex; align-items: center; justify-content: center;
  flex: none; width: 40px; height: 40px;
  border-radius: 10px;
  background: var(--rg-accent);
  color: #fff; font-size: 1.1rem;
}
.rg-sms-ask-body { flex: 1 1 auto; min-width: 0; }
.rg-sms-ask-title { font-size: .95rem; font-weight: 700; color: var(--rg-text); }
.rg-sms-ask-note { margin-top: 2px; font-size: .8rem; color: var(--rg-muted); }
.rg-sms-ask-actions { display: flex; align-items: center; gap: 8px; flex: none; }
.rg-sms-ask-actions .rg-btn-primary,
.rg-sms-ask-actions .rg-btn-ghost { white-space: nowrap; }

/* Sent, waiting on them. There's nothing to do but wait, so it steps
   back to grey and keeps only the button for when the text plainly
   didn't land. */
.rg-sms-ask-pending {
  border-color: var(--rg-border);
  border-left-color: var(--rg-muted);
  background: var(--rg-bg);
}
.rg-sms-ask-pending .rg-sms-ask-icon { background: var(--rg-muted); }

/* On a phone the buttons go under the question rather than shrinking it
   to a column of single words. */
@media (max-width: 767px) {
  .rg-sms-ask { flex-wrap: wrap; }
  .rg-sms-ask-actions { width: 100%; }
  .rg-sms-ask-actions .rg-btn-primary { flex: 1 1 auto; justify-content: center; }
}

/* ── A message somebody held, over the conversation ──
   Amber, and not the blue of the question above it or the red the app
   keeps for mistakes: nothing has gone wrong, somebody made a decision on
   purpose, and this is the reminder they asked for by making it. Lives
   here and nowhere else — the conversation is where a message the
   customer hasn't had belongs.

   It floats over the thread rather than sitting in the flow above it, so
   it reads as an interruption rather than as one more card the eye
   learns to slide past — and so the conversation underneath keeps the
   same shape whether something is held or not.

   A 360px column has no room for an icon tile and a row of buttons, so
   it stacks. */
.rg-thread-wrap { position: relative; }

/* The room the popup floats in. A thread with two bubbles in it — or
   none at all — is shorter than the thing hovering over it, and without
   a floor the message spills onto the compose box below. Sized to the
   popup's own ceiling: header, note, the capped preview, and the
   button. */
.rg-held-room        { min-height: 320px; }
.rg-held-room-folded { min-height: 96px; }

.rg-held {
  padding: 11px 13px;
  border: 1px solid rgba(180,83,9,.35);
  border-left: 3px solid var(--rg-warn);
  border-radius: 9px;
  /* Opaque, not the .07 tint it used to be: it has messages behind it
     now, and a translucent panel over a conversation is unreadable. */
  background: #fffbeb;
}

.rg-held-pop {
  position: absolute;
  top: 0; left: 0; right: 0;
  z-index: 3;
  box-shadow: 0 8px 24px rgba(15,23,42,.16), 0 2px 6px rgba(15,23,42,.08);
}

/* The whole header is the fold toggle — a caret on its own is a 4mm
   target on a tablet at a counter. Reset from <button> to look like the
   heading it is. */
.rg-held-title {
  display: flex; align-items: center; gap: 8px;
  width: 100%;
  padding: 0; border: 0; background: none;
  font: 700 .85rem/1.4 inherit;
  color: var(--rg-text);
  text-align: left;
  cursor: pointer;
}
.rg-held-title > i:first-child { color: var(--rg-warn); font-size: .8rem; }
.rg-held-title > span { flex: 1 1 auto; min-width: 0; }
.rg-held-caret { flex: none; color: var(--rg-muted); font-size: .7rem; }

.rg-held-note { margin-top: 4px; font-size: .78rem; color: var(--rg-muted); }

/* Capped and scrolled. This is floating over the conversation, and a
   status email is long enough to bury the whole thread if it were let
   run to its natural height. */
.rg-held-preview {
  max-height: 170px;
  overflow-y: auto;
  margin-top: 2px;
}

.rg-held-actions { display: flex; align-items: center; gap: 8px; margin-top: 10px; }
.rg-held-actions .rg-btn-primary { flex: 1 1 auto; justify-content: center; }

/* Folded: one line and the button, so somebody can read the thread
   behind it without the message ceasing to exist. Getting it out of the
   way and making it go away are different things, and only one of them
   is on offer. */
.rg-held-folded { padding-bottom: 9px; }
.rg-held-folded .rg-held-actions { margin-top: 8px; }

/* ── The message itself, before it goes ──
   Used in two places and styled once: the dialog that asks before a
   status change, and the card holding one that was declined. They show
   the same thing and must look like the same thing, or the card stops
   reading as "the message you saw earlier".

   Bordered and set on the surface colour so it reads as a quotation of
   something else rather than as more of the page's own prose. */
.rg-preview-msg {
  margin-top: 10px;
  border: 1px solid var(--rg-border);
  border-radius: 8px;
  background: var(--rg-surface);
  overflow: hidden;
}
.rg-preview-head {
  display: flex; align-items: center; gap: 7px;
  padding: 6px 10px;
  border-bottom: 1px solid var(--rg-border);
  background: var(--rg-bg);
  font-size: .7rem; font-weight: 700;
  text-transform: uppercase; letter-spacing: .04em;
  color: var(--rg-muted);
}
/* A long address truncates rather than pushing the icon off the row. */
.rg-preview-head span {
  min-width: 0;
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.rg-preview-head i { flex: none; font-size: .75rem; }

.rg-preview-subject {
  padding: 8px 10px 0;
  font-size: .82rem; font-weight: 700;
}
/* pre-wrap, so the paragraph breaks in the template survive — a status
   email read as one run-on block is not the email that will arrive. */
.rg-preview-body {
  padding: 6px 10px 9px;
  font-size: .82rem; line-height: 1.5;
  white-space: pre-wrap;
  color: var(--rg-text);
}

/* The unsubscribe line, set the way the email sets it: smaller, greyer,
   above a rule. It reads as the footer it is rather than as another
   sentence of the message — and it is deliberately not styled as a link,
   because it isn't one here. */
.rg-preview-footer {
  margin: 0 10px;
  padding: 8px 0 9px;
  border-top: 1px solid var(--rg-border);
  font-size: .72rem;
  line-height: 1.45;
  color: var(--rg-muted);
}

/* In the dialog there may be two of these and a question above them, on
   a screen that also has a keyboard open. Scroll the preview rather than
   the dialog: the buttons must stay reachable. */
.rg-dialog-preview {
  margin-top: 10px;
  max-height: 40vh;
  overflow-y: auto;
}
.rg-dialog-preview .rg-preview-msg:first-child { margin-top: 0; }

/* ── Definition rows ── */
.rg-def { display: flex; justify-content: space-between; gap: 12px; padding: 9px 0; border-bottom: 1px solid var(--rg-border); font-size: .875rem; }
.rg-def:last-child { border-bottom: none; }
.rg-def-key { color: var(--rg-muted); }
.rg-def-val { font-weight: 600; text-align: right; }

/* ============================================================
   NOTIFICATIONS
   The bell in the shop nav, its panel, and the toast for things
   that land while someone is already looking at the screen.
   ============================================================ */
/* Sits between the links (which take the slack) and the New Order CTA. */
.smp-bell-wrap { position: relative; display: flex; align-items: center; flex-shrink: 0; }

.smp-bell {
  position: relative;
  display: flex;
  align-items: center;
  justify-content: center;
  width: 36px; height: 36px;
  border: 1px solid transparent;
  border-radius: 9px;
  background: transparent;
  color: rgba(255,255,255,.7);
  font-size: .95rem;
  cursor: pointer;
  transition: background .12s, color .12s, border-color .12s;
}
.smp-bell:hover      { background: rgba(255,255,255,.08); color: #fff; }
.smp-bell.open       { background: rgba(255,255,255,.12); color: #fff; border-color: rgba(255,255,255,.18); }

/* Unread count. Sized to sit on the bell without covering it. */
.smp-bell-count {
  position: absolute;
  top: 2px; right: 0;
  min-width: 17px;
  padding: 0 4px;
  border-radius: 9px;
  background: var(--rg-danger);
  color: #fff;
  font-size: .62rem;
  font-weight: 700;
  line-height: 17px;
  text-align: center;
}

/* Catches the click that closes the panel, without a document listener
   that would fight AngularJS's digest. */
.smp-bell-backdrop { position: fixed; inset: 0; z-index: 1040; }

/* Fixed, not anchored to the bell: the same panel has to work from the
   desktop nav and from the phone's More sheet, where the bell isn't on
   screen at all. */
.smp-bell-panel {
  position: fixed;
  top: calc(60px + var(--safe-t));
  right: calc(14px + var(--safe-r));
  z-index: 1050;
  width: 380px;
  max-width: calc(100vw - 24px);
  background: var(--rg-surface);
  border: 1px solid var(--rg-border);
  border-radius: 12px;
  box-shadow: 0 16px 44px rgba(16,24,40,.18);
  overflow: hidden;
}
.smp-bell-head {
  display: flex;
  align-items: center;
  gap: 4px;
  padding: 12px 14px;
  border-bottom: 1px solid var(--rg-border);
  font-size: .78rem;
  font-weight: 700;
  text-transform: uppercase;
  letter-spacing: .04em;
  color: var(--rg-muted);
}
.smp-bell-clear {
  border: 0;
  background: transparent;
  color: var(--rg-accent);
  font-family: inherit;
  font-size: .72rem;
  font-weight: 600;
  text-transform: none;
  letter-spacing: 0;
  cursor: pointer;
}

/* Sits between the title and "Mark all read", quiet until hovered — it
   is a setting you reach for twice a year. */
.smp-bell-mute {
  margin-left: auto;
  margin-right: 10px;
  background: none;
  border: 0;
  padding: 2px 4px;
  color: var(--rg-muted);
  cursor: pointer;
  font-size: .8rem;
}
.smp-bell-mute:hover { color: var(--rg-text); }
.smp-bell-clear:hover { text-decoration: underline; }

.smp-bell-list { max-height: min(60vh, 460px); overflow-y: auto; }

.smp-bell-item {
  display: flex;
  align-items: flex-start;
  gap: 11px;
  width: 100%;
  padding: 12px 14px;
  border: 0;
  border-bottom: 1px solid var(--rg-border);
  background: transparent;
  text-align: left;
  font-family: inherit;
  cursor: pointer;
}
.smp-bell-item:last-child { border-bottom: 0; }
.smp-bell-item:hover      { background: var(--rg-bg); }
.smp-bell-item.unread     { background: rgba(37,99,235,.04); }

.smp-bell-icon {
  display: flex;
  align-items: center;
  justify-content: center;
  flex: 0 0 auto;
  width: 30px; height: 30px;
  border-radius: 8px;
  background: rgba(37,99,235,.1);
  color: var(--rg-accent);
  font-size: .76rem;
}
.smp-bell-text  { display: flex; flex-direction: column; gap: 2px; min-width: 0; flex: 1 1 auto; }
.smp-bell-title { font-size: .84rem; font-weight: 600; color: var(--rg-text); }
.smp-bell-body  {
  font-size: .78rem;
  color: var(--rg-muted);
  /* Two lines is enough to know whether to walk over; the rest is on
     the repair order. */
  display: -webkit-box;
  -webkit-line-clamp: 2;
  -webkit-box-orient: vertical;
  overflow: hidden;
}
.smp-bell-when  { font-size: .7rem; color: var(--rg-muted); opacity: .8; }
.smp-bell-dot   {
  flex: 0 0 auto;
  width: 8px; height: 8px;
  margin-top: 6px;
  border-radius: 50%;
  background: var(--rg-accent);
}

.smp-bell-empty {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 8px;
  padding: 32px 16px;
  color: var(--rg-muted);
  font-size: .82rem;
}
.smp-bell-empty i { font-size: 1.3rem; opacity: .5; }

.smp-bell-foot { padding: 10px; border-top: 1px solid var(--rg-border); background: var(--rg-bg); }
.smp-bell-enable {
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 8px;
  width: 100%;
  padding: 9px;
  border: 1px solid var(--rg-border);
  border-radius: 8px;
  background: var(--rg-surface);
  color: var(--rg-text);
  font-family: inherit;
  font-size: .78rem;
  font-weight: 600;
  cursor: pointer;
}
.smp-bell-enable:hover:not(:disabled) { border-color: var(--rg-accent); color: var(--rg-accent); }
.smp-bell-enable:disabled { opacity: .6; cursor: default; }

/* ── Toast ── */
.smp-toast {
  position: fixed;
  z-index: 1060;
  right: calc(18px + var(--safe-r));
  bottom: calc(18px + var(--safe-b));
  display: flex;
  align-items: flex-start;
  gap: 11px;
  width: 340px;
  max-width: calc(100vw - 32px);
  padding: 13px 14px;
  border: 1px solid var(--rg-border);
  border-left: 3px solid var(--rg-accent);
  border-radius: 11px;
  box-shadow: 0 12px 32px rgba(16,24,40,.18);
  cursor: pointer;
  animation: smp-toast-in .22s ease-out;
}
@keyframes smp-toast-in {
  from { opacity: 0; transform: translateY(10px); }
  to   { opacity: 1; transform: none; }
}
.smp-toast-icon {
  display: flex;
  align-items: center;
  justify-content: center;
  flex: 0 0 auto;
  width: 30px; height: 30px;
  border-radius: 8px;
  background: rgba(37,99,235,.1);
  color: var(--rg-accent);
  font-size: .78rem;
}
.smp-toast-text  { display: flex; flex-direction: column; gap: 2px; min-width: 0; flex: 1 1 auto; }
.smp-toast-title { font-size: .84rem; font-weight: 600; color: var(--rg-text); }
.smp-toast-body  {
  font-size: .78rem;
  color: var(--rg-muted);
  display: -webkit-box;
  -webkit-line-clamp: 2;
  -webkit-box-orient: vertical;
  overflow: hidden;
}
.smp-toast-x {
  flex: 0 0 auto;
  border: 0;
  background: transparent;
  color: var(--rg-muted);
  font-size: .8rem;
  cursor: pointer;
  padding: 2px 4px;
}
.smp-toast-x:hover { color: var(--rg-text); }

/* Clear of the mobile bottom nav. */
/* Above the nav AND above the floating new-order button, which now owns
   this corner. Full width rather than a 340px card hugging one edge: on
   a phone a toast that covers the button is a toast that eats the tap
   aimed at it. */
@media (max-width: 991.98px) {
  .smp-toast {
    left: calc(12px + var(--safe-l));
    right: calc(12px + var(--safe-r));
    bottom: calc(var(--nav-h) + var(--safe-b) + 84px);
    width: auto;
    max-width: none;
  }
}

/* ── Portal: work handed over, and the push offer ──
   Same accent-tint recipe as .rg-alert-info / the office bell items. */
.smp-newjob {
  display: flex;
  align-items: flex-start;
  gap: 12px;
  padding: 14px;
  margin-bottom: 10px;
  background: rgba(37,99,235,.07);
  border: 1px solid rgba(37,99,235,.25);
  border-radius: var(--rg-radius);
  cursor: pointer;
}
.smp-newjob:active { background: rgba(37,99,235,.12); }
.smp-newjob-icon {
  display: flex;
  align-items: center;
  justify-content: center;
  flex: 0 0 auto;
  width: 36px; height: 36px;
  border-radius: 10px;
  background: var(--rg-accent);
  color: #fff;
  font-size: .85rem;
}
.smp-newjob-text  { display: flex; flex-direction: column; gap: 3px; min-width: 0; flex: 1 1 auto; }
.smp-newjob-title { font-size: .95rem; font-weight: 700; color: var(--rg-text); }
.smp-newjob-body  { font-size: .85rem; color: var(--rg-muted); }
.smp-newjob-when  { font-size: .74rem; color: var(--rg-muted); }
.smp-newjob-x {
  flex: 0 0 auto;
  /* A thumb target, not an icon: this is dismissed one-handed. */
  width: 34px; height: 34px;
  border: 0;
  border-radius: 8px;
  background: transparent;
  color: var(--rg-muted);
  font-size: .95rem;
}
.smp-newjob-x:active { background: var(--rg-bg); color: var(--rg-text); }

.smp-push-ask {
  display: flex;
  align-items: center;
  gap: 10px;
  width: 100%;
  padding: 13px 14px;
  margin-bottom: 10px;
  border: 1px dashed var(--rg-border);
  border-radius: var(--rg-radius);
  background: transparent;
  color: var(--rg-muted);
  font-family: inherit;
  font-size: .85rem;
  text-align: left;
}
.smp-push-ask i      { color: var(--rg-accent); }
.smp-push-ask:active { background: var(--rg-bg); }

/* Under the bell that opened it. It used to rise from the bottom, which
   was right when the only way in was the More sheet down there — the
   bell is on the app bar now, and a panel that opens at the opposite end
   of the screen from the thing you pressed reads as a different feature. */
@media (max-width: 991.98px) {
  .smp-bell-panel {
    top: calc(var(--safe-t) + 56px);
    bottom: auto;
    left: calc(8px + var(--safe-l));
    right: calc(8px + var(--safe-r));
    width: auto;
    max-width: none;
  }
  .smp-bell-list { max-height: 62vh; }
}

/* Unread count on the More sheet's Activity row. */
.rg-sheet-badge {
  margin-left: auto;
  min-width: 20px;
  padding: 1px 6px;
  border-radius: 10px;
  background: var(--rg-danger);
  color: #fff;
  font-size: .68rem;
  font-weight: 700;
  text-align: center;
}

/* Its own row at the foot of the mobile sheet. */
.rg-sheet-signout { margin-top: 6px; color: var(--rg-danger) !important; }
.rg-sheet-signout i { color: var(--rg-danger) !important; }

/* ============================================================
   SETTINGS HUB
   ============================================================ */
.rg-hub {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(320px, 1fr));
  gap: 12px;
}
.rg-hub-card {
  display: flex;
  align-items: flex-start;
  gap: 13px;
  padding: 16px;
  background: var(--rg-surface);
  border: 1px solid var(--rg-border);
  border-radius: var(--rg-radius);
  box-shadow: var(--rg-shadow);
  text-decoration: none;
  color: inherit;
  transition: border-color .12s, box-shadow .12s, transform .1s;
}
.rg-hub-card:hover {
  border-color: var(--rg-accent);
  box-shadow: var(--rg-shadow-md);
  transform: translateY(-1px);
  color: inherit;
}
.rg-hub-icon {
  display: flex;
  align-items: center;
  justify-content: center;
  flex: 0 0 auto;
  width: 40px; height: 40px;
  border-radius: 10px;
  background: rgba(37,99,235,.1);
  color: var(--rg-accent);
  font-size: .95rem;
}
.rg-hub-text { display: flex; flex-direction: column; gap: 3px; min-width: 0; flex: 1 1 auto; }
.rg-hub-name-row { display: flex; align-items: center; gap: 7px; }
.rg-hub-name { font-size: .92rem; font-weight: 700; color: var(--rg-text); }
.rg-hub-desc { font-size: .8rem; color: var(--rg-muted); line-height: 1.5; }
.rg-hub-go   { align-self: center; color: var(--rg-muted); font-size: .75rem; }
.rg-hub-card:hover .rg-hub-go { color: var(--rg-accent); }

/* Owner-only tiles get a warm tint instead of the default blue — a
   visual cue that this one belongs to the account above everyone
   else's, not just another item on the same list. */
.rg-hub-icon-owner { background: rgba(217,119,6,.12); color: var(--rg-warn); }

/* Settings hub: each group of tiles sits inside a normal .rg-card,
   the same "section of the app" language used everywhere else, rather
   than a bare heading floating over a loose grid. Stretched to equal
   height side by side so a short group doesn't look like an afterthought
   next to a tall one. */
.rg-settings-row > [class*="col-"] { display: flex; margin-bottom: 14px; }
.rg-settings-row .rg-card { flex: 1 1 auto; }
.rg-settings-row .rg-hub { grid-template-columns: 1fr; }

.rg-hub-note {
  display: flex;
  align-items: flex-start;
  gap: 9px;
  font-size: .8rem;
  color: var(--rg-muted);
}
.rg-hub-note i { margin-top: 2px; }

/* ── Account card ── */
.rg-account-card {
  display: flex;
  align-items: center;
  gap: 14px;
  padding: 16px;
  background: var(--rg-surface);
  border: 1px solid var(--rg-border);
  border-radius: var(--rg-radius);
  box-shadow: var(--rg-shadow);
}
.rg-account-avatar {
  display: flex;
  align-items: center;
  justify-content: center;
  flex: 0 0 auto;
  width: 44px; height: 44px;
  border-radius: 50%;
  background: var(--rg-accent);
  color: #fff;
  font-size: .9rem;
  font-weight: 700;
}
.rg-account-who   { display: flex; flex-direction: column; gap: 1px; min-width: 0; flex: 1 1 auto; }
.rg-account-name  { font-size: .95rem; font-weight: 700; color: var(--rg-text); }
.rg-account-email { font-size: .8rem; color: var(--rg-muted); }
/* The person behind a company account — a contact worth knowing, but
   secondary to the business name the ticket is actually opened under. */
.rg-account-person { font-size: .8rem; color: var(--rg-muted); }
.rg-account-role  { font-size: .72rem; color: var(--rg-muted); text-transform: uppercase; letter-spacing: .04em; }
.rg-account-roles { display: flex; flex-wrap: wrap; gap: 5px; margin-top: 4px; }

.rg-btn-danger {
  display: inline-flex; align-items: center; gap: 7px;
  flex-shrink: 0;
  height: 36px;
  padding: 0 15px;
  border: 1px solid rgba(220,38,38,.35);
  border-radius: 8px;
  background: rgba(220,38,38,.06);
  color: var(--rg-danger);
  font-family: inherit;
  font-size: .85rem;
  font-weight: 600;
  white-space: nowrap;
  cursor: pointer;
  transition: background .12s, border-color .12s;
}
.rg-btn-danger:hover { background: var(--rg-danger); border-color: var(--rg-danger); color: #fff; }

@media (max-width: 575.98px) {
  .rg-account-card { flex-wrap: wrap; }
  .rg-btn-danger   { width: 100%; justify-content: center; }
}

/* ── Notes that fill the card ──
   The Order card sits beside a taller column, so the leftover space
   below the fields is dead. Handing it to the notes box means long
   notes are readable without scrolling a three-row window. */
.rg-card-fill { display: flex; flex-direction: column; }
.rg-card-fill .rg-card-body {
  display: flex;
  flex-direction: column;
  flex: 1 1 auto;
  /* Without this a flex child refuses to shrink below its content. */
  min-height: 0;
}
.rg-notes-fill {
  display: flex;
  flex-direction: column;
  flex: 1 1 auto;
  min-height: 0;
  margin-top: 16px;
}
/* Element + class, to outrank the general `textarea.rg-input` rule that
   turns the resize handle on. */
textarea.rg-notes-area {
  flex: 1 1 auto;
  /* Enough to be usable when the neighbouring column is short. */
  min-height: 92px;
  /* The handle offered a size the layout already decides. */
  resize: none;
}

/* Back to the settings page. An anchor styled as a ghost button, so it
   sits on the topbar's baseline with the rest of the controls. */
.rg-back {
  text-decoration: none;
  padding: 0 11px;
  color: var(--rg-muted);
}
.rg-back:hover { color: var(--rg-text); }

/* ============================================================
   MONEY
   The payment panel on a repair order, the receivables board,
   and the reports page.
   ============================================================ */

/* The balance is the one number anyone at the counter asks about, so
   it gets the size to match. */
.rg-balance {
  padding: 14px 16px;
  margin-bottom: 12px;
  border-radius: 10px;
  background: linear-gradient(180deg, rgba(37,99,235,.08), rgba(37,99,235,.03));
  border: 1px solid rgba(37,99,235,.18);
  text-align: center;
}
.rg-balance-label {
  font-size: .62rem;
  font-weight: 700;
  letter-spacing: .12em;
  text-transform: uppercase;
  color: var(--rg-muted);
}
.rg-balance-amount {
  font-family: 'DM Mono', monospace;
  font-size: 1.7rem;
  font-weight: 500;
  color: var(--rg-accent);
  margin-top: 2px;
}
.rg-balance-clear { color: var(--rg-success); }

/* Frozen-total notice */
.rg-invoice-state {
  display: flex;
  align-items: flex-start;
  gap: 8px;
  margin-top: 10px;
  padding-top: 10px;
  border-top: 1px solid var(--rg-border);
  font-size: .76rem;
  color: var(--rg-muted);
  line-height: 1.5;
}
.rg-invoice-state i { margin-top: 2px; }

/* ── Payment history ── */
.rg-pay-list { margin-top: 14px; border-top: 1px solid var(--rg-border); }
.rg-pay-row {
  display: flex;
  align-items: flex-start;
  gap: 10px;
  padding: 10px 0;
  border-bottom: 1px solid var(--rg-border);
}
.rg-pay-row:last-child { border-bottom: 0; }
.rg-pay-icon {
  display: flex;
  align-items: center;
  justify-content: center;
  flex: 0 0 auto;
  width: 26px; height: 26px;
  border-radius: 50%;
  background: rgba(22,163,74,.12);
  color: var(--rg-success);
  font-size: .66rem;
}
.rg-pay-text   { display: flex; flex-direction: column; gap: 1px; min-width: 0; flex: 1 1 auto; }
.rg-pay-top    { display: flex; align-items: baseline; gap: 8px; font-weight: 600; font-size: .88rem; }
.rg-pay-method { font-size: .74rem; font-weight: 500; color: var(--rg-muted); }
.rg-pay-sub    { font-size: .74rem; color: var(--rg-muted); }

/* A voided payment stays on the page, struck through — the correction
   has to be as visible as the mistake. */
.rg-pay-void .rg-pay-top   { text-decoration: line-through; opacity: .55; }
.rg-pay-void .rg-pay-icon  { background: rgba(100,116,139,.14); color: var(--rg-muted); }
.rg-pay-voided             { color: var(--rg-danger); }

/* Money taken but not yet placed against an invoice. */
.rg-pay-icon-credit { background: rgba(37,99,235,.12); color: var(--rg-accent); }

/* ── Receivables ── */
.rg-age {
  display: inline-block;
  padding: 2px 7px;
  border-radius: 20px;
  background: var(--rg-bg);
  border: 1px solid var(--rg-border);
  font-family: 'DM Mono', monospace;
  font-size: .72rem;
  color: var(--rg-muted);
}
.rg-age-30 { border-color: rgba(217,119,6,.35);  background: rgba(217,119,6,.08);  color: var(--rg-warn); }
.rg-age-60 { border-color: rgba(217,119,6,.55);  background: rgba(217,119,6,.14);  color: var(--rg-warn); }
.rg-age-90 { border-color: rgba(220,38,38,.45);  background: rgba(220,38,38,.10);  color: var(--rg-danger); }

/* A selected ageing band, so it's clear the list below is filtered. */
.rg-stat-on {
  border-color: var(--rg-accent);
  box-shadow: 0 0 0 3px rgba(37,99,235,.12);
}

.rg-sub { font-size: .75rem; color: var(--rg-muted); }

/* ── Reports ── */
.rg-range {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  justify-content: space-between;
  gap: 12px;
}
.rg-range-presets { display: flex; flex-wrap: wrap; gap: 6px; }
.rg-range-dates   { display: flex; align-items: center; gap: 8px; }
.rg-range-dates .rg-input { width: auto; min-width: 150px; }
.rg-range-to { font-size: .8rem; color: var(--rg-muted); }

.rg-mix { margin-bottom: 12px; }
.rg-mix-head {
  display: flex;
  justify-content: space-between;
  font-size: .82rem;
  margin-bottom: 4px;
}
.rg-mix-track {
  height: 7px;
  border-radius: 4px;
  background: var(--rg-bg);
  overflow: hidden;
}
.rg-mix-fill {
  height: 100%;
  border-radius: 4px;
  background: var(--rg-accent);
  transition: width .25s;
}

@media (max-width: 575.98px) {
  .rg-range { flex-direction: column; align-items: stretch; }
  .rg-range-dates .rg-input { flex: 1 1 auto; min-width: 0; }
}

/* ── Report period picker ──
   Two lines per button: the abbreviation people say out loud, and the
   window it actually means, so nobody has to guess whether QTD ends
   today or at the end of the quarter. */
.rg-period {
  display: flex;
  flex-direction: column;
  align-items: flex-start;
  gap: 1px;
  padding: 7px 13px;
  border: 1px solid var(--rg-border);
  border-radius: 8px;
  background: var(--rg-surface);
  font-family: inherit;
  text-align: left;
  cursor: pointer;
  transition: border-color .12s, background .12s, color .12s;
}
.rg-period:hover { border-color: var(--rg-accent); }
.rg-period-label { font-size: .82rem; font-weight: 700; color: var(--rg-text); }
.rg-period-sub   { font-size: .68rem; color: var(--rg-muted); white-space: nowrap; }

.rg-period-on {
  background: rgba(37,99,235,.08);
  border-color: var(--rg-accent);
}
.rg-period-on .rg-period-label { color: var(--rg-accent); }
.rg-period-on .rg-period-sub   { color: var(--rg-accent); opacity: .8; }

@media (max-width: 575.98px) {
  .rg-range-presets { display: grid; grid-template-columns: repeat(3, 1fr); gap: 6px; width: 100%; }
  .rg-period-sub    { white-space: normal; }
}

/* ── Labor attached to a saved part ──
   The catalog editor's second block, and the marker on a quick-add chip
   that writes two lines instead of one. */
.rg-attach {
  margin-top: 16px;
  padding: 14px;
  border: 1px solid var(--rg-border);
  border-radius: 10px;
  background: var(--rg-bg);
}

.rg-chip-labor {
  margin-left: 6px;
  padding: 1px 6px;
  border-radius: 10px;
  background: rgba(37,99,235,.12);
  color: var(--rg-accent);
  font-family: 'DM Mono', monospace;
  font-size: .68rem;
  font-weight: 500;
}

/* ── Locked line items ──
   An invoiced order's lines are settled paperwork: read-only, and the
   drag grip becomes a padlock so the reason is visible on the row. */
.rg-lock-mark { color: var(--rg-muted); opacity: .5; font-size: .75rem; }
.rg-cell-input[readonly],
.rg-cell-input:disabled {
  background: transparent;
  border-color: transparent;
  color: var(--rg-text);
  cursor: default;
}
.rg-cell-input[readonly]:hover,
.rg-cell-input:disabled:hover { background: transparent; border-color: transparent; }

/* Small ghost button, for inline actions inside a section label. */
.rg-btn-mini { height: 26px; padding: 0 9px; font-size: .74rem; }

/* ── Customer's vehicles ──
   A car-shaped icon carries the same weight the initials avatar gives
   the customer above it, the order count says which car actually keeps
   coming back, and "Last in" answers "when did we last see this one"
   without a trip to the repair orders table. */
.rg-veh-grid {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(260px, 1fr));
  gap: 10px;
  padding-bottom: 12px;
}
.rg-veh {
  display: flex;
  align-items: flex-start;
  gap: 12px;
  padding: 13px 14px;
  border: 1px solid var(--rg-border);
  border-radius: 10px;
  background: var(--rg-surface);
  text-decoration: none;
  color: inherit;
  transition: border-color .12s, box-shadow .12s, transform .12s;
}
.rg-veh:hover {
  border-color: var(--rg-accent);
  box-shadow: var(--rg-shadow-md);
  transform: translateY(-1px);
  color: inherit;
}
.rg-veh-icon {
  flex: none;
  width: 38px;
  height: 38px;
  display: flex;
  align-items: center;
  justify-content: center;
  border-radius: 9px;
  background: rgba(37,99,235,.1);
  color: var(--rg-accent);
  font-size: 1rem;
}
.rg-veh-body { display: flex; flex-direction: column; gap: 3px; min-width: 0; flex: 1 1 auto; }
.rg-veh-name { font-size: .88rem; font-weight: 700; color: var(--rg-text); }
.rg-veh-meta {
  display: flex;
  flex-wrap: wrap;
  gap: 2px 9px;
  font-size: .78rem;
  color: var(--rg-muted);
}
.rg-veh-vin   { font-size: .68rem; color: var(--rg-muted); opacity: .8; }
.rg-veh-visit { font-size: .72rem; color: var(--rg-muted); }
.rg-veh-orders {
  flex: none;
  display: inline-flex;
  align-items: center;
  gap: 5px;
  align-self: center;
  padding: 4px 9px;
  border-radius: 999px;
  background: var(--rg-bg);
  color: var(--rg-muted);
  font-size: .74rem;
  font-weight: 700;
}
.rg-veh-orders .fa { font-size: .68rem; opacity: .7; }

/* ============================================================
   TAKE PAYMENT
   The balance banner that starts it, and the card that walks
   through amount → allocation → confirm.
   ============================================================ */

/* What they owe, at the size of the question being asked. */
.rg-due {
  display: flex;
  align-items: center;
  justify-content: space-between;
  flex-wrap: wrap;
  gap: 16px;
  padding: 18px 22px;
  margin-bottom: 14px;
  border: 1px solid rgba(220,38,38,.28);
  border-left: 4px solid var(--rg-danger);
  border-radius: var(--rg-radius);
  background: linear-gradient(180deg, rgba(220,38,38,.06), rgba(220,38,38,.02));
}
.rg-due-label {
  font-size: .62rem;
  font-weight: 700;
  letter-spacing: .12em;
  text-transform: uppercase;
  color: var(--rg-muted);
}
.rg-due-amount {
  font-family: 'DM Mono', monospace;
  font-size: 2rem;
  font-weight: 500;
  line-height: 1.15;
  color: var(--rg-danger);
}
.rg-due-sub { font-size: .8rem; color: var(--rg-muted); }

/* Deliberately bigger than a standard primary button: it's the one
   action this page exists for. */
.rg-btn-pay {
  display: inline-flex;
  align-items: center;
  gap: 9px;
  height: 46px;
  padding: 0 26px;
  border: 0;
  border-radius: 10px;
  background: var(--rg-success);
  color: #fff;
  font-family: inherit;
  font-size: .95rem;
  font-weight: 700;
  white-space: nowrap;
  cursor: pointer;
  box-shadow: 0 2px 8px rgba(22,163,74,.3);
  transition: background .12s, box-shadow .12s, transform .1s;
}
.rg-btn-pay:hover  { background: #15803d; box-shadow: 0 4px 14px rgba(22,163,74,.4); transform: translateY(-1px); }
.rg-btn-pay:active { transform: translateY(0); }

/* ── Steps ── */
.rg-step { margin-bottom: 18px; }
.rg-step-head {
  display: flex;
  align-items: center;
  gap: 9px;
  margin-bottom: 10px;
  padding-bottom: 8px;
  border-bottom: 1px solid var(--rg-border);
}
.rg-step-num {
  display: flex;
  align-items: center;
  justify-content: center;
  flex: 0 0 auto;
  width: 22px; height: 22px;
  border-radius: 50%;
  background: var(--rg-accent);
  color: #fff;
  font-size: .72rem;
  font-weight: 700;
}
.rg-step-title { font-size: .9rem; font-weight: 700; color: var(--rg-text); }
.rg-step-body  { padding-left: 31px; }

/* The amount typed at the counter deserves to be legible across a desk. */
.rg-input-big .rg-input { height: 46px; font-size: 1.15rem; }
.rg-input-big .rg-money-sym { font-size: 1rem; }

/* ── Invoice rows ──
   One line each: a checkbox to keep an invoice in the split or take it
   out, what it owes, and what's being put on it. Compact on purpose —
   a fleet account settling a dozen tickets at once needs to scan the
   whole list at a glance, not scroll past a card for each one. */
.rg-inv-rows { border: 1px solid var(--rg-border); border-radius: 10px; overflow: hidden; }
.rg-inv-row {
  display: grid;
  grid-template-columns: 22px minmax(0, 1fr) 84px 16px 128px auto;
  align-items: center;
  gap: 10px;
  padding: 7px 12px;
  background: var(--rg-surface);
  border-bottom: 1px solid var(--rg-border);
  transition: background-color .12s;
}
.rg-inv-row:last-child { border-bottom: none; }
.rg-inv-row.rg-inv-on  { background: rgba(22,163,74,.05); }
.rg-inv-row.rg-inv-off { opacity: .5; }

.rg-inv-check { display: flex; margin: 0; cursor: pointer; }
.rg-inv-check input { width: 16px; height: 16px; margin: 0; cursor: pointer; accent-color: var(--rg-accent); }

.rg-inv-info { display: flex; flex-direction: column; min-width: 0; line-height: 1.3; }
.rg-inv-ro   { font-size: .78rem; font-weight: 700; color: var(--rg-text); }
.rg-inv-veh  {
  font-size: .78rem;
  color: var(--rg-muted);
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.rg-inv-date { font-size: .68rem; color: var(--rg-muted); }

.rg-inv-owed { text-align: right; font-size: .84rem; }
.rg-inv-arrow  { color: var(--rg-border); font-size: .8rem; justify-self: center; }
.rg-inv-apply { width: 128px; }
.rg-inv-apply .rg-input { height: 32px; font-size: .84rem; }

.rg-inv-fill {
  grid-column: 6;
  justify-self: start;
}

/* The status badge and the "Rest" button share the last column, since
   only one of them is ever showing for a given row. */
.rg-inv-status {
  grid-column: 6;
  font-size: .72rem;
  font-weight: 600;
  white-space: nowrap;
}
.rg-inv-status-clear { color: var(--rg-success); }
.rg-inv-status-clear i { margin-right: 4px; }

@media (max-width: 575.98px) {
  .rg-inv-row {
    grid-template-columns: 22px 1fr auto;
    grid-template-areas:
      "check info  owed"
      "amt   amt   side";
    row-gap: 6px;
  }
  .rg-inv-check  { grid-area: check; }
  .rg-inv-info   { grid-area: info; }
  .rg-inv-owed   { grid-area: owed; }
  .rg-inv-arrow  { display: none; }
  .rg-inv-apply  { grid-area: amt; width: auto; }
  .rg-inv-fill, .rg-inv-status { grid-area: side; justify-self: end; }
}

.rg-alloc-label {
  display: block;
  font-size: .58rem;
  font-weight: 700;
  letter-spacing: .1em;
  text-transform: uppercase;
  color: var(--rg-muted);
  margin-bottom: 2px;
}

/* Why a field is missing, rather than a field that shouldn't exist. */
.rg-hint-lock {
  display: flex;
  align-items: flex-start;
  gap: 8px;
  padding: 9px 11px;
  border: 1px solid var(--rg-border);
  border-radius: 8px;
  background: var(--rg-bg);
  line-height: 1.5;
}
.rg-hint-lock i { margin-top: 2px; color: var(--rg-muted); }

/* ── The running answer ── */
.rg-pay-foot {
  display: flex;
  align-items: center;
  justify-content: space-between;
  flex-wrap: wrap;
  gap: 14px;
  padding: 13px 15px;
  border: 1px solid var(--rg-border);
  border-radius: 10px;
  background: var(--rg-bg);
}
.rg-pay-ready {
  border-color: rgba(22,163,74,.4);
  background: rgba(22,163,74,.06);
}
.rg-pay-tally { display: flex; gap: 26px; flex-wrap: wrap; }
.rg-pay-tally .rg-mono { font-size: 1rem; font-weight: 600; }
.rg-pay-left .rg-mono  { color: var(--rg-warn); }
.rg-pay-ready .rg-pay-left .rg-mono { color: var(--rg-success); }

@media (max-width: 767.98px) {
  .rg-due          { flex-direction: column; align-items: stretch; text-align: center; }
  .rg-btn-pay      { justify-content: center; width: 100%; }
  .rg-step-body    { padding-left: 0; }
  .rg-inv-arrow    { display: none; }
  .rg-inv-apply    { flex: 1 1 auto; }
  .rg-pay-foot     { flex-direction: column; align-items: stretch; }
  .rg-pay-foot .d-flex { justify-content: flex-end; }
}

/* ── Paperwork & schedule ──
   Sits above the line items because it governs them: choosing Invoice
   freezes the total and locks the lines below, and the scheduled time
   is what a bay assignment starts from. Two fields side by side with a
   rule between them. */
.rg-doc-card { padding: 16px 18px; }
.rg-doc-inner {
  display: flex;
  align-items: flex-start;
  gap: 22px;
  flex-wrap: wrap;
}
.rg-doc-field  { flex: 1 1 280px; min-width: 0; }
.rg-doc-divide { width: 1px; align-self: stretch; background: var(--rg-border); }
.rg-doc-label {
  font-size: .62rem;
  font-weight: 700;
  letter-spacing: .12em;
  text-transform: uppercase;
  color: var(--rg-muted);
  margin-bottom: 3px;
}
.rg-doc-hint {
  font-size: .78rem;
  color: var(--rg-muted);
  line-height: 1.5;
  margin-top: 6px;
}
.rg-doc-hint i { margin-right: 4px; color: var(--rg-accent); }
.rg-doc-pick {
  display: flex;
  align-items: center;
  gap: 10px;
}
.rg-doc-pick .rg-input { flex: 1 1 auto; min-width: 0; }

/* Stacked on a phone, this card was costing about a screen and a half.
   Most of that wasn't the fields: the divider is a flex item, so once
   the fields wrap it stops being a line between two columns and becomes
   its own full-width row with a 22px gap on each side. */
@media (max-width: 767px) {
  .rg-doc-card   { padding: 12px 14px; }
  .rg-doc-inner  { gap: 12px; }
  .rg-doc-divide { display: none; }
  .rg-doc-field  { flex: 1 1 100%; }

  /* The hints earn their space on a wide screen where they sit beside
     the field. Stacked they push the next field off the screen, so they
     get the smaller, tighter version of themselves. */
  .rg-doc-hint {
    margin-top: 4px;
    font-size: .72rem;
    line-height: 1.35;
  }
}

/* ── Add a line ──
   A row of buttons under the list instead of an entry row sitting open
   on every ticket, where it read as something left half-written.
   Right-justified so it reads as an action taken on the list above it,
   not another row in it. */
.rg-add-line-row {
  display: flex;
  justify-content: flex-end;
  gap: 8px;
  padding: 12px;
}
.rg-add-line {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: 8px;
  padding: 0 16px;
  height: 36px;
  border: 1px solid var(--rg-border);
  border-radius: 8px;
  background: var(--rg-surface);
  color: var(--rg-text);
  font-family: inherit;
  font-size: .85rem;
  font-weight: 600;
  cursor: pointer;
  white-space: nowrap;
  transition: border-color .12s, color .12s, background .12s, box-shadow .15s, transform .1s;
}
.rg-add-line:hover:not(:disabled) {
  border-color: var(--rg-accent);
  color: var(--rg-accent);
  background: rgba(37,99,235,.04);
  box-shadow: var(--rg-shadow-md);
  transform: translateY(-1px);
}
.rg-add-line:active:not(:disabled) { transform: translateY(0); }
.rg-add-line:disabled {
  opacity: .45;
  cursor: not-allowed;
  box-shadow: none;
}

@media (max-width: 767.98px) {
  .rg-doc-inner  { flex-direction: column; align-items: stretch; gap: 16px; }
  .rg-doc-divide { width: auto; height: 1px; align-self: auto; }
}

/* The typeahead menu now opens from a table cell, which needs a
   positioning context and enough stacking to clear the rows below. */
/* The row's menu is positioned against the viewport, not the cell.
   .rg-table-wrap scrolls horizontally, and an absolutely positioned
   menu inside it gets clipped — it vanished behind the rows below.
   Coordinates come from ng-style; everything else is here. */
td.rg-typeahead { position: relative; }
.rg-typeahead-menu.rg-ta-fixed {
  position: fixed;
  right: auto;
  z-index: 1200;
  max-height: min(50vh, 380px);
  overflow-y: auto;
}

/* ── Grouped line items ──
   One level: a part parents the labor to fit it. The child is indented
   with a rule running back to its parent, so the group reads as one
   item of work rather than two unrelated lines.

   Column indices count from the select checkbox, which is column 1:
   select(1) grip(2) type(3) item#(4) description(5). */

.rg-line-child td:nth-child(2) { position: relative; }
.rg-line-child td:nth-child(2)::before {
  content: '';
  position: absolute;
  left: 8px; top: 0; bottom: 50%;
  border-left: 2px solid var(--rg-accent);
  border-bottom: 2px solid var(--rg-accent);
  width: 12px;
  border-bottom-left-radius: 5px;
  opacity: .45;
}

/* Indent the description, which is what carries the meaning. */
.rg-line-child td:nth-child(5) { padding-left: 26px; }

/* Holds the grip column's width open on a child row, which has no drag
   handle of its own — the elbow rule is drawn on the cell itself. */
.rg-child-mark { display: inline-block; width: 22px; }

/* A parent's own row keeps a hairline under it so the block holds
   together visually even when the types differ. */
.rg-line-parent > td { border-bottom-color: transparent; }

/* A parent that owns a group gets a heavier left edge and its
   description set in the stronger text colour, so "this is the job"
   and "this is part of it" are legible at a glance. */
.rg-line-parent td:first-child { box-shadow: inset 4px 0 0 var(--line-accent) !important; }
.rg-line-parent .rg-cell-input { font-weight: 600; }

/* The whole child block is tinted and rides on a rail back to its
   parent, so a group reads as one thing however long it gets. A lighter
   blue than the accent — it sits behind white fields and only has to
   say "this belongs to the row above". */
.rg-line-child > td { background: rgba(96,165,250,.13); }
.rg-line-child:hover > td { background: rgba(96,165,250,.19); }
.rg-line-child td:first-child {
  box-shadow: inset 4px 0 0 var(--line-accent), inset 22px 0 0 -20px rgba(37,99,235,.35) !important;
}

/* ── Which shop ──
   A label for most accounts; a menu for the owner, who has more than
   one. It sits left of the bell so the answer to "where am I working"
   reads before anything about the work itself. */
.smp-shop { display: flex; align-items: center; flex-shrink: 0; }
.smp-shop-btn {
  display: inline-flex;
  align-items: center;
  gap: 7px;
  height: 34px;
  padding: 0 12px;
  border: 1px solid rgba(255,255,255,.14);
  border-radius: 8px;
  background: rgba(255,255,255,.05);
  color: #d1d5db;
  font-family: inherit;
  font-size: .8rem;
  font-weight: 600;
  white-space: nowrap;
  cursor: pointer;
}
.smp-shop-btn:hover  { border-color: rgba(255,255,255,.3); color: #fff; }
.smp-shop-btn i      { color: var(--rg-accent); font-size: .78rem; }
.smp-shop-fixed      { cursor: default; }
.smp-shop-fixed:hover { border-color: rgba(255,255,255,.14); color: #d1d5db; }

@media (max-width: 1279.98px) {
  .smp-shop-btn span { display: none; }
  .smp-shop-btn      { padding: 0 10px; }
}

/* The shop currently being worked in. */
.rg-hub-current { border-color: var(--rg-success); box-shadow: 0 0 0 3px rgba(22,163,74,.1); }

/* ── Ready for pickup is the office's call ──
   The floor says the work is done by stopping the clock; the writer is
   who tells the customer to come and get it. This says which of those
   has happened, so the absence of a button isn't a missing feature. */
.smp-hand-off {
  display: flex;
  align-items: flex-start;
  gap: 9px;
  margin-top: 10px;
  padding: 11px 13px;
  border-radius: 10px;
  background: rgba(22,163,74,.08);
  border: 1px solid rgba(22,163,74,.25);
  color: #166534;
  font-size: .85rem;
  line-height: 1.5;
}
.smp-hand-off i { margin-top: 2px; color: var(--rg-success); }

.smp-hand-off-quiet {
  background: var(--rg-bg);
  border-color: var(--rg-border);
  color: var(--rg-muted);
}
.smp-hand-off-quiet i { color: var(--rg-muted); }

/* ── DVI checklist, in the portal ──
   Grade buttons are letters, not icons: a thumb glancing down at a
   sunlit phone screen reads G/Y/R faster than parsing a dot's color. */
/* Collapsed headers sit in an even stack: the gap between two of them is
   this margin and nothing else, so a closed header contributes no bottom
   margin of its own (see .smp-dvi-cat). */
.smp-dvi-group { margin-top: 8px; }
.smp-dvi-group:first-child { margin-top: 0; }
/* Each item is its own card. They used to be divided by a hairline,
   which is enough when a row is a name and three buttons — but an item
   now carries grades, findings, a note and two camera buttons, and a
   rule between two blocks that tall doesn't say where one ends. On the
   section's grey panel these read as separate cards. */
.smp-dvi-item {
  padding: 14px;
  margin-bottom: 12px;
  border: 1px solid var(--rg-border);
  border-radius: 10px;
  background: var(--rg-surface);
}
.smp-dvi-item:last-child { margin-bottom: 0; }
.smp-dvi-item-head { display: flex; align-items: center; gap: 10px; }
.smp-dvi-name { font-weight: 700; font-size: .95rem; flex: 1; min-width: 0; }
/* The corner, read as part of the item's own name — "Front Left Tread
   depth" — rather than a badge sitting beside it. Same colour, size and
   weight as the name itself, so the row is simply one phrase. */
.smp-dvi-pos-text { color: var(--rg-text); margin-right: 6px; }

.smp-dvi-required {
  display: inline-block; margin-left: 8px; font-size: .68rem; font-weight: 700;
  text-transform: uppercase; letter-spacing: .04em; color: var(--rg-danger);
}
/* The grades fill the row and share it equally — a thumb shouldn't have
   to aim at a 44px square, and four buttons of different widths read as
   though one of them mattered more. */
.smp-dvi-grades { display: flex; gap: 6px; margin-top: 10px; }
.smp-dvi-grades .smp-dvi-grade { flex: 1 1 0; min-width: 0; width: auto; }
.smp-dvi-grade {
  height: 48px; border-radius: 8px; border: 2px solid var(--rg-border);
  background: var(--rg-bg); font-weight: 800; font-size: .95rem; color: var(--rg-muted);
  cursor: pointer; transition: box-shadow .1s ease, background .1s ease;
}
.smp-dvi-grade:disabled { opacity: .6; }
.smp-dvi-green  { color: var(--rg-success); border-color: rgba(22,163,74,.4);  background: rgba(22,163,74,.08); }
.smp-dvi-yellow { color: #854d0e;           border-color: rgba(234,179,8,.55); background: rgba(234,179,8,.14); }
.smp-dvi-red    { color: var(--rg-danger); border-color: rgba(220,38,38,.4);  background: rgba(220,38,38,.08); }
.smp-dvi-green.active  { background: var(--rg-success); border-color: var(--rg-success); color: #fff; box-shadow: 0 0 0 3px rgba(22,163,74,.3); }
.smp-dvi-yellow.active { background: var(--rg-dvi-yellow); border-color: var(--rg-dvi-yellow); color: var(--rg-dvi-yellow-ink); box-shadow: 0 0 0 3px rgba(234,179,8,.3); }
.smp-dvi-red.active    { background: var(--rg-danger); border-color: var(--rg-danger); color: #fff; box-shadow: 0 0 0 3px rgba(220,38,38,.3); }
/* N/A is deliberately neutral — it isn't a grade on the red/yellow/green
   scale, it's opting the item out of one, and shouldn't visually compete
   with them for attention. */
.smp-dvi-na { font-size: .72rem; font-weight: 700; }
.smp-dvi-na.active { background: var(--rg-muted); border-color: var(--rg-muted); color: #fff; box-shadow: 0 0 0 3px rgba(100,116,139,.25); }
/* ── "The clock started" ──
   An overlay, not a line in the page: the tech didn't ask for this, so
   it has to arrive in front of whatever they were looking at. Sits at
   the bottom, where the thumb already is and where nothing on the
   inspection screen competes with it, clears the home indicator, and is
   tappable anywhere to dismiss. */
.smp-clock-toast {
  position: fixed;
  z-index: 1150;
  bottom: calc(14px + var(--safe-b));
  left: calc(12px + var(--safe-l));
  right: calc(12px + var(--safe-r));
  display: flex; align-items: center; gap: 13px;
  padding: 17px 18px;
  border-radius: 14px;
  border: 1px solid rgba(22,163,74,.3);
  background: #ecfdf5;
  box-shadow: var(--rg-shadow-md);
  color: #166534;
  font-size: .96rem; font-weight: 600; line-height: 1.4;
  cursor: pointer;
  animation: smpToastIn .28s cubic-bezier(.22, 1, .36, 1);
}
.smp-clock-toast-icon { color: var(--rg-success); font-size: 1.2rem; }
@keyframes smpToastIn {
  from { opacity: 0; transform: translateY(14px); }
  to   { opacity: 1; transform: translateY(0); }
}

/* ── Priority board ── */
.rg-tech-tabs {
  display: flex; flex-wrap: wrap; gap: 8px;
  margin-bottom: 14px;
}
.rg-tech-tab {
  display: inline-flex; align-items: center; gap: 8px;
  padding: 9px 15px;
  border: 1px solid var(--rg-border);
  border-radius: 20px;
  background: var(--rg-surface);
  font-family: inherit; font-size: .85rem; font-weight: 600;
  color: var(--rg-text);
  cursor: pointer;
}
.rg-tech-tab:hover  { border-color: var(--rg-accent); }
.rg-tech-tab.active { background: var(--rg-accent); border-color: var(--rg-accent); color: #fff; }
.rg-tech-tab-count {
  font-family: 'DM Mono', monospace; font-size: .74rem;
  padding: 1px 7px; border-radius: 20px;
  background: var(--rg-bg); color: var(--rg-muted);
}
.rg-tech-tab.active .rg-tech-tab-count { background: rgba(255,255,255,.24); color: #fff; }

.rg-priority-row { cursor: pointer; }
.rg-priority-rank {
  font-family: 'DM Mono', monospace; font-size: 1rem; font-weight: 700;
  color: var(--rg-muted); text-align: center;
}
.rg-priority-title { font-weight: 600; }
.rg-priority-sub   { font-size: .78rem; color: var(--rg-muted); margin-top: 2px; }

/* How far through its checklist an inspection is, at a glance — the
   numbers underneath say it exactly. */
.rg-dvi-mini {
  height: 6px; border-radius: 3px;
  background: var(--rg-border);
  overflow: hidden;
}
.rg-dvi-mini-fill {
  height: 100%;
  background: var(--rg-accent);
  transition: width .2s ease;
}
.rg-dvi-mini-done { background: var(--rg-success); }

/* ── The car, from above ──
   A plan view with a wheel at each corner: the tech taps the wheel
   they're stood next to rather than reading four near-identical names
   off a list. Drawn in CSS rather than shipped as an image — it has to
   recolour with status, and an SVG that needed four tinted states would
   be four assets to keep in step with the palette.

   The shape does the orienting: a rounded nose, a windscreen wider than
   the rear glass, and stub axles out to each wheel. FRONT is still
   written on it, because a diagram this small shouldn't rely on the
   reader working the geometry out. */
/* Sized and laid out like a car seen from above rather than stretched
   to the phone's width: fixed dimensions, centred, roughly as long as a
   vehicle is against its width. The wheels are placed absolutely rather
   than sat in grid cells so they can do what real ones do — tuck into
   the arches, overlapping the body's sides, and sit inset from the nose
   and tail instead of flush at the four corners. */
.smp-car {
  position: relative;

  /* Every dimension of the car lives here, so the shape can be drawn at
     another size without being redrawn. The customer's report puts one of
     these beside every part it checked at four wheels, and at the
     technician's size — where there is one car for the whole inspection —
     that would run to hundreds of pixels per part. .smp-car-sm below is
     the same car, smaller; nothing about its proportions is restated. */
  --car-w: 140px;
  --car-h: 264px;
  --tyre-w: 40px;
  --tyre-h: 64px;
  /* 104px of body, with the wheels tucked 22px under each side — so
     only 18px of tyre shows proud, and the whole thing is 140 across. */
  --tyre-inset: 18px;
  --axle-front: 42px;
  --axle-rear: 38px;
  --car-label: .56rem;
  --car-pos: .82rem;
  --car-read: .62rem;

  width: var(--car-w);
  height: var(--car-h);
  margin: 4px auto 18px;
}

/* The same car at report size. Only the numbers change. */
.smp-car-sm {
  /* Wide enough for the longest thing a wheel has to say. A reading is
     up to five characters — 6/32", 26psi — and a tyre it overflows is a
     tyre with text lying across the bodywork. */
  --car-w: 112px;
  --car-h: 196px;
  --tyre-w: 34px;
  --tyre-h: 48px;
  --tyre-inset: 14px;
  --axle-front: 30px;
  --axle-rear: 27px;
  --car-label: .48rem;
  --car-pos: .6rem;
  --car-read: .56rem;
  margin: 2px auto 6px;
}

.smp-car-body {
  position: absolute;
  top: 0; bottom: 0; left: var(--tyre-inset); right: var(--tyre-inset);
  display: flex; align-items: flex-start; justify-content: center;
  padding-top: 8px;
  /* Nose rounder than the tail — the two-value radius makes the curve
     oval rather than a quarter-circle, which is what reads as a bonnet. */
  border: 2px solid var(--rg-border);
  border-radius: 40px 40px 20px 20px / 52px 52px 18px 18px;
  background: linear-gradient(180deg, var(--rg-surface), var(--rg-bg));
  font-size: var(--car-label); font-weight: 700; letter-spacing: .18em;
  color: var(--rg-muted);
}
/* Windscreen, and the smaller rear glass behind it. */
.smp-car-body::before,
.smp-car-body::after {
  content: '';
  position: absolute;
  border: 1px solid var(--rg-border);
  background: rgba(14,165,233,.16);
}
.smp-car-body::before {
  left: 15%; right: 15%; top: 24%; height: 15%;
  border-radius: 14px 14px 5px 5px;
}
.smp-car-body::after {
  left: 18%; right: 18%; bottom: 16%; height: 11%;
  border-radius: 5px 5px 12px 12px;
  background: rgba(14,165,233,.1);
}

.smp-car-tire {
  position: absolute;
  z-index: 1;
  display: flex; flex-direction: column; align-items: center; justify-content: center;
  gap: 1px;
  width: var(--tyre-w);
  height: var(--tyre-h);
  padding: 0;
  border: 2px solid var(--rg-border);
  /* A tyre from above is a slab longer than it is wide. */
  border-radius: 12px;
  background-color: var(--rg-surface);
  /* Tread, faint enough to stay out of the label's way. */
  background-image: repeating-linear-gradient(
    90deg, rgba(15,23,32,.07) 0 3px, transparent 3px 8px);
  font-family: inherit;
  cursor: pointer;
}
/* Overlapping the body by 22px on each side, and set back from the
   ends the way an axle actually sits. */
.smp-car-fl { left: 0;  top: var(--axle-front); }
.smp-car-fr { right: 0; top: var(--axle-front); }
.smp-car-rl { left: 0;  bottom: var(--axle-rear); }
.smp-car-rr { right: 0; bottom: var(--axle-rear); }
.smp-car-pos   { font-size: var(--car-pos); font-weight: 800; color: var(--rg-text); }
.smp-car-count {
  font-family: 'DM Mono', monospace;
  font-size: var(--car-read);
  color: var(--rg-muted);
  white-space: nowrap;
}

/* What was found at that corner. Ungraded stays plain — an untouched
   wheel is no news, not good news. */
.smp-car-green  { border-color: rgba(22,163,74,.5);  background-color: rgba(22,163,74,.09); }
.smp-car-yellow { border-color: rgba(234,179,8,.6);  background-color: rgba(234,179,8,.14); }
.smp-car-red    { border-color: rgba(220,38,38,.5);  background-color: rgba(220,38,38,.09); }
.smp-car-green .smp-car-count  { color: var(--rg-success); }
.smp-car-yellow .smp-car-count { color: #854d0e; }
.smp-car-red .smp-car-count    { color: var(--rg-danger); }

/* The wheel being worked, matching the open section below it. */
.smp-car-tire.active {
  border-color: var(--rg-dvi-yellow);
  background-color: var(--rg-dvi-yellow);
  box-shadow: 0 3px 10px rgba(234,179,8,.4);
}
.smp-car-tire.active .smp-car-pos,
.smp-car-tire.active .smp-car-count { color: var(--rg-dvi-yellow-ink); }

/* ── Category headers, foldable ── */
.smp-dvi-cat {
  display: flex; align-items: center; gap: 10px; width: 100%;
  padding: 10px 12px; margin-bottom: 0;
  background: var(--rg-bg);
  border: 1px solid var(--rg-border);
  border-radius: 8px;
  font-family: inherit; font-size: .72rem; font-weight: 700;
  letter-spacing: .08em; text-transform: uppercase; color: var(--rg-muted);
  cursor: pointer;
}
.smp-dvi-cat-caret { font-size: .7rem; flex: none; }
/* Held shut until the first section has been answered. Still tappable —
   the tap is what scrolls the tech back to what's being asked for, and a
   header that does nothing at all reads as a broken screen. */
.smp-dvi-cat-held { opacity: .5; }
.smp-car-held { opacity: .45; }
.smp-dvi-cat-count {
  flex: none; font-family: 'DM Mono', monospace; letter-spacing: 0;
  font-size: .78rem; color: var(--rg-muted);
}
/* Everything in it graded, so it can be folded away without checking. */
.smp-dvi-cat-done {
  border-color: rgba(22,163,74,.3);
  background: rgba(22,163,74,.07);
  color: #166534;
}
.smp-dvi-cat-done .smp-dvi-cat-count { color: var(--rg-success); }
/* The one that's open — last, so it wins over the graded-green above.
   Solid amber, not a tint: with every other header sitting quiet and
   grey, the section being worked should be findable at a glance from
   across a bay, without reading a word of it. Amber rather than the
   accent blue because this is "you are here", not a status — it must
   not read as another grade on the green/red scale the items use.
   It also grows: taller, larger type, and squared off at the bottom so
   it joins the items underneath as one block rather than floating
   above them.

   Full-bleed to the panel it caps, exactly like the corner sections'
   .smp-dvi-cat-label — the negative margins cancel the panel's side
   padding, so the two kinds of section header sit in the same place and
   the bar meets the panel's edges instead of floating 12px inside a
   second border. Text lines up with the left edge of the item cards
   below it, which is the only vertical line down this screen worth
   holding. */
.smp-dvi-cat-open {
  padding: 14px 12px;
  margin: 0 -12px 12px;
  border: 0;
  /* One less than the panel's 10px: the inner corner sits inside the
     outer one rather than cutting across it. */
  border-radius: 9px 9px 0 0;
  font-weight: 800;
  font-size: .92rem;
  letter-spacing: .04em;
  color: var(--rg-dvi-yellow-ink);
  background: var(--rg-dvi-yellow);
  border-color: var(--rg-dvi-yellow);
  box-shadow: 0 3px 10px rgba(234,179,8,.4);
}
.smp-dvi-cat-open .smp-dvi-cat-caret { color: var(--rg-dvi-yellow-ink); font-size: .85rem; }
/* The section being worked sits on its own panel, a shade off the card
   behind it, so where it starts and ends is obvious once the header has
   scrolled away. */
.smp-dvi-group-open {
  background: var(--rg-bg);
  border: 1px solid var(--rg-border);
  border-radius: 10px;
  padding: 0 12px 12px;
  margin-bottom: 16px;
}

/* Its title is its own bar, not a restyled foldable header: the wheel
   above is what opens and closes this, so it's a heading rather than a
   control. Full-bleed to the panel's inner edge — the side margins
   cancel the panel's padding exactly, so nothing is eyeballed. */
.smp-dvi-cat-label {
  display: flex;
  align-items: center;
  gap: 10px;
  margin: 0 -12px 12px;
  /* Sides match the panel's padding, so the title starts on the same
     line the item cards below it start on. */
  padding: 12px;
  border: 0;
  /* One less than the panel's 10px, so the inner corner sits inside the
     outer one instead of cutting across it. */
  border-radius: 9px 9px 0 0;
  background: var(--rg-text);
  color: #fff;
  font-family: inherit;
  font-size: .9rem;
  font-weight: 800;
  letter-spacing: .04em;
  cursor: default;
}
.smp-dvi-cat-label .smp-dvi-cat-count {
  color: #fff;
  font-size: .8rem;
  background: rgba(255,255,255,.2);
  border-radius: 20px;
  padding: 2px 9px;
}

/* Back to the car. */
.smp-dvi-back {
  display: flex; align-items: center; justify-content: center;
  flex: none;
  width: 30px; height: 30px;
  border: 1px solid rgba(255,255,255,.3);
  border-radius: 8px;
  background: rgba(255,255,255,.12);
  color: #fff;
  font-size: .85rem;
  cursor: pointer;
}
.smp-dvi-back:active { background: rgba(255,255,255,.25); }
.smp-dvi-cat-open .smp-dvi-cat-count {
  color: var(--rg-dvi-yellow-ink);
  font-size: .85rem;
  background: rgba(0,0,0,.13);
  border-radius: 20px;
  padding: 2px 9px;
}

/* ── Measurement scale ──
   One row, always. A scale that wraps stops reading as a scale — the
   low end ends up under the high end and the gap between "3" and "4"
   depends on the phone's width. So the buttons share the row instead
   of holding a fixed width: every one the same size, whatever the
   range or screen. The N/A button sits in the row on the same terms.
   The chosen reading is simply filled in solid; nothing lifts or rings,
   so the row never moves under a thumb mid-tap. */
/* Fixed 52px squares that wrap, rather than nine of them squeezed into
   one row. Dividing the width by however many values a scale happens to
   have gave a 34px target on a phone — under a gloved thumb that is a
   guess, and the number it lands on is the inspection. A second row is
   the cheaper price. */
.smp-dvi-measures { display: flex; gap: 6px; flex-wrap: wrap; }
.smp-dvi-measures .smp-dvi-grade {
  flex: 0 0 auto;
  width: 52px;
  height: 52px;
  padding: 0;
  font-size: .92rem;
}
.smp-dvi-measures .smp-dvi-grade.active {
  transform: none;
  box-shadow: none;
  font-weight: 800;
}

/* ── Typed readings ──
   Two boxes at most, side by side, with the unit inside the field so the
   number and what it means don't get separated on a narrow screen. */
.smp-dvi-typed {
  display: flex; align-items: flex-end; flex-wrap: wrap; gap: 10px;
}
.smp-dvi-typed-box { display: flex; flex-direction: column; gap: 3px; min-width: 0; }
.smp-dvi-typed-box .rg-label { margin: 0; }
.smp-dvi-typed-input { position: relative; display: block; }
.smp-dvi-typed-input .rg-input {
  width: 118px;
  padding-right: 46px;   /* room for the unit */
  min-height: 48px;
  font-size: 1.05rem;
}
.smp-dvi-typed-unit {
  position: absolute; right: 12px; top: 50%; transform: translateY(-50%);
  font-size: .8rem; font-weight: 600; color: var(--rg-muted);
  pointer-events: none;
}
.smp-dvi-typed .smp-dvi-na { align-self: flex-end; }

/* ── Room to work on a phone ──
   Four layers of padding stack down this screen — the content well, the
   card, the section panel, then the item card — and at desktop figures
   that came to 124px of a 360px screen before a single word of the
   checklist. Each layer is trimmed rather than any one removed, so the
   nesting still reads as nested. The section label's negative margin has
   to move with the panel's padding or it stops bleeding to the edge. */
@media (max-width: 575px) {
  .smp-portal .rg-content {
    padding-left:  calc(10px + var(--safe-l));
    padding-right: calc(10px + var(--safe-r));
  }
  .smp-portal .rg-card-body { padding-left: 10px; padding-right: 10px; }
  .smp-dvi-group-open       { padding-left: 8px;  padding-right: 8px; }
  .smp-dvi-item             { padding: 12px 10px; }

  /* Both section headers bleed to the panel's edge, so their side
     margins and padding follow the panel's padding down with it. */
  .smp-dvi-cat-label,
  .smp-dvi-cat-open {
    margin-left: -8px; margin-right: -8px;
    padding-left: 8px; padding-right: 8px;
  }
}

/* ── Findings ──
   Why a component is yellow or red. Chips rather than a list of
   switches: several apply at once, they're short, and a tech is picking
   from a known set rather than reading each one to decide. */
.smp-dvi-findings {
  display: flex; flex-wrap: wrap; gap: 6px;
  margin-top: 10px;
}
.smp-dvi-finding {
  display: inline-flex; align-items: center; gap: 6px;
  min-height: 36px; padding: 0 12px;
  border: 1px solid var(--rg-border);
  border-radius: 20px;
  background: var(--rg-surface);
  font-family: inherit; font-size: .82rem; font-weight: 600;
  color: var(--rg-muted);
  cursor: pointer;
}
.smp-dvi-finding i { font-size: .7rem; }
/* One tap yellow, another red, a third clears it. The chip carries the
   weight the technician gave it, rather than borrowing the item's grade —
   two things can be wrong with one part at two different weights, and a
   streaking wiper is not a torn one. */
.smp-dvi-finding-yellow {
  background: var(--rg-warn);
  border-color: var(--rg-warn);
  color: #1f2937;
}
.smp-dvi-finding-red {
  background: var(--rg-danger);
  border-color: var(--rg-danger);
  color: #fff;
}
.smp-dvi-finding:disabled { opacity: .6; cursor: default; }
/* A photo item's one control. Wider than a grade square because it says
   a sentence rather than a letter, and there is only ever one of it. */
.smp-dvi-done {
  display: inline-flex; align-items: center; justify-content: center; gap: 8px;
  min-height: 40px; padding: 0 18px;
  margin-top: 10px;
  border: 1px solid var(--rg-border);
  border-radius: 20px;
  background: var(--rg-surface);
  font-family: inherit; font-size: .85rem; font-weight: 600;
  color: var(--rg-muted);
  cursor: pointer;
}
.smp-dvi-done.active {
  background: var(--rg-success, #16a34a);
  border-color: var(--rg-success, #16a34a);
  color: #fff;
}
.smp-dvi-done:disabled { opacity: .6; cursor: default; }

/* An item that is nothing but its findings — dash warning lights. With
   no grade row above them the chips are the control, not a footnote to
   one, so they get the room to look like it. */
.smp-dvi-findings-only { margin-top: 12px; gap: 8px; }
.smp-dvi-findings-only .smp-dvi-finding { min-height: 40px; font-size: .85rem; }
/* "Nothing on" — the one answer a dash with no lights needs. Goes flat
   once a light is ticked: the two can't both be true, and greying it is
   cheaper to read than an error after the fact. */
.smp-dvi-clear { font-size: .82rem; letter-spacing: .01em; }

/* ── The gate ──
   What the checklist is asking for before it opens. Sits above the item
   it's about, in the accent colour rather than a warning one: nothing has
   gone wrong, the tech is simply being asked for something first. */
.smp-gate {
  display: flex; align-items: flex-start; gap: 10px;
  margin-bottom: 12px; padding: 12px;
  border: 1px solid rgba(37,99,235,.3);
  border-radius: 10px;
  background: rgba(37,99,235,.07);
  color: var(--rg-text);
}
.smp-gate > i { flex: none; margin-top: 2px; font-size: 1.05rem; color: var(--rg-accent); }
.smp-gate strong { display: block; font-size: .9rem; }
.smp-gate span { display: block; margin-top: 3px; font-size: .8rem; color: var(--rg-muted); }
/* Tapping a locked section scrolls back here — the flash is what says
   "this is the thing that answered your tap", since the scroll on its own
   can land somewhere the tech was already looking. */
.smp-gate-nudge {
  animation: smp-gate-flash 1.1s ease-out 2;
}
@keyframes smp-gate-flash {
  0%, 100% { border-color: rgba(37,99,235,.3);  background: rgba(37,99,235,.07); }
  40%      { border-color: rgba(37,99,235,.85); background: rgba(37,99,235,.2); }
}

/* ── Which lamp ──
   A finding that could be either side, or any of four beams, asks where.
   Drawn as the car head-on rather than offered as another row of chips:
   "LH" is a thing you decode, a headlight in the top-left of a car is a
   thing you point at. Sits under the chip that opened it, in its own
   well, so it reads as a question about that chip rather than as more
   findings. */
.smp-lamp {
  margin-top: 8px;
  padding: 10px;
  border: 1px solid var(--rg-border);
  border-radius: 10px;
  background: var(--rg-bg);
}
.smp-lamp-head {
  display: flex; align-items: center; gap: 8px;
  font-size: .76rem; font-weight: 700;
  letter-spacing: .03em; text-transform: uppercase;
  color: var(--rg-muted);
}
.smp-lamp-chosen {
  margin-left: auto; flex: none;
  padding: 2px 9px; border-radius: 20px;
  background: var(--rg-dvi-yellow); color: var(--rg-dvi-yellow-ink);
  font-size: .72rem; letter-spacing: 0; text-transform: none;
}

/* The car. Capped narrow and centred — stretched to a tablet's width it
   stops looking like a car and starts looking like a toolbar.

   One skeleton, three views: roof / face / bumper, with two columns of
   lamps and a box between them. FRONT is a windscreen, grille and
   bumper; REAR swaps the grille for a number plate and can hang the
   high-mount brake light in the window; TOP drops the roof and bumper
   entirely and turns the middle into the body of the car seen from
   above. The modifier class does all of it — the markup doesn't change
   between them. */
.smp-front { max-width: 280px; margin: 10px auto 0; }

/* Cabin, tapered in from the fascia below it. */
.smp-front-roof {
  display: flex; align-items: center; justify-content: center;
  min-height: 20px; margin: 0 26px;
  border: 1px solid var(--rg-border); border-bottom: 0;
  border-radius: 12px 12px 0 0;
  background: linear-gradient(#e0edfd, #cfe0f7);
}
.smp-front-face {
  display: flex; align-items: center; gap: 8px;
  padding: 10px 8px;
  border: 1px solid var(--rg-border);
  border-radius: 14px 14px 4px 4px;
  background: var(--rg-surface);
}
/* Source order is cluster, cluster, grille; the grille belongs between
   them. Ordering rather than markup gymnastics, since the two clusters
   come out of one ng-repeat. */
.smp-front-cluster {
  display: flex; flex-direction: column; gap: 4px;
  flex: none; width: 76px;
}
.smp-front-cluster + .smp-front-cluster { order: 2; }
.smp-front-grille {
  order: 1; flex: 1 1 auto; height: 34px; border-radius: 5px;
  background: repeating-linear-gradient(var(--rg-border) 0 2px, transparent 2px 7px);
}
.smp-front-side {
  text-align: center;
  font-size: .6rem; font-weight: 800;
  letter-spacing: .1em; text-transform: uppercase;
  color: var(--rg-muted);
}
/* Each lamp is a target in its own right — a beam picked wrong is a part
   ordered wrong, so these get the same 38px+ a grade button gets rather
   than the size the drawing would like them to be. */
.smp-front-lamp {
  display: flex; align-items: center; justify-content: center; gap: 5px;
  height: 38px; padding: 0 6px;
  border: 2px solid var(--rg-border);
  border-radius: 8px;
  background: var(--rg-bg);
  font-family: inherit; font-size: .78rem; font-weight: 800;
  color: var(--rg-muted);
  cursor: pointer;
}
.smp-front-lamp i { font-size: .72rem; }
/* Lit, because that is what a headlight does. Amber rather than red:
   this says which one, not how bad — the finding chip above already
   said how bad. */
.smp-front-lamp.active {
  background: var(--rg-dvi-yellow);
  border-color: var(--rg-dvi-yellow);
  color: var(--rg-dvi-yellow-ink);
}
.smp-front-lamp:disabled { opacity: .6; cursor: default; }
.smp-front-bumper {
  height: 9px; margin: 3px 8px 0;
  border-radius: 0 0 8px 8px;
  background: var(--rg-border);
}

/* ── From behind ──
   Tail, brake, marker and plate lights. Darker glass than a windscreen,
   and the middle box is the number plate rather than a grille. */
.smp-view-REAR .smp-front-roof {
  background: linear-gradient(#cdddf2, #b4cbe8);
  padding: 0 8px;
}
.smp-view-REAR .smp-front-grille {
  flex: 0 1 96px; align-self: center;
  height: 26px; border-radius: 4px;
  background: #fff;
  border: 2px solid var(--rg-border);
}
/* The third brake light, where it actually lives. Wider and shorter than
   a cluster lamp, like the strip it represents. */
.smp-front-third {
  margin: 6px 0; height: 30px;
  font-size: .68rem; letter-spacing: .02em;
}

/* ── From above ──
   Turn signals and side markers: four corners, so this is the same plan
   view the wheels use. No windscreen and no bumper — the middle box is
   the whole car, and the lamps sit at its corners. */
.smp-view-TOP .smp-front-roof,
.smp-view-TOP .smp-front-bumper { display: none; }
.smp-view-TOP .smp-front-face {
  align-items: stretch;
  border-radius: 12px;
}
.smp-view-TOP .smp-front-grille {
  display: flex; align-items: flex-start; justify-content: center;
  height: auto; padding-top: 7px;
  border: 1px solid var(--rg-border);
  border-radius: 16px 16px 7px 7px;
  background: var(--rg-bg);
  font-size: .58rem; font-weight: 800;
  letter-spacing: .1em; color: var(--rg-muted);
}
/* Front lamp at the top, rear at the bottom, with the car between them —
   the column has to fill the box for that to read. */
.smp-view-TOP .smp-front-cluster { justify-content: space-between; }
.smp-view-TOP .smp-front-lamp { flex: 1 1 0; min-height: 38px; }

/* The pieces down the centre of the car: windscreen, back glass, a
   rearview mirror, a sunroof. Two of them push apart so the windscreen
   lands at the front and the back glass at the back; one sits in the
   middle where a sunroof and a mirror both belong. */
.smp-view-TOP .smp-front-grille {
  flex-direction: column; justify-content: space-between;
  gap: 6px; padding: 6px;
}
.smp-view-TOP .smp-front-grille:only-child { justify-content: flex-start; }
.smp-front-mid {
  width: 100%; min-height: 34px; height: auto;
  padding: 0 4px;
  font-size: .68rem; letter-spacing: .01em;
  line-height: 1.15;
}
/* A single centre piece is centred, not pinned to the top. */
.smp-front-mid:only-child { margin: auto 0; }

/* "All good" — a clean section in one tap. Quiet: it's a shortcut, not
   the main way through the checklist. */
.smp-dvi-allgood {
  display: inline-flex; align-items: center; gap: 6px;
  flex: none;
  padding: 5px 11px;
  border: 1px solid rgba(255,255,255,.35);
  border-radius: 20px;
  background: rgba(255,255,255,.16);
  font-family: inherit; font-size: .68rem; font-weight: 700;
  letter-spacing: .04em; text-transform: uppercase;
  color: inherit;
  cursor: pointer;
}
.smp-dvi-allgood i { font-size: .7rem; }
/* The translucent white above is drawn for the corner sections' dark bar.
   On the amber header it disappears into the background, leaving the chip
   as floating words with no edge — so it borrows the header's own ink. */
.smp-dvi-cat-open .smp-dvi-allgood {
  border-color: rgba(0,0,0,.22);
  background: rgba(0,0,0,.09);
}
.smp-dvi-allgood:active { background: rgba(255,255,255,.3); }

/* ── Photo, video, library ──
   The evidence is most of what a DVI is worth to the customer, so the
   two camera buttons aren't quiet secondary controls: thumb-height, in
   the accent colour, dashed because they open a picker rather than
   commit anything. They share the row on equal terms — neither is the
   lesser action, and one wider than the other would say otherwise. */
.smp-dvi-capture {
  display: flex;
  gap: 8px;
  margin-top: 10px;
}
.smp-dvi-shoot {
  position: relative;   /* holds the visually-hidden file input */
  flex: 1 1 0;
  min-width: 0;
  display: flex; align-items: center; justify-content: center; gap: 9px;
  min-height: 52px; padding: 0 12px;
  border: 2px dashed rgba(37,99,235,.4);
  border-radius: 10px;
  background: rgba(37,99,235,.06);
  color: var(--rg-accent);
  font-size: .92rem; font-weight: 700;
  cursor: pointer;
}
.smp-dvi-shoot i { font-size: 1.05rem; }
.smp-dvi-shoot:active { background: rgba(37,99,235,.13); }

/* The item can't be submitted without one — say so on the control that
   fixes it, not only in the label above. */
.smp-dvi-capture-wanted .smp-dvi-shoot {
  border-color: rgba(220,38,38,.45);
  background: rgba(220,38,38,.07);
  color: var(--rg-danger);
}

/* Set apart, and quieter: something already on the phone is the
   exception, not what a tech reaches for stood at a wheel. */
.smp-dvi-pick {
  position: relative;   /* holds the visually-hidden file input */
  display: flex; align-items: center; justify-content: center; gap: 8px;
  width: 100%; min-height: 40px;
  margin-top: 10px; padding-top: 10px;
  border: 0; border-top: 1px solid var(--rg-border);
  background: none;
  color: var(--rg-muted);
  font-size: .82rem; font-weight: 600;
  cursor: pointer;
}
.smp-dvi-pick:active { color: var(--rg-accent); }

.smp-dvi-media { display: flex; gap: 8px; flex-wrap: wrap; margin-top: 8px; }
.smp-dvi-thumb { position: relative; width: 72px; height: 72px; }
.smp-dvi-thumb img, .smp-dvi-thumb video {
  width: 100%; height: 100%; object-fit: cover; border-radius: 8px; border: 1px solid var(--rg-border);
}
.smp-dvi-thumb-remove {
  position: absolute; top: -6px; right: -6px; width: 20px; height: 20px; border-radius: 50%;
  border: 0; background: var(--rg-danger); color: #fff; font-size: .65rem; line-height: 1; cursor: pointer;
}

/* ── DVI: the link to it from a repair order ── */
.rg-dvi-link {
  display: flex; align-items: center; gap: 12px; width: 100%;
  padding: 14px 16px; margin-bottom: 8px;
  background: var(--rg-surface);
  border: 1px solid var(--rg-border);
  border-radius: var(--rg-radius);
  box-shadow: var(--rg-shadow);
  font-family: inherit; font-size: .9rem; color: var(--rg-text);
  cursor: pointer;
}
.rg-dvi-link:hover { border-color: var(--rg-accent); background: var(--rg-bg); }
/* The row and its remove button: the link fills the space, the bin sits
   outside it — a button inside a button isn't valid, and the whole row
   being clickable shouldn't mean deleting is one stray tap away. */
.rg-dvi-linkrow { display: flex; align-items: center; gap: 6px; margin-bottom: 8px; }
.rg-dvi-linkrow .rg-dvi-link { margin-bottom: 0; }
/* Stacked, not side by side: the button is the action and gets the full
   width; the checklist picker above it only appears when there's more
   than one to choose between. */
.rg-dvi-add { display: flex; flex-direction: column; gap: 8px; margin-bottom: 8px; }
/* A touch bigger than an ordinary ghost button — it's the one action in
   this space, and reads as its own affordance rather than a toolbar
   leftover. */
.rg-dvi-add .rg-btn-ghost {
  justify-content: center;
  height: 44px;
  font-size: .92rem;
  font-weight: 600;
}
.rg-dvi-link-icon  { color: var(--rg-accent); font-size: 1.05rem; flex: none; }
.rg-dvi-link-title { display: block; font-weight: 700; }
.rg-dvi-link-sub   { display: block; font-size: .78rem; color: var(--rg-muted); margin-top: 2px; }
.rg-dvi-link-caret { color: var(--rg-border); font-size: .8rem; flex: none; }

/* ── DVI: the office's review screen ── */

/* ── Folding the quiet rows ──
   71 of the 92 rows on a real inspection say nothing: graded good,
   nothing found, nothing written, nothing photographed. They are the
   proof the check was done, not work, and they were burying the work.
   Every section still appears with its full count, so the sheet reads as
   a complete inspection rather than a filtered one. */
.rg-dvi-secbar {
  display: flex;
  gap: 8px;
  margin-bottom: 12px;
}

/* The section label, now a control. Full width and left-aligned so it
   still reads as a heading and not as a button somebody put in a heading's
   place. */
.rg-dvi-sec-head {
  display: flex;
  align-items: center;
  gap: 6px;
  width: 100%;
  padding: 6px 0;
  border: 0;
  background: none;
  text-align: left;
  cursor: pointer;
}
.rg-dvi-sec-head:hover { color: var(--rg-text); }

/* What is behind the fold, said in the open — never hidden without being
   counted. Lower case and lighter than the label it follows: it is a
   count, not a second heading. */
.rg-dvi-sec-sum {
  margin-left: auto;
  font-weight: 500;
  letter-spacing: 0;
  text-transform: none;
  color: var(--rg-muted);
}

.rg-dvi-fold {
  display: flex;
  align-items: center;
  gap: 8px;
  width: 100%;
  padding: 8px 0;
  border: 0;
  border-top: 1px dashed var(--rg-border);
  background: none;
  font-size: .82rem;
  color: var(--rg-muted);
  text-align: left;
  cursor: pointer;
}
.rg-dvi-fold:hover { color: var(--rg-text); }
.rg-dvi-fold-act { margin-left: auto; text-decoration: underline; }

/* Shown on request, and quieter than the rows that needed somebody — a
   row with nothing to say should not compete with one that has. */
.rg-dvi-row-calm { opacity: .72; }

.rg-dvi-row {
  /* Same reason as .rgd-item: a hundred rows, most carrying photographs,
     and only a dozen on screen at a time. */
  content-visibility: auto;
  contain-intrinsic-size: auto 58px;

  padding: 10px 0;
  border-bottom: 1px solid var(--rg-border);
}
.rg-dvi-row:last-child { border-bottom: 0; }
/* Rows with a video in them stay rendered — see .rgd-item-live. */
.rg-dvi-row-live { content-visibility: visible; contain-intrinsic-size: none; }
/* Which corner of the car this row is about — FL/FR/RL/RR. Monospaced
   and boxed so a column of them lines up and reads as a diagram. */
.rg-dvi-pos {
  flex: none;
  font-family: 'DM Mono', monospace;
  font-size: .68rem; font-weight: 700; letter-spacing: .04em;
  color: var(--rg-muted);
  background: var(--rg-bg);
  border: 1px solid var(--rg-border);
  border-radius: 5px;
  padding: 1px 6px;
}
.rg-dvi-note {
  display: flex; align-items: flex-start; gap: 8px;
  margin-top: 8px; padding: 9px 11px;
  background: var(--rg-bg);
  border: 1px solid var(--rg-border);
  border-radius: 8px;
  font-size: .83rem;
}
.rg-dvi-note-raw { color: var(--rg-muted); }
.rg-dvi-note-out { color: var(--rg-text); margin-top: 5px; }
.rg-dvi-note-who {
  display: inline-block; min-width: 86px;
  font-size: .62rem; font-weight: 700; letter-spacing: .1em;
  text-transform: uppercase; color: var(--rg-muted);
}
.rg-dvi-media { display: flex; gap: 8px; flex-wrap: wrap; margin-top: 8px; }
.rg-dvi-thumb {
  position: relative; width: 84px; height: 84px; padding: 0;
  border: 1px solid var(--rg-border); border-radius: 8px;
  background: var(--rg-bg); overflow: hidden; cursor: pointer;
}
.rg-dvi-thumb:hover { border-color: var(--rg-accent); }
.rg-dvi-thumb img, .rg-dvi-thumb video { width: 100%; height: 100%; object-fit: cover; display: block; }
.rg-dvi-play {
  position: absolute; inset: 0; display: flex; align-items: center; justify-content: center;
  background: rgba(15,23,32,.35); color: #fff; font-size: 1.1rem;
}

/* ── Full-size photo/video viewer ──
   Its own layer over everything: the point is the picture, so the page
   behind it gets out of the way entirely. */
.rg-media-viewer {
  position: fixed; inset: 0; z-index: 2500;
  display: flex; align-items: center; justify-content: center;
  padding: calc(20px + var(--safe-t)) calc(20px + var(--safe-r)) calc(20px + var(--safe-b)) calc(20px + var(--safe-l));
  background: rgba(9,12,17,.92);
}
.rg-media-stage { max-width: 100%; max-height: 100%; text-align: center; }
.rg-media-stage img, .rg-media-stage video {
  max-width: 100%; max-height: calc(100vh - 140px);
  border-radius: 10px; display: block; margin: 0 auto;
}
.rg-media-caption { margin-top: 12px; color: #e5e7eb; font-size: .85rem; }
.rg-media-close, .rg-media-step {
  position: absolute; display: flex; align-items: center; justify-content: center;
  border: 1px solid rgba(255,255,255,.2); border-radius: 50%;
  background: rgba(255,255,255,.1); color: #fff; cursor: pointer;
}
.rg-media-close {
  top: calc(16px + var(--safe-t)); right: calc(16px + var(--safe-r));
  width: 40px; height: 40px; font-size: 1rem;
}
.rg-media-step { top: 50%; margin-top: -24px; width: 48px; height: 48px; font-size: 1.1rem; }
.rg-media-prev { left: calc(12px + var(--safe-l)); }
.rg-media-next { right: calc(12px + var(--safe-r)); }
.rg-media-close:hover, .rg-media-step:hover { background: rgba(255,255,255,.2); }

/* ── Rates for one invoice ──
   Set apart from the profile picker above it: these numbers apply to
   this repair order and nothing else, and that has to be obvious at a
   glance or someone will think they've edited the shop's rate card. */
.rg-rates {
  border-top: 1px solid var(--rg-border);
  border-bottom: 2px solid var(--rg-border);
  background: linear-gradient(180deg, rgba(217,119,6,.07), transparent);
  box-shadow: inset 3px 0 0 rgba(217,119,6,.5);
}

/* ============================================================
   DIALOGS
   The app's own confirm/prompt. The browser's block the page,
   can't be styled, say "localhost says", and can't carry the
   list of what is about to happen — which is usually the whole
   point of asking.
   ============================================================ */
.rg-dialog-backdrop {
  position: fixed;
  inset: 0;
  z-index: 2000;
  display: flex;
  align-items: center;
  justify-content: center;
  padding: calc(20px + var(--safe-t)) 16px calc(20px + var(--safe-b));
  background: rgba(16,24,40,.45);
  animation: rg-dialog-fade .12s ease-out;
}
@keyframes rg-dialog-fade { from { opacity: 0; } to { opacity: 1; } }

.rg-dialog {
  width: 100%;
  max-width: 460px;
  max-height: 100%;
  overflow-y: auto;
  background: var(--rg-surface);
  border-radius: 14px;
  box-shadow: 0 20px 60px rgba(16,24,40,.3);
  animation: rg-dialog-rise .16s ease-out;
}
@keyframes rg-dialog-rise {
  from { opacity: 0; transform: translateY(8px) scale(.98); }
  to   { opacity: 1; transform: none; }
}

.rg-dialog-head {
  display: flex;
  align-items: center;
  gap: 10px;
  padding: 16px 18px 0;
  font-size: 1rem;
  font-weight: 700;
  color: var(--rg-text);
}
.rg-dialog-head i        { color: var(--rg-accent); }

/* The payment dialog is a form, not a question, so it gets more room
   than a confirm and a subtitle saying which ticket is being paid. */
.rg-pay-dialog { max-width: 520px; }
.rg-bay-dialog { max-width: 560px; }

/* The shared dialog body ends at 4px, because a confirm's buttons live
   in their own footer that supplies the space. This one carries its
   buttons inside the body, so it has to supply its own — without it the
   Record Payment button sits on the dialog's edge. */
.rg-pay-dialog .rg-dialog-body { padding-bottom: 18px; }
.rg-dialog-sub {
  margin-left: auto;
  font-size: .74rem;
  font-weight: 500;
  color: var(--rg-muted);
  white-space: nowrap;
}

/* The one thing worth reading before typing an amount. */
.rg-pay-elsewhere {
  display: flex;
  gap: 8px;
  margin-bottom: 14px;
  padding: 10px 12px;
  border: 1px solid var(--rg-border);
  border-radius: 9px;
  background: var(--rg-bg);
  font-size: .78rem;
  line-height: 1.5;
  color: var(--rg-muted);
}
.rg-pay-elsewhere i { color: var(--rg-accent); margin-top: 2px; }
.rg-pay-elsewhere strong { color: var(--rg-text); }
.rg-dialog-danger        { color: var(--rg-danger) !important; }

.rg-dialog-body    { padding: 12px 18px 4px; }
.rg-dialog-message { font-size: .88rem; color: var(--rg-text); line-height: 1.6; white-space: pre-line; }

/* What is about to happen, itemised — the thing a browser dialog
   can't show without turning into a wall of text. */
.rg-dialog-detail {
  margin: 10px 0 0;
  padding: 10px 12px 10px 28px;
  border-radius: 8px;
  background: var(--rg-bg);
  font-size: .82rem;
  color: var(--rg-muted);
  line-height: 1.7;
  max-height: 200px;
  overflow-y: auto;
}
.rg-dialog-error { margin-top: 6px; font-size: .78rem; color: var(--rg-danger); }

.rg-dialog-actions {
  display: flex;
  justify-content: flex-end;
  gap: 8px;
  padding: 14px 18px 16px;
}

/* The standing hint that "/" does something — low-key on purpose, a
   corner of the page rather than a banner, since it's for whoever
   glances down and wonders, not something that needs reading once. */
.rg-cmdk-hint {
  position: fixed;
  left: calc(14px + var(--safe-l));
  bottom: calc(14px + var(--safe-b));
  z-index: 900;
  align-items: center;
  gap: 7px;
  padding: 6px 11px;
  border: 1px solid var(--rg-border);
  border-radius: 999px;
  background: var(--rg-surface);
  box-shadow: var(--rg-shadow);
  font-size: .74rem;
  color: var(--rg-muted);
  pointer-events: none;
  opacity: .85;
}
.rg-cmdk-hint kbd {
  padding: 1px 6px;
  border: 1px solid var(--rg-border);
  border-radius: 4px;
  background: var(--rg-bg);
  color: var(--rg-muted);
  font-family: inherit;
  font-size: .74rem;
}

/* ── Command palette (press "/" anywhere) ──
   Anchored higher than rg-dialog and wider — this is a list you scan and
   a field you type into, not a question with a couple of buttons. */
.rg-cmdk-backdrop {
  position: fixed;
  inset: 0;
  z-index: 2100;
  display: flex;
  justify-content: center;
  padding: calc(72px + var(--safe-t)) 16px calc(20px + var(--safe-b));
  background: rgba(16,24,40,.45);
  animation: rg-dialog-fade .12s ease-out;
}
.rg-cmdk {
  width: 100%;
  max-width: 560px;
  max-height: 100%;
  height: fit-content;
  display: flex;
  flex-direction: column;
  background: var(--rg-surface);
  border-radius: 14px;
  box-shadow: 0 20px 60px rgba(16,24,40,.35);
  animation: rg-dialog-rise .16s ease-out;
  overflow: hidden;
}
.rg-cmdk-input-row {
  display: flex;
  align-items: center;
  gap: 10px;
  padding: 14px 18px;
  border-bottom: 1px solid var(--rg-border);
}
.rg-cmdk-slash {
  flex: none;
  width: 22px;
  height: 22px;
  display: flex;
  align-items: center;
  justify-content: center;
  border-radius: 5px;
  background: var(--rg-bg);
  color: var(--rg-muted);
  font-size: .72rem;
}

/* The "narrowed to one kind of record" chip — a Gmail/Slack-filter
   look, so it reads as a removable condition on the search rather than
   a label sitting in front of it. */
.rg-cmdk-scope {
  flex: none;
  display: flex;
  align-items: center;
  gap: 6px;
  padding: 4px 6px 4px 9px;
  border-radius: 999px;
  background: rgba(37,99,235,.1);
  color: var(--rg-accent);
  font-size: .78rem;
  font-weight: 600;
  white-space: nowrap;
}
.rg-cmdk-scope button {
  display: flex;
  align-items: center;
  justify-content: center;
  width: 16px;
  height: 16px;
  border: none;
  border-radius: 50%;
  background: transparent;
  color: inherit;
  font-size: .68rem;
  cursor: pointer;
}
.rg-cmdk-scope button:hover { background: rgba(37,99,235,.18); }
.rg-cmdk-input {
  flex: 1 1 auto;
  min-width: 0;
  border: none;
  outline: none;
  background: transparent;
  font-family: inherit;
  font-size: 1rem;
  color: var(--rg-text);
}
.rg-cmdk-input::placeholder { color: var(--rg-muted); }
.rg-cmdk-spin { flex: none; color: var(--rg-muted); font-size: .85rem; }

.rg-cmdk-list  { padding: 6px; overflow-y: auto; max-height: 360px; }
.rg-cmdk-group {
  padding: 10px 10px 4px;
  font-size: .68rem;
  font-weight: 700;
  letter-spacing: .06em;
  text-transform: uppercase;
  color: var(--rg-muted);
}
.rg-cmdk-group:first-child { padding-top: 4px; }
.rg-cmdk-item {
  display: flex;
  align-items: center;
  gap: 11px;
  width: 100%;
  padding: 8px 10px;
  border: none;
  border-radius: 8px;
  background: transparent;
  text-align: left;
  cursor: pointer;
}
.rg-cmdk-item.active { background: var(--rg-bg); }
.rg-cmdk-icon {
  flex: none;
  width: 30px;
  height: 30px;
  display: flex;
  align-items: center;
  justify-content: center;
  border-radius: 8px;
  background: rgba(37,99,235,.1);
  color: var(--rg-accent);
  font-size: .85rem;
}
.rg-cmdk-text  { flex: 1 1 auto; min-width: 0; display: flex; flex-direction: column; gap: 1px; }
.rg-cmdk-label { font-size: .88rem; font-weight: 600; color: var(--rg-text); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.rg-cmdk-sub   { font-size: .74rem; color: var(--rg-muted); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.rg-cmdk-go    { flex: none; color: var(--rg-muted); font-size: .8rem; }

.rg-cmdk-empty {
  padding: 24px 18px;
  text-align: center;
  font-size: .84rem;
  color: var(--rg-muted);
}

.rg-cmdk-foot {
  display: flex;
  gap: 16px;
  padding: 9px 18px;
  border-top: 1px solid var(--rg-border);
  background: var(--rg-bg);
  font-size: .72rem;
  color: var(--rg-muted);
}
.rg-cmdk-foot span { display: flex; align-items: center; gap: 5px; }
.rg-cmdk-foot i    { font-size: .68rem; }
.rg-cmdk-foot kbd {
  padding: 1px 5px;
  border: 1px solid var(--rg-border);
  border-radius: 4px;
  /* Bootstrap's default <kbd> is white-on-dark — it only sets a
     background here without also overriding Bootstrap's white text,
     which is unreadable against this light one. */
  background: var(--rg-surface);
  color: var(--rg-muted);
  font-family: inherit;
  font-size: .68rem;
}
/* The way out for a screen with no Escape key. */
.rg-cmdk-x {
  display: none;
  flex: none;
  align-items: center;
  justify-content: center;
  width: 34px; height: 34px;
  border: 0;
  border-radius: 9px;
  background: var(--rg-bg);
  color: var(--rg-muted);
}

/* On a phone the palette is the screen, not a card floating on it. It
   is the only way to reach a record from a page with no list on it, and
   a 560px card inset 16px from every edge wastes the half of the screen
   that isn't covered by the keyboard. */
@media (max-width: 991.98px) {
  .rg-cmdk-backdrop { padding: 0; background: rgba(16,24,40,.2); }
  .rg-cmdk {
    max-width: none;
    width: 100%;
    height: 100%;
    max-height: 100%;
    border-radius: 0;
    animation: none;
  }
  .rg-cmdk-input-row { padding: calc(10px + var(--safe-t)) 12px 10px; }
  /* 16px or iOS zooms the whole page the moment it takes focus. */
  .rg-cmdk-input { font-size: 16px; }
  .rg-cmdk-list  { flex: 1; max-height: none; }
  .rg-cmdk-x     { display: flex; }
  /* Every hint on it is a key this device does not have. */
  .rg-cmdk-foot  { display: none; }
  /* Rows a thumb can hit. */
  .rg-cmdk-item  { padding-block: 11px; }
}

/* The method picker sits beside the amount, which is deliberately
   oversized — a combo left at its default height next to a 46px field
   reads as two rows that failed to line up. */
.rg-pay-card .rg-combo .rg-combo-input { height: 46px; font-size: .95rem; }

/* A locked line's type reads as text, not as a control that refuses —
   a greyed dropdown still shows an arrow, which offers something the
   invoice will not do. */
.rg-cell-static {
  display: inline-block;
  padding: 6px 2px;
  font-size: .8rem;
  color: var(--rg-text);
}

/* A line moved by the rate preview, not yet saved. Marked so nobody
   reads an unsaved number as the invoice. */
/* Cost sits behind the price it explains, not beside it as an equal. */
.rg-cell-cost { color: var(--rg-muted); }
.rg-cell-cost:focus { color: var(--rg-text); }

.rg-line-preview > td { background: rgba(217,119,6,.07); }
.rg-line-preview .rg-cell-input { color: var(--rg-warn); font-weight: 600; }

/* A rate typed but not yet applied — the same amber, so "changed and
   unsaved" looks the same wherever it appears. */
.rg-input-changed,
.rg-input-changed:focus {
  border-color: var(--rg-warn);
  background: rgba(217,119,6,.06);
  color: var(--rg-warn);
  font-weight: 600;
}

/* ============================================================
   ACCOUNT MENU
   Settings and Log out belong to whoever is signed in, so they
   sit under the person rather than loose in the bar — which
   also gives the nav back the width those two controls took.
   ============================================================ */
.smp-account { flex-shrink: 0; }

/* The settings cog, immediately left of the account button. Sized and
   coloured off .smp-bell so the row of controls at the end of the bar
   reads as one set rather than three borrowed styles; it is an <a>, not
   a <button>, because it goes to a page. */
.smp-navcog {
  display: flex;
  align-items: center;
  justify-content: center;
  flex-shrink: 0;
  width: 36px; height: 36px;
  border: 1px solid transparent;
  border-radius: 9px;
  background: transparent;
  color: rgba(255,255,255,.7);
  font-size: .95rem;
  text-decoration: none;
  transition: background .12s, color .12s, border-color .12s;
}
.smp-navcog:hover  { background: rgba(255,255,255,.08); color: #fff; }
.smp-navcog.active { background: rgba(255,255,255,.12); color: #fff; border-color: rgba(255,255,255,.18); }

.smp-account-btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 36px; height: 36px;
  padding: 0;
  border: 1px solid rgba(255,255,255,.14);
  border-radius: 50%;
  background: transparent;
  cursor: pointer;
  transition: border-color .12s, background .12s;
}
.smp-account-btn:hover,
.smp-account-btn[aria-expanded="true"] {
  border-color: rgba(255,255,255,.35);
  background: rgba(255,255,255,.08);
}
/* Bootstrap's caret would crowd a 36px circle. */
.smp-account-btn.dropdown-toggle::after { display: none; }

.smp-account-mark {
  font-size: .72rem;
  font-weight: 700;
  letter-spacing: .02em;
  color: #e5e7eb;
}

/* Whose account this is — the thing that makes the two items below
   read as belonging to a person. */
.smp-menu-who  { padding: 8px 12px 4px; }
.smp-menu-name { font-size: .85rem; font-weight: 700; color: #fff; }
.smp-menu-mail { font-size: .72rem; color: #9ca3af; }

.smp-menu-out       { color: #fca5a5 !important; }
.smp-menu-out:hover { background: rgba(220,38,38,.15) !important; color: #fff !important; }

/* A link sitting in a field's label, next to what it manages. */
.rg-label-link {
  float: right;
  font-weight: 500;
  text-transform: none;
  letter-spacing: 0;
  font-size: .72rem;
}

/* ============================================================
   LIST TOOLS — filters, sortable headings, and paging.
   Shared by the customer and vehicle books, which are long
   enough that neither fits on one screen or in one query.
   ============================================================ */

/* ── Filter bar ──
   Collapsed to a single row of buttons until somebody wants more.
   The panel underneath is where the less common questions live, so
   the common case — search, and one of three quick filters — costs
   no vertical space at all. */
.rg-filterbar {
  display: flex;
  align-items: center;
  flex-wrap: wrap;
  gap: 8px;
  margin-bottom: 12px;
}
.rg-filterbar .rg-btn-ghost.active {
  border-color: var(--rg-accent);
  color: var(--rg-accent);
  background: rgba(37,99,235,.05);
}

/* The count of what came back, sitting with the controls that
   changed it. */
.rg-filter-total {
  margin-left: auto;
  font-size: .78rem;
  color: var(--rg-muted);
  white-space: nowrap;
}
.rg-filter-total b { color: var(--rg-text); }

.rg-filter-panel {
  background: var(--rg-surface);
  border: 1px solid var(--rg-border);
  border-radius: var(--rg-radius);
  padding: 14px 16px 16px;
  margin-bottom: 12px;
  box-shadow: var(--rg-shadow);
}
.rg-filter-grid {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(170px, 1fr));
  gap: 12px 14px;
}
.rg-filter-field .rg-label { margin-bottom: 3px; }
.rg-filter-field select.rg-input,
.rg-filter-field input.rg-input { min-height: 36px; font-size: .82rem; }

/* Two dates that mean one range. */
.rg-filter-range { display: flex; align-items: center; gap: 6px; }
.rg-filter-range .rg-input { min-width: 0; }
.rg-filter-range span { font-size: .75rem; color: var(--rg-muted); }

.rg-filter-foot {
  display: flex;
  align-items: center;
  gap: 8px;
  margin-top: 14px;
  padding-top: 12px;
  border-top: 1px solid var(--rg-border);
}

/* ── Active filters ──
   What is currently narrowing the list, each with the way to take it
   off. Without these a filter set three panels ago goes on quietly
   hiding rows, and the list just looks wrong. */
.rg-active-filters { display: flex; align-items: center; flex-wrap: wrap; gap: 6px; margin-bottom: 12px; }
.rg-active-filter {
  display: inline-flex;
  align-items: center;
  gap: 7px;
  background: rgba(37,99,235,.07);
  border: 1px solid rgba(37,99,235,.22);
  border-radius: 20px;
  padding: 3px 6px 3px 11px;
  font-size: .76rem;
  font-weight: 500;
  color: var(--rg-accent);
  max-width: 260px;
}
.rg-active-filter-key {
  font-size: .66rem;
  font-weight: 700;
  letter-spacing: .06em;
  text-transform: uppercase;
  opacity: .75;
}
.rg-active-filter-val { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.rg-active-filter button {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 18px;
  height: 18px;
  border: none;
  border-radius: 50%;
  background: transparent;
  color: inherit;
  font-size: .7rem;
  cursor: pointer;
  flex: none;
}
.rg-active-filter button:hover { background: rgba(37,99,235,.18); }

/* ── Sortable column headings ──
   The whole heading is the control, so there's no small target to
   hunt for. The arrow only appears on the column actually doing the
   sorting; a full set of grey arrows reads as decoration. */
.rg-table th.rg-th-sort { padding: 0; }
.rg-th-sort button {
  display: inline-flex;
  align-items: center;
  gap: 5px;
  width: 100%;
  border: none;
  background: none;
  padding: 10px 16px;
  font: inherit;
  color: inherit;
  letter-spacing: inherit;
  text-transform: inherit;
  cursor: pointer;
  white-space: nowrap;
}
.rg-table th.rg-th-sort.text-end button { justify-content: flex-end; }
.rg-th-sort button:hover { color: var(--rg-text); background: var(--rg-bg); }
.rg-th-sort button.active { color: var(--rg-accent); }
.rg-th-sort i { font-size: .62rem; opacity: 0; }
.rg-th-sort button.active i { opacity: 1; }
.rg-th-sort button:hover i { opacity: .45; }
.rg-th-sort button.active:hover i { opacity: 1; }

/* ── Pager ──
   Under the table, where you arrive after reading it. */
.rg-pager {
  display: flex;
  align-items: center;
  flex-wrap: wrap;
  gap: 10px 14px;
  padding: 12px 16px;
  border-top: 1px solid var(--rg-border);
  background: var(--rg-surface);
  border-radius: 0 0 var(--rg-radius) var(--rg-radius);
}
.rg-pager-count { font-size: .8rem; color: var(--rg-muted); }
.rg-pager-count b { color: var(--rg-text); font-weight: 600; }

.rg-pager-size { display: flex; align-items: center; gap: 6px; margin-left: auto; }
.rg-pager-size label {
  font-size: .68rem; font-weight: 700; letter-spacing: .08em;
  text-transform: uppercase; color: var(--rg-muted); margin: 0;
}
.rg-pager-select { width: auto; min-height: 32px; padding: 3px 8px; font-size: .8rem; }

.rg-pager-nav { display: flex; align-items: center; gap: 4px; flex-wrap: wrap; }
.rg-page-btn {
  min-width: 32px;
  height: 32px;
  padding: 0 8px;
  border: 1px solid var(--rg-border);
  border-radius: 7px;
  background: var(--rg-surface);
  color: var(--rg-text);
  font-size: .8rem;
  font-weight: 500;
  cursor: pointer;
  transition: border-color .12s, color .12s, background .12s;
}
.rg-page-btn:hover:not(:disabled) { border-color: var(--rg-accent); color: var(--rg-accent); }
.rg-page-btn.active {
  background: var(--rg-accent);
  border-color: var(--rg-accent);
  color: #fff;
  font-weight: 700;
}
.rg-page-btn:disabled { opacity: .45; cursor: default; }
/* The ellipsis is a gap, not a destination. */
.rg-page-btn.gap { border-color: transparent; background: none; opacity: 1; min-width: 20px; padding: 0; }

/* On a phone the rows-per-page control takes the second line to
   itself rather than squeezing the numbers off the edge. */
@media (max-width: 575.98px) {
  .rg-pager { gap: 8px; }
  .rg-pager-count { width: 100%; }
  .rg-pager-size { margin-left: 0; }
  .rg-pager-nav { width: 100%; justify-content: center; }
}

/* ── Money that isn't owed ──
   A cancelled ticket owes nothing, but the figure it would have carried
   is still worth showing. Said quietly, and never in the colour the
   screens use for debt, so it can't be mistaken for a charge. */
.rg-sub-void {
  color: var(--rg-muted);
  font-style: italic;
  font-weight: 400;
}
.rg-stat-note {
  margin-top: 3px;
  font-size: .72rem;
  font-style: italic;
  color: var(--rg-muted);
}
.rg-balance-note {
  margin-top: 4px;
  font-size: .75rem;
  font-style: italic;
  color: var(--rg-muted);
}

/* ============================================================
   EDITOR MODAL — the customer and vehicle editors, and anything
   else that wants a full form in a popup rather than a page.
   A bigger sibling of .rg-dialog: same backdrop idea, but sized
   for a multi-card form with its own scrolling body between a
   sticky head and foot, and edge-to-edge on a phone.
   ============================================================ */
.rg-modal-backdrop {
  position: fixed;
  inset: 0;
  z-index: 1900;
  display: flex;
  align-items: center;
  justify-content: center;
  padding: calc(20px + var(--safe-t)) 16px calc(20px + var(--safe-b));
  background: rgba(16,24,40,.45);
  animation: rg-dialog-fade .12s ease-out;
}

.rg-modal {
  display: flex;
  flex-direction: column;
  width: 100%;
  max-width: 880px;
  max-height: 100%;
  background: var(--rg-bg);
  border-radius: 14px;
  box-shadow: 0 20px 60px rgba(16,24,40,.3);
  overflow: hidden;
  animation: rg-dialog-rise .16s ease-out;
}

.rg-modal-head {
  flex: 0 0 auto;
  background: var(--rg-surface);
  border-bottom: 1px solid var(--rg-border);
  padding: 0 16px;
  min-height: 58px;
  display: flex;
  align-items: center;
  gap: 14px;
}
.rg-modal-title { font-size: 1.05rem; font-weight: 700; letter-spacing: -.2px; }
.rg-modal-sub   { font-size: .72rem; color: var(--rg-muted); margin-top: 1px; }
.rg-modal-head-actions { margin-left: auto; display: flex; align-items: center; gap: 8px; }

.rg-modal-body {
  flex: 1 1 auto;
  overflow-y: auto;
  padding: 16px;
}

.rg-modal-foot {
  flex: 0 0 auto;
  display: flex;
  align-items: center;
  justify-content: flex-end;
  gap: 10px;
  padding: 12px 16px;
  background: var(--rg-surface);
  border-top: 1px solid var(--rg-border);
}
.rg-modal-hint { margin-right: auto; }

/* Edge-to-edge on a phone: a centred card with margins on all sides is
   room the form doesn't have to spare, and it already reads as "the
   current screen" rather than something floating over another one. */
@media (max-width: 767.98px) {
  .rg-modal-backdrop { padding: 0; }
  .rg-modal {
    max-width: none;
    height: 100%;
    max-height: 100%;
    border-radius: 0;
    padding-top: var(--safe-t);
    padding-bottom: var(--safe-b);
  }
  .rg-modal-foot { flex-wrap: wrap; }
  .rg-modal-hint { width: 100%; order: 1; text-align: center; margin: 0 0 6px; }
}

/* ── Cost-tiered parts markup ──
   A short list of ranges, each with its own percentage — compact rows
   rather than a table, since there's rarely more than three or four. */
.rg-tier-rows { display: flex; flex-direction: column; gap: 8px; }
.rg-tier-row {
  display: flex;
  align-items: flex-end;
  gap: 10px;
  padding: 10px 12px;
  border: 1px solid var(--rg-border);
  border-radius: 9px;
  background: var(--rg-surface);
}
.rg-tier-field { flex: 1 1 0; min-width: 0; }
.rg-tier-pct { flex: 0 1 100px; }
.rg-tier-arrow { color: var(--rg-border); padding-bottom: 9px; flex: none; }

@media (max-width: 575.98px) {
  .rg-tier-row { flex-wrap: wrap; }
  .rg-tier-arrow { display: none; }
  .rg-tier-field, .rg-tier-pct { flex: 1 1 40%; }
}

/* ============================================================
   MESSAGE LOG
   Both channels in one table — "did the customer get told?" is
   one question, and nobody asking it knows which queue holds
   the answer.
   ============================================================ */
.rg-log-tally { display: flex; flex-wrap: wrap; gap: 8px; }

/* The whole row opens the detail, so it has to look like it does. */
.rg-log-row { cursor: pointer; }
.rg-log-row:hover { background: var(--rg-bg); }

/* Retries are a footnote to the status, not a second status. */
.rg-log-attempts {
  margin-left: 6px;
  font-family: 'DM Mono', monospace;
  font-size: .72rem;
  color: var(--rg-muted);
}

/* What was actually sent. Sits in the table rather than a dialog: the
   next row down is usually the one you want to compare it with. */
.rg-log-detail {
  padding: 12px 14px !important;
  background: var(--rg-bg);
  white-space: normal;
}
.rg-log-body {
  padding: 10px 12px;
  border: 1px solid var(--rg-border);
  border-radius: 8px;
  background: var(--rg-surface);
  font-size: .84rem;
  /* A queued email keeps its newlines and a text is one long line;
     pre-wrap is the only setting that reads correctly for both. */
  white-space: pre-wrap;
  word-break: break-word;
  max-height: 260px;
  overflow-y: auto;
}

/* Cancel buttons sit at the far end of the tally row: the counts are
   what they act on, and putting them anywhere else would mean reading
   the number in one place and pressing the button in another. Pushed
   right so they never sit adjacent to a filter by accident. */
.rg-log-clear { display: flex; flex-wrap: wrap; gap: 8px; margin-left: auto; }
.rg-log-tally { align-items: center; }

@media (max-width: 575.98px) {
  /* The shared .rg-btn-danger rule goes full width on a phone, which
     inside a wrapping flex row would put one button per line — right for
     an account page, wrong for a pair of secondary actions. */
  .rg-log-clear { width: 100%; margin-left: 0; }
  .rg-log-clear .rg-btn-danger { width: auto; flex: 1 1 auto; }
}

/* ── Texts in the conversation panel ──
   Same bubbles as the email, tinted and marked so the channel is never a
   guess: a shop chasing an unanswered message needs to know whether it
   went to an inbox or a lock screen. Green rather than the accent blue,
   because it reads as "the other pipe" instead of as a state. */
.rg-bubble-channel { margin-right: 5px; font-size: .68rem; opacity: .75; }

.rg-sms .rg-bubble {
  border-color: rgba(22,163,74,.35);
  background: rgba(22,163,74,.06);
}
.rg-sms.rg-mine .rg-bubble { border-color: rgba(22,163,74,.45); }
.rg-sms .rg-bubble-channel { color: var(--rg-success); opacity: 1; }

/* ── "Send updates on this repair order" ──
   A switch, not a notice: it decides whether a customer hears anything
   about this job, and it has to be findable at a glance from the top of
   the panel. Reads as a row rather than a bare checkbox so the
   explanation sits with the thing it explains. */
.rg-notify-toggle {
  display: flex; align-items: flex-start; gap: 10px;
  margin-bottom: 12px; padding: 10px 12px;
  border: 1px solid rgba(37,99,235,.28);
  border-radius: 10px;
  background: rgba(37,99,235,.06);
  cursor: pointer;
}
.rg-notify-toggle input { margin-top: 3px; flex: none; width: 16px; height: 16px; cursor: pointer; }
.rg-notify-text { display: flex; flex-direction: column; gap: 2px; min-width: 0; }
.rg-notify-title { font-size: .85rem; font-weight: 700; color: var(--rg-text); }
.rg-notify-title i { margin-right: 6px; color: var(--rg-accent); }
.rg-notify-note { font-size: .76rem; color: var(--rg-muted); }

/* Off is the quiet state, not an alarm — a completed job with updates
   switched off is the normal end of every ticket. */
.rg-notify-off {
  border-color: var(--rg-border);
  background: var(--rg-bg);
}
.rg-notify-off .rg-notify-title { color: var(--rg-muted); }
.rg-notify-off .rg-notify-title i { color: var(--rg-muted); }

/* Catching a message before the cron pass takes it. Sits with the
   delivery state it contradicts, and is deliberately small: cancelling
   is the exception, and the usual thing to do with a queued message is
   let it go. */
.rg-bubble-cancel {
  margin-left: 8px;
  padding: 1px 8px;
  border: 1px solid var(--rg-border);
  border-radius: 20px;
  background: var(--rg-surface);
  font-family: inherit; font-size: .68rem; font-weight: 700;
  color: var(--rg-muted);
  cursor: pointer;
}
.rg-bubble-cancel i { margin-right: 4px; font-size: .62rem; }
.rg-bubble-cancel:hover { border-color: var(--rg-danger); color: var(--rg-danger); }
.rg-bubble-cancel:disabled { opacity: .6; cursor: default; }

/* ── The buttons that end a step ──
   "Finish Front Left", "No damage — start the inspection", "Submit to
   office". These are the moments a technician commits to something, and
   they are pressed with a gloved thumb on a phone held in one hand at
   arm's length over an engine bay — a 36px bar sized for a mouse is the
   wrong target for all three.

   The class was on the markup with no rule behind it, so these buttons
   were only ever full-width, never bigger. Taller, larger type, and a
   shadow so the eye lands on it after the last item in the section.
   Centred, because a full-width button with left-aligned text reads as
   a list row rather than a control. */
.smp-dvi-cta {
  justify-content: center;
  height: 54px;
  border-radius: 10px;
  font-size: 1rem;
  font-weight: 700;
  letter-spacing: .01em;
  box-shadow: 0 2px 8px rgba(37,99,235,.3);
}
.smp-dvi-cta i { font-size: 1rem; }
.smp-dvi-cta:disabled { opacity: .5; box-shadow: none; }

/* Bigger still where it matters most, and clear of the home indicator. */
@media (max-width: 575px) {
  .smp-dvi-cta { height: 58px; font-size: 1.05rem; }
}

/* ============================================================
   VERSION BADGE
   Always on screen, on every route, signed in or not — the whole
   point is that a screenshot from dev can never be mistaken for
   one from live.
   ============================================================ */
.smp-version {
  position: fixed;
  z-index: 880;                      /* under the "/" hint (900) and the toast (1060) */
  left: calc(14px + var(--safe-l));
  /* Clear of the "/" hint, which owns this corner on desktop. Written
     as its parts so it survives the hint changing size: the hint sits
     14px up and stands about 30px tall, and this leaves 16px of air
     above it rather than the 6px it had. */
  bottom: calc(14px + 30px + 16px + var(--safe-b));
  display: inline-flex;
  align-items: center;
  gap: 5px;
  padding: 2px 8px;
  border-radius: 20px;
  /* Background on ::before — it is fixed against the bottom edge, and
     on dev it is bright yellow. See EDGE PAINT. */
  border: 1px solid var(--rg-border);
  font-family: 'DM Mono', ui-monospace, monospace;
  font-size: .66rem;
  line-height: 1.6;
  color: var(--rg-muted);
  /* Never intercepts a click. It is a label, not a control, and it
     sits over whatever the page happens to put in that corner. */
  pointer-events: none;
  user-select: none;
  opacity: .78;
}
.smp-version::before {
  content: '';
  position: absolute;
  inset: 0;
  z-index: -1;
  border-radius: inherit;
  background: var(--rg-surface);
}
.smp-version i { font-size: .6rem; }

/* Not a release. Amber, named, and impossible to read as production. */
.smp-version-dev::before { background: var(--rg-dvi-yellow); }
.smp-version-dev {
  border-color: var(--rg-dvi-yellow);
  color: var(--rg-dvi-yellow-ink);
  opacity: 1;
  font-weight: 500;
}

/* On a phone the "/" hint is gone, so the badge drops to the bottom of
   the screen — and stops there, rather than reserving room for a bar
   that may not be on this page. The nav is signed-in office screens
   only: on the sign-in page, on a customer's report and for a
   technician there is nothing down there to clear, and the badge was
   floating 64px up from the bottom for no reason. */
@media (max-width: 991.98px) {
  .smp-version     { bottom: calc(8px + var(--safe-b)); }
  .smp-version-nav { bottom: calc(var(--nav-h) + var(--safe-b) + 8px); }
}

/* ── The technician's clock bar ──
   Fixed at every width, so this sits AFTER the phone rules above: a
   media query adds no specificity, and one class beats one class by
   source order alone — written before them, the phone rule would drop
   the badge back onto the clock.

   Measured, not assumed: portal.js writes the bar's real height to
   --smp-clockbar-h, because it wraps on a narrow phone and the 104px
   default is only what the first paint uses. It also CLEARS it to the
   safe-area inset when the bar is not there, which is the whole of the
   DVI view — otherwise the last measured height stays behind and the
   badge floats above a bar that has gone.

   An earlier version cleared the bottom nav's 64px here on the stated
   grounds that the two bars are "the same height by design". They are
   not, and the badge sat on the clock. */
.smp-version-clock { bottom: calc(var(--smp-clockbar-h, 104px) + 8px); }

/* Off in print. A fixed badge lands in a different place on every
   printed page and says nothing an invoice needs to carry — the
   printed documents build their own header from shop_profile(). */
@media print {
  .smp-version { display: none; }
}

/* ── First-run setup ──
   Wears the sign-in card. The only additions are the explanation above
   the form and the password read-out, since this is the one account on
   the install and there is no administrator above it to notice a weak
   one. */
.smp-setup-lede {
  margin: 0 0 18px;
  font-size: .88rem;
  line-height: 1.55;
  color: var(--rg-muted);
}

.smp-pw { display: flex; align-items: center; gap: 10px; margin-top: 8px; }
.smp-pw-bar {
  flex: 1 1 auto;
  height: 4px;
  border-radius: 2px;
  background: var(--rg-border);
  overflow: hidden;
}
.smp-pw-fill {
  display: block;
  height: 100%;
  border-radius: 2px;
  transition: width .18s ease, background .18s ease;
}
.smp-pw-weak   { background: var(--rg-danger); }
.smp-pw-ok     { background: var(--rg-dvi-yellow); }
.smp-pw-strong { background: var(--rg-success); }

.smp-pw-label { flex: none; font-size: .72rem; font-weight: 600; }
.smp-pw-text-weak   { color: var(--rg-danger); }
.smp-pw-text-ok     { color: var(--rg-warn); }
.smp-pw-text-strong { color: var(--rg-success); }

@media (prefers-reduced-motion: reduce) {
  .smp-pw-fill { transition: none; }

  /* The pull itself still tracks the finger — that is the gesture, not
     decoration. The spinner keeps turning too: it is the only thing
     saying the reload is under way, and a still spinner says the
     opposite. It just turns without the scale-in that precedes it. */
  .smp-ptr { transition: none; }
}

/* ══════════════════════════════════════════════════════════════
   DIGITAL VEHICLE INSPECTION — the report, and building it
   ══════════════════════════════════════════════════════════════
   Two consumers, one stylesheet: the customer's own page at
   /dvi-report/<code> and the preview pane on the office screen. The
   .rgd- rules below are the report itself and are deliberately
   self-contained — they must look the same whichever of the two is
   drawing them, because they are the same markup.
   ============================================================== */

/* ── The office screen: edit on the left, report on the right ── */
.smp-dvi2 { display: block; }

@media (min-width: 992px) {
  .smp-dvi2 {
    display: grid;
    /* Even halves. The two sides are read against each other — change a
       row on the left, see what it did on the right — and a document
       squeezed into a third of the screen is not something anybody can
       judge before sending it to a customer. */
    grid-template-columns: minmax(0, 1fr) minmax(0, 1fr);
    /* THE ROW MUST BE CLAMPED TOO. A height on the container does not
       constrain an auto-sized row: the row grows to whichever pane has
       the most content — a hundred-row checklist — and both panes grow
       with it, so the report never overflows its box and never gets a
       scrollbar. minmax(0, 1fr) pins the row to the container and lets
       the panes shrink inside it, which is what makes the overflow real. */
    grid-template-rows: minmax(0, 1fr);
    gap: 18px;
    align-items: stretch;

    /* Each pane scrolls on its own, so working down a hundred-row
       checklist never moves the report, and reading to the bottom of the
       report never loses the writer's place in the checklist. The page
       itself stops scrolling: 58px of sticky topbar plus the content's
       own top padding is the chrome this has to live under. */
    height: calc(100vh - 58px - 26px);
  }

  /* Nothing to show on the right yet. The checklist takes the width
     rather than sitting in half a screen beside a blank. */
  .smp-dvi2.smp-dvi2-solo { grid-template-columns: minmax(0, 1fr); }

  /* Both of these are grid items, and a grid item will not shrink below
     its content unless it is told it may — without min-height:0 the pane
     grows to fit the checklist and the PAGE scrolls instead, which is the
     exact behaviour being replaced. */
  .smp-dvi2-edit {
    min-height: 0;
    overflow-y: auto;
    /* Room for the scrollbar so it doesn't sit on the controls. */
    padding-right: 4px;
  }

  .smp-dvi2-preview {
    min-height: 0;
    display: flex;
    flex-direction: column;
  }
}

.smp-dvi2-preview {
  border: 1px solid var(--rg-border, #e5e7eb);
  border-radius: 12px;
  background: #fff;
  overflow: hidden;
}
.smp-dvi2-preview-bar {
  display: flex;
  align-items: center;
  padding: 9px 12px;
  border-bottom: 1px solid var(--rg-border, #e5e7eb);
  background: var(--rg-surface, #f9fafb);
  font-size: .78rem;
  font-weight: 700;
  color: var(--rg-muted, #6b7280);
  text-transform: uppercase;
  letter-spacing: .04em;
}
/* The report's own scroll, under a header that stays put. min-height:0
   for the same reason the panes need it — this is a flex child. */
.smp-dvi2-preview-body {
  flex: 1;
  min-height: 0;
  overflow-y: auto;
  /* The report is a document and wants a margin. Without this it runs
     into the pane's border on both sides and reads like a fragment of a
     page rather than the page itself. */
  padding: 16px 20px 24px;
}

/* The document inside keeps the measure the customer's own page gives it
   (.rgd-page is 720px), centred in whatever the pane happens to be. The
   pane is half the screen; the report is not — letting it stretch to 940
   on a wide monitor would make the preview wider than the thing it is
   previewing, which is the one thing this pane must never be. */
.smp-dvi2-preview-body .rgd {
  max-width: 720px;
  margin-inline: auto;
}

/* The tab toggle, and ONLY below the breakpoint. Deliberately not
   Bootstrap's d-none/d-lg-block: those carry !important, so d-lg-block
   overrode the pane's own display:flex at exactly the width where the
   two-pane layout needs it — which is what stopped the report pane
   scrolling. Above the breakpoint this class does nothing at all and the
   pane's own rules stand. */
@media (max-width: 991.98px) {
  .smp-dvi2-off { display: none; }
}

/* Tabs, below the breakpoint. Hiding the preview entirely on a phone
   would hide the point of the page. */
.smp-dvi2-tabs { display: flex; gap: 6px; margin-bottom: 12px; }
.smp-dvi2-tabs button {
  flex: 1;
  padding: 9px 10px;
  border: 1px solid var(--rg-border, #e5e7eb);
  border-radius: 9px;
  background: #fff;
  font-size: .85rem;
  font-weight: 600;
}
.smp-dvi2-tabs button.active {
  border-color: var(--rg-accent);
  background: rgba(37,99,235,.08);
  color: var(--rg-accent);
}

/* ── The worklist ── */
.smp-todo {
  margin-bottom: 12px;
  border: 1px solid var(--rg-border, #e5e7eb);
  border-radius: 12px;
  background: #fff;
  overflow: hidden;
}
.smp-todo-head {
  padding: 10px 14px;
  border-bottom: 1px solid var(--rg-border, #e5e7eb);
  font-size: .8rem;
  font-weight: 700;
  text-transform: uppercase;
  letter-spacing: .04em;
  color: var(--rg-muted, #6b7280);
}
.smp-todo-row {
  display: flex;
  align-items: center;
  gap: 10px;
  width: 100%;
  padding: 11px 14px;
  border: 0;
  border-top: 1px solid var(--rg-border, #e5e7eb);
  background: transparent;
  text-align: left;
  font-size: .88rem;
}
.smp-todo-row:first-of-type { border-top: 0; }
.smp-todo-static            { cursor: default; }
.smp-todo-n {
  min-width: 26px;
  padding: 2px 8px;
  border-radius: 999px;
  background: rgba(217,119,6,.16);
  color: #92400e;
  font-weight: 700;
  font-variant-numeric: tabular-nums;
  text-align: center;
}
.smp-todo-soft { background: rgba(0,0,0,.06); color: var(--rg-muted, #6b7280); }
.smp-todo-ready {
  padding: 10px 14px;
  border-top: 1px solid var(--rg-border, #e5e7eb);
  color: #15803d;
  font-size: .85rem;
  font-weight: 600;
}

/* ── The report's address ── */
.smp-code {
  display: flex;
  align-items: center;
  gap: 8px;
  margin-bottom: 12px;
  padding: 8px 12px;
  border: 1px dashed var(--rg-border, #e5e7eb);
  border-radius: 10px;
  background: var(--rg-surface, #f9fafb);
}
.smp-code-label {
  font-size: .7rem; font-weight: 700; letter-spacing: .05em;
  text-transform: uppercase; color: var(--rg-muted, #6b7280);
}
.smp-code-value {
  flex: 1;
  font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
  font-size: .95rem;
  font-weight: 700;
  letter-spacing: .06em;
}

/* On the report, or not. */
.smp-inc {
  width: 28px; height: 28px;
  border: 1px solid transparent;
  border-radius: 7px;
  background: transparent;
  color: var(--rg-accent);
  font-size: .8rem;
}
.smp-inc.off { color: var(--rg-muted, #9ca3af); }
.smp-inc:hover { background: var(--rg-surface, #f3f4f6); }
.rg-dvi-row-hidden { opacity: .45; }

/* ══ The report itself ══════════════════════════════════════════ */
/* The customer's page mounts into #contentContainer, which is a ROW flex
   box with min-height:0 — every other page's root is .rg-page, which is a
   flex column that fills it. A bare centred div is a flex item that never
   grows, so the report overflowed a collapsed box and could not be
   scrolled at all. It takes the space and owns the scroll. */
.rgd-page {
  flex: 1;
  min-width: 0;
  min-height: 0;
  overflow-y: auto;
  background: var(--rg-bg, #f0f0f0);
}

/* The measure. Separate from the scroller, because one element cannot
   both fill the width to scroll in and be 720px wide. */
.rgd-sheet {
  max-width: 720px;
  margin: 0 auto;
  /* This page wears no chrome at all, which means nothing above it is
     holding the cutout off — the shop's name was the first thing on the
     screen and it sat under the status bar. Every other bar in the app
     pads for this; the one page a customer opens on a phone did not. */
  padding: calc(20px + max(var(--safe-t), var(--edge-fade))) calc(16px + var(--safe-r))
           calc(60px + var(--safe-b)) calc(16px + var(--safe-l));
}
.rgd { color: #111827; }

.rgd-head { margin-bottom: 16px; }
.rgd-shop {
  font-size: .78rem; font-weight: 700; letter-spacing: .06em;
  text-transform: uppercase; color: var(--rg-accent);
}
.rgd-title { margin: 2px 0 4px; font-size: 1.45rem; font-weight: 800; }
.rgd-sub   { color: #4b5563; font-size: .9rem; }
.rgd-code  {
  margin-top: 4px;
  font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
  font-size: .78rem; color: #9ca3af; letter-spacing: .05em;
}

/* How much of this is a decision, said before a hundred rows of it. */
.rgd-asks {
  margin-bottom: 14px;
  padding: 10px 12px;
  border-radius: 10px;
  background: rgba(37,99,235,.07);
  border: 1px solid rgba(37,99,235,.18);
  font-size: .85rem;
  color: #1e40af;
}
.rgd-ask-n { font-weight: 800; }

.rgd-cat {
  margin: 16px 0 6px;
  font-size: .74rem; font-weight: 700; letter-spacing: .06em;
  text-transform: uppercase; color: #6b7280;
}
/* ══ A PART AND ITS CORNERS ═════════════════════════════════════
   The drawing is the PORTAL's car (.smp-car), used class for class so
   the customer's report and the screen the shop filled in are one
   product rather than two that resemble each other. Only what is
   specific to the report lives here.
   ============================================================== */
.rgd-part { margin-bottom: 12px; }
.rgd-part-head {
  font-size: .88rem;
  font-weight: 700;
  margin-bottom: 4px;
}

/* Nothing on a report is tappable, and a pointer cursor on a picture
   promises otherwise. */
.rgd-car .smp-car-tire { cursor: default; }

/* ── The reading sits OUTSIDE its tyre ──
   Inside, it had to fit 34px: "26psi" only just did and "Not applicable"
   did not, so the longest thing a wheel might say was the thing that
   decided how wide every wheel was drawn. Outside, the tyre is a tyre and
   the number is free to be as long as it needs.

   Left wheels read leftwards and right wheels rightwards, each centred on
   its own axle, so the four readings sit in two clean columns either side
   of the car.

   Scoped to .smp-car-sm — the report's size — so the technician's car,
   where the label is a progress count that belongs on the wheel it counts,
   is untouched. */
.smp-car-sm .smp-car-count {
  position: absolute;
  top: 50%;
  transform: translateY(-50%);
  font-size: .68rem;
  font-weight: 700;
}
.smp-car-sm .smp-car-fl .smp-car-count,
.smp-car-sm .smp-car-rl .smp-car-count { right: calc(100% + 7px); text-align: right; }
.smp-car-sm .smp-car-fr .smp-car-count,
.smp-car-sm .smp-car-rr .smp-car-count { left:  calc(100% + 7px); text-align: left;  }

/* ── A reading drawn against its scale ──
   "6/32" is a number nobody outside a workshop can place. The bar IS the
   scale: zero at the left, a new tyre at the right, the shop's two lines
   where the colours change, and a marker at what was found. The zones are
   a gradient rather than three elements — the office draws this page
   beside a hundred checklist rows, and twelve more nodes per part buys
   nothing. */
.rgd-gauge {
  display: inline-block;
  position: relative;
  width: 52px;
  height: 9px;
  border-radius: 5px;
  vertical-align: middle;
  /* The colours are strong enough to read at 9px tall; the ring keeps the
     bar off a white page without dulling them. */
  box-shadow: inset 0 0 0 1px rgba(0,0,0,.12);
}

/* Where it actually is. White with a dark edge, so it reads on any of the
   three zones — a single dark line vanishes into the red end. */
.rgd-gauge-mark {
  position: absolute;
  top: -2px;
  bottom: -2px;
  width: 3px;
  margin-left: -1.5px;
  border-radius: 2px;
  background: #fff;
  box-shadow: 0 0 0 1px rgba(17,24,39,.85);
}

/* The figure, under the bar it is drawn on. The bar answers "how much is
   left"; the number is what the customer repeats down the phone to
   whoever they ask about it, and what the shop is held to. Centred on the
   bar rather than inheriting the count's left/right alignment, which
   exists to push the readings away from the car. */
.rgd-gauge-val {
  display: block;
  margin-top: 2px;
  text-align: center;
  font-size: .62rem;
  font-weight: 600;
  line-height: 1.1;
}

/* The key. One per part — the same sentence under four wheels is three
   too many. */
.rgd-scale {
  display: flex;
  flex-wrap: wrap;
  justify-content: center;
  gap: 4px 14px;
  margin: -2px 0 9px;
  font-size: .66rem;
  color: #6b7280;
}
.rgd-scale-k { display: inline-flex; align-items: center; gap: 5px; white-space: nowrap; }
.rgd-scale-sw { width: 9px; height: 9px; border-radius: 3px; }
.rgd-sw-red    { background: #dc2626; }
.rgd-sw-yellow { background: #eab308; }
.rgd-sw-green  { background: #16a34a; }

/* Room for them either side, so nothing lands on the neighbouring
   column or gets clipped by the pane. */
.rgd-car { margin-left: auto; margin-right: auto; }
.rgd-part .smp-car-sm { --car-w: 112px; }
.rgd-car-wrap { padding: 0 62px; }

/* What the tyres should have been, and what became of them. Without it
   "26" is a number with nothing to measure itself against, and low is the
   whole point. Carries .rgd-item as well, so it is the same card as every
   other block under a drawing. It does NOT take the 10px indent those
   get: it is centred, and an indent would put its centre 10px off the
   centre of the car above it. */
.rgd-spec { margin-top: 6px; }
/* Centred under the drawing it describes, rather than ranged left like
   the corner cards — it is about all four wheels at once, not a row in a
   list of them. The name drops its flex:1 so the dot travels with the
   text instead of being pinned to the left edge. */
.rgd-spec .rgd-item-head { justify-content: center; }
.rgd-spec .rgd-name { flex: 0 1 auto; text-align: center; }

/* A part with no corners: one plain line. */
.rgd-plain {
  display: flex;
  align-items: center;
  gap: 9px;
  padding: 9px 13px;
  border: 1px solid #e5e7eb;
  border-radius: 10px;
  background: #fff;
}

/* The detail blocks sit under their drawing, indented so they read as
   belonging to it. */
.rgd-part .rgd-item { margin-top: 6px; margin-left: 10px; }

.rgd-item {
  /* Rows off screen are not laid out or painted at all. A report runs to
     a hundred of them, most with photographs, and the browser was doing
     the full job on every one on every frame of a scroll. The intrinsic
     size keeps the scrollbar honest while they are skipped. */
  content-visibility: auto;
  contain-intrinsic-size: auto 64px;

  padding: 11px 13px;
  border: 1px solid #e5e7eb;
  border-radius: 10px;
  background: #fff;
  margin-bottom: 7px;
}
/* A row with a video in it opts out. Playing one forces that row to stay
   rendered while its neighbours are skipped, so it flips between its
   placeholder height and its real height and the page relays out on every
   frame of playback — which locks the tab up. Static photos are fine. */
.rgd-item-live { content-visibility: visible; contain-intrinsic-size: none; }

/* The player's idle state inside a report's media strip. */
/* Bigger than a thumbnail: a video is the one thing on a report you
   cannot judge from a postage stamp. The HEIGHT is not set — the player
   puts the clip's own aspect ratio on its box, so a portrait phone clip
   is portrait rather than letterboxed into a landscape hole. */
.rgd-media .rgv { width: 256px; }

/* The rows that ask something are the reason the page exists. */
.rgd-item-flagged { border-left: 3px solid #d97706; }

.rgd-item-head { display: flex; align-items: center; gap: 9px; }
.rgd-dot { width: 10px; height: 10px; border-radius: 50%; flex: 0 0 auto; }
.rgd-dot-green  { background: #16a34a; }
.rgd-dot-yellow { background: #a16207; }
.rgd-dot-red    { background: #dc2626; }
.rgd-dot-grey   { background: #94a3b8; }
.rgd-name  { flex: 1; font-weight: 600; font-size: .92rem; }
/* The corner, on the row. Lighter than the part name because it
   qualifies it rather than competing with it — the customer is scanning
   for "Brake Pad", not for "Front Left". Its own class: .rgd-where
   already means a FINDING's scope, and one name for two ideas is how a
   stylesheet stops being readable. */
.rgd-at { font-weight: 500; color: #6b7280; }
.rgd-reading {
  font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
  font-size: .82rem; color: #374151;
}
.rgd-grade { font-size: .76rem; font-weight: 700; }
.rgd-grade.rgd-dot-green  { background: none; color: #16a34a; width: auto; height: auto; border-radius: 0; }
.rgd-grade.rgd-dot-yellow { background: none; color: #a16207; width: auto; height: auto; border-radius: 0; }
.rgd-grade.rgd-dot-red    { background: none; color: #dc2626; width: auto; height: auto; border-radius: 0; }
.rgd-grade.rgd-dot-grey   { background: none; color: #94a3b8; width: auto; height: auto; border-radius: 0; }

.rgd-findings { margin-top: 7px; display: flex; flex-wrap: wrap; gap: 5px; }
/* Each finding in the weight the technician gave it, not the item's. Two
   things can be wrong with one part at two different weights — a
   streaking wiper and a torn one — and painting both red because the
   worse of them is red overstates the lesser to somebody being asked to
   pay for it. */
.rgd-finding {
  padding: 2px 8px;
  border-radius: 999px;
  font-size: .76rem;
  font-weight: 600;
}
.rgd-finding-yellow { background: rgba(161,98,7,.11);  color: #854d0e; }
.rgd-finding-red    { background: rgba(220,38,38,.09); color: #991b1b; }
.rgd-where { font-weight: 400; opacity: .8; }

.rgd-note {
  margin: 8px 0 0;
  padding: 8px 10px;
  border-radius: 8px;
  background: #f9fafb;
  font-size: .86rem;
  line-height: 1.45;
}

.rgd-media { margin-top: 9px; display: flex; flex-wrap: wrap; gap: 8px; }
.rgd-shot { margin: 0; width: 128px; }
/* Width from the column, height from the picture. No fixed box, so
   nothing is cropped to a shape chosen before the media was seen. */
.rgd-shot img {
  width: 100%;
  height: auto;
  aspect-ratio: 4 / 3;    /* only until the real ratio is known */
  object-fit: cover;
  border-radius: 8px;
  background: #111827;
}
.rgd-shot figcaption {
  margin-top: 3px;
  font-size: .7rem;
  line-height: 1.3;
  color: #4b5563;
}

.rgd-decide  { margin-top: 10px; }
.rgd-buttons { display: flex; gap: 8px; }
.rgd-approve, .rgd-decline {
  flex: 1;
  padding: 9px 12px;
  border-radius: 8px;
  font-size: .88rem;
  font-weight: 700;
  border: 1px solid transparent;
}
.rgd-approve { background: #16a34a; color: #fff; }
.rgd-decline { background: #fff; color: #4b5563; border-color: #d1d5db; }
/* Inert in the office preview — a document being read, not a question. */
.rgd-approve:disabled, .rgd-decline:disabled { opacity: .55; cursor: default; }
.rgd-decided { font-size: .86rem; font-weight: 700; }
.rgd-yes { color: #16a34a; }
.rgd-no  { color: #6b7280; }

.rgd-foot { margin-top: 22px; color: #6b7280; font-size: .85rem; }

.rgd-loading, .rgd-gone { padding: 60px 20px; text-align: center; color: #6b7280; }
.rgd-gone h1 { font-size: 1.2rem; color: #111827; }
.rgd-gone-hint { font-size: .85rem; }

/* Above the bottom nav, and clear of it.
   It had no z-index at all, so it lost to the nav's 1200 and the one
   message telling somebody their answer was recorded was the one thing
   they could not see. The offset matters as much as the stacking: sitting
   ON TOP of the bar is still covering the bar's buttons. The nav is
   mobile-only, so the clearance is too. */
.rgd-toast {
  position: fixed;
  left: 50%;
  bottom: calc(20px + var(--safe-b));
  z-index: 1400;
  transform: translateX(-50%);
  /* A long message on a narrow phone should wrap, not run off the side. */
  max-width: calc(100vw - 32px);
  text-align: center;
  padding: 11px 18px;
  border-radius: 999px;
  background: #111827;
  color: #fff;
  font-size: .88rem;
  font-weight: 600;
  box-shadow: 0 8px 24px rgba(0,0,0,.25);
}

/* No bottom-nav offset: the report hides the nav entirely (isReport()),
   so there is nothing to climb over. The z-index above is kept anyway —
   a toast that loses a stacking contest is a message nobody reads, and
   this page is also opened by staff checking how it looks. */

/* A photo in the report grid is a button now, not a link to a new tab. */
.rgd-open {
  padding: 0;
  border: 0;
  border-radius: 8px;
  background: transparent;
  line-height: 0;
  cursor: zoom-in;
}
.rgd-open img { border-radius: 8px; }

/* ── The report's photo lightbox ──
   Same idea as the office's: stay on the page, say which row the photo
   came from, and page through the rest. */
.rgd-lb {
  position: fixed;
  inset: 0;
  z-index: 2300;
  display: flex;
  flex-direction: column;
  background: rgba(9,11,16,.95);
}
.rgd-lb-bar {
  display: flex;
  align-items: center;
  gap: 10px;
  padding: calc(10px + env(safe-area-inset-top, 0px)) 12px 10px;
  color: #e5e7eb;
  font-size: .82rem;
}
.rgd-lb-of   { opacity: .65; font-variant-numeric: tabular-nums; }
.rgd-lb-item { font-weight: 600; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.rgd-lb-x {
  margin-left: auto;
  width: 34px; height: 34px;
  border: 0; border-radius: 8px;
  background: rgba(255,255,255,.1);
  color: #fff;
}
.rgd-lb-stage {
  flex: 1;
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 6px;
  min-height: 0;
  padding: 0 6px;
}
/* Fit, never crop — a cropped inspection photo is a photo of the wrong
   thing. */
.rgd-lb-stage img { max-width: 100%; max-height: 100%; object-fit: contain; }
.rgd-lb-step {
  flex: 0 0 auto;
  width: 40px; height: 64px;
  border: 0; border-radius: 8px;
  background: rgba(255,255,255,.08);
  color: #fff;
}
.rgd-lb-cap {
  padding: 12px 16px calc(14px + env(safe-area-inset-bottom, 0px));
  color: #e5e7eb;
  font-size: .86rem;
  text-align: center;
}

/* ══════════════════════════════════════════════════════════════
   INSPECTION VIDEO PLAYER  (rg-video)
   ══════════════════════════════════════════════════════════════
   TWO BOXES, and which carries what is the whole thing:

     .rgv        outer. Carries the aspect-ratio, so the space is held at
                 the clip's real shape before anything loads.
     .rgv-stage  absolutely fills it. Carries NO ratio, and is what goes
                 fullscreen.

   Putting the ratio on the element that also goes fullscreen is what made
   an earlier version play thumbnail-sized in the corner of the screen: a
   ratio does not stop applying when the browser hands an element the
   whole display. Separated, fullscreen needs no special-casing at all.
   ============================================================== */
.rgv {
  position: relative;
  width: 100%;
  border-radius: 8px;
  overflow: hidden;
  background: #000;
}

.rgv-stage {
  position: absolute;
  inset: 0;
  display: flex;
  flex-direction: column;
}

/* Takes what the bar leaves. min-height:0 because a flex item will not
   shrink below its intrinsic size without it, and a 720p frame is far
   bigger than the box. `contain` centres and letterboxes, so a clip whose
   real shape differs from a stale recorded one is never stretched. */
.rgv-el {
  flex: 1;
  min-height: 0;
  width: 100%;
  background: #000;
  object-fit: contain;
}

/* Idle: no <video> element exists behind this at all. */
.rgv-start {
  position: absolute;
  inset: 0;
  width: 100%;
  border: 0;
  background: #111827;
  color: #fff;
  font-size: 1.1rem;
}
.rgv-start:hover { background: #1f2937; }

/* Only while playback has actually starved. */
.rgv-wait {
  position: absolute;
  inset: 0;
  display: none;
  align-items: center;
  justify-content: center;
  color: #fff;
  font-size: 1.5rem;
  text-shadow: 0 2px 8px rgba(0,0,0,.6);
  pointer-events: none;
}
.rgv-busy .rgv-wait { display: flex; }

/* The picture itself is the play/pause control — the bar's buttons are
   small, and on a phone they sit over the bottom of the shot. */
.rgv-el { cursor: pointer; }

/* What that tap will do, shown over the picture. Never clickable: the
   tap belongs to the video, and a target that swallowed it would leave
   the picture feeling dead everywhere except one circle. */
.rgv-hint {
  position: absolute;
  inset: 0;
  display: none;
  align-items: center;
  justify-content: center;
  color: #fff;
  font-size: 1.6rem;
  text-shadow: 0 2px 10px rgba(0,0,0,.7);
  pointer-events: none;
}
/* A finished clip says so and offers the way back. Paused says nothing —
   the frame is frozen on screen, which is its own announcement, and a
   permanent icon over a still is just something in the way. */
.rgv-ended .rgv-hint-replay { display: flex; background: rgba(17,24,39,.45); }

.rgv-bar {
  flex: 0 0 auto;
  display: flex;
  align-items: center;
  gap: 8px;
  padding: 6px 9px;
  background: rgba(0,0,0,.74);
  color: #fff;
}
.rgv-btn {
  flex: 0 0 auto;
  width: 26px; height: 26px;
  border: 0; border-radius: 5px;
  background: rgba(255,255,255,.14);
  color: #fff;
  font-size: .7rem;
}
.rgv-btn:hover { background: rgba(255,255,255,.28); }
.rgv-t {
  flex: 0 0 auto;
  font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
  font-size: .68rem;
  color: rgba(255,255,255,.85);
}

/* Loaded behind played, knob on top. Tall hit area so a finger can grab
   it; the visible bar is the thin part inside. */
.rgv-track {
  position: relative;
  flex: 1;
  height: 16px;
  display: flex;
  align-items: center;
  cursor: pointer;
  touch-action: none;
}
.rgv-track::before {
  content: '';
  position: absolute;
  left: 0; right: 0;
  height: 4px;
  border-radius: 2px;
  background: rgba(255,255,255,.22);
}
.rgv-buf, .rgv-fill {
  position: absolute;
  left: 0;
  width: 0;
  height: 4px;
  border-radius: 2px;
}
.rgv-buf  { background: rgba(255,255,255,.42); }   /* loaded */
.rgv-fill { background: var(--rg-accent, #2563eb); }
.rgv-knob {
  position: absolute;
  left: 0;
  width: 11px; height: 11px;
  margin-left: -5px;
  border-radius: 50%;
  background: #fff;
  box-shadow: 0 1px 3px rgba(0,0,0,.5);
  pointer-events: none;
}

/* In a lightbox the player is the whole stage. */
.rg-media-video, .smp-viewer-video { width: 100%; max-width: 900px; }
