/* VertiServices — modern, mobile-first stylesheet */

/* Sprint 474 — the icon CSS is NOT imported here any more.
   These three @import rules used to sit on this line, and an @import cannot
   start downloading until its parent stylesheet has been fetched AND parsed.
   On this host (~1s time-to-first-byte on every request, HTTP/1.1) that was a
   guaranteed extra serial round trip in front of first paint. The parts are
   now concatenated into assets/css/icons.css (built by
   scripts/build-icons-css.php) and every page links it in PARALLEL with this
   file. Brands went with them: fa-brands is used nowhere in the app.
   Do not reintroduce an @import here — add to the build script instead. */

/* Alpine.js — hide elements with x-cloak until Alpine boots, so users
 * never see the unhydrated template on first paint. */
[x-cloak] { display: none !important; }

:root {
  /* Sprint 199: brand navy = the VERTI logo colour (#1D3360) so menus,
     headings, buttons, links and default table text all match the logo. */
  --verti-navy: #1D3360;
  --verti-navy-700: #142848;
  --verti-navy-50: #EEF2F8;
  --tke-orange: #E85A2A;
  --tke-purple: #7A2E6B;
  /* Sprint 200: the two-brand accent — balanced. VERTI navy holds only a
     short lead (~20%), then an even transition through TKE purple (55%) to
     TKE orange (100%), so the three brand colours read in similar proportion
     with navy just a touch ahead. Used everywhere: topbar strip, page-header
     underline, KPI cards, buttons, table top-line, dividers, helpers. */
  --brand-gradient: linear-gradient(90deg, #1D3360 0%, #1D3360 20%, #7A2E6B 55%, #E85A2A 100%);
  --bg: #F4F6F9;
  --surface: #FFFFFF;
  --text: #1A2333;
  --muted: #6B7588;
  --border: #E2E6EE;
  --danger: #D94B4B;
  --success: #2E9E6B;
  --warning: #E0A93A;
  --radius: 12px;
  --radius-sm: 8px;
  --shadow-sm: 0 1px 2px rgba(20,30,60,0.06);
  --shadow-md: 0 6px 24px rgba(20,30,60,0.08);
  --font: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, 'Helvetica Neue', sans-serif;

  /* Sprint 474 (C4) — MOTION TOKENS. One curve for everything that travels,
     one set of durations, defined once. Before this the navigation curve was
     the literal string cubic-bezier(0.32, 0.72, 0, 1) copied into four rules;
     a timing that lives in several places stops being a design decision and
     becomes four numbers that drift apart. Use these, never a raw curve.
       --ease-spring : the iOS navigation curve — decelerates like a physical
                       object. Anything that MOVES uses it.
       --ease-exit   : leaving the screen; starts fast, no lingering.
       --ease-enter  : arriving; eases into place.
     Durations are deliberately short: a transition the user waits for is a
     cost, not a delight. */
  --ease-spring: cubic-bezier(0.32, 0.72, 0, 1);
  --ease-exit:   cubic-bezier(0.4, 0, 1, 1);
  --ease-enter:  cubic-bezier(0, 0, 0.2, 1);
  --dur-nav:      430ms;   /* page-to-page directional push */
  --dur-out:      190ms;   /* content leaving */
  --dur-in:       400ms;   /* content arriving */
  --dur-pill:     320ms;   /* the active-menu highlight morph */
}

* { box-sizing: border-box; }
html, body { height: 100%; }
body {
  margin: 0;
  font-family: var(--font);
  font-size: 15px;
  line-height: 1.5;
  color: var(--text);
  background: var(--bg);
  -webkit-font-smoothing: antialiased;
}

a { color: var(--verti-navy); text-decoration: none; }
a:hover { text-decoration: underline; }

/* ---------- Buttons ---------- */
.btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: 8px;
  padding: 10px 16px;
  border: 1px solid transparent;
  border-radius: var(--radius-sm);
  background: var(--brand-gradient);
  color: #fff;
  font-weight: 600;
  font-size: 14px;
  cursor: pointer;
  transition: filter 0.15s, transform 0.05s, box-shadow 0.15s;
  white-space: nowrap;
}
.btn:hover { filter: brightness(1.08); }
.btn:active { transform: translateY(1px); }
.btn:disabled { opacity: 0.6; cursor: not-allowed; }
.btn.btn-block { width: 100%; padding: 12px; font-size: 15px; }
.btn.btn-ghost {
  background: transparent;
  color: var(--verti-navy);
  border-color: var(--border);
}
.btn.btn-ghost:hover { background: var(--verti-navy-50); }
.btn.btn-danger { background: var(--danger); }
.btn.btn-danger:hover { background: #B83C3C; }
.btn.btn-accent { background: var(--tke-orange); }
.btn.btn-accent:hover { background: #C9491E; }
.btn.btn-sm { padding: 6px 10px; font-size: 13px; }

/* ---------- Inputs ---------- */
.field { margin-bottom: 14px; }
.field label {
  display: block;
  font-size: 13px;
  font-weight: 600;
  margin-bottom: 6px;
  color: var(--text);
}
.field input,
.field select,
.field textarea {
  width: 100%;
  padding: 11px 12px;
  border: 1px solid var(--border);
  border-radius: var(--radius-sm);
  font: inherit;
  background: #fff;
  transition: border-color 0.15s, box-shadow 0.15s;
}
.field input:focus,
.field select:focus,
.field textarea:focus {
  outline: none;
  border-color: var(--verti-navy);
  box-shadow: 0 0 0 3px rgba(27, 58, 107, 0.15);
}
.field .hint { font-size: 12px; color: var(--muted); margin-top: 4px; }

/* ---------- Alerts ---------- */
.alert {
  padding: 10px 12px;
  border-radius: var(--radius-sm);
  font-size: 14px;
  margin-bottom: 12px;
}
.alert.error  { background: #FBE9E9; color: #8C2A2A; border: 1px solid #F2C9C9; }
.alert.success { background: #E5F4EC; color: #235E45; border: 1px solid #BFE2D0; }
.alert.warning { background: #FBF2DA; color: #6E5318; border: 1px solid #EFD89A; }

/* ---------- Login layout ---------- */
.login-shell {
  min-height: 100vh;
  display: grid;
  grid-template-columns: 1fr;
}
.login-hero { display: none; }
.login-panel {
  display: flex;
  align-items: center;
  justify-content: center;
  padding: 24px;
}
.login-card {
  width: 100%;
  max-width: 400px;
  background: var(--surface);
  border-radius: var(--radius);
  padding: 28px 24px;
  box-shadow: var(--shadow-md);
  position: relative;
  overflow: hidden;
}
/* Sprint 200: two-brand gradient cap on the login card. */
.login-card::before {
  content: '';
  position: absolute; top: 0; left: 0; right: 0; height: 4px;
  background: var(--brand-gradient);
}
/* Sprint 200: real combined VERTI + TKE lockup replaces the old "V" chip. */
.login-brand { margin-bottom: 24px; }
.login-logo { height: 34px; width: auto; max-width: 280px; display: block; }
.brand-row {
  display: flex;
  align-items: center;
  gap: 12px;
  margin-bottom: 22px;
}
.brand-logo {
  width: 44px; height: 44px;
  border-radius: 50%;
  background: var(--verti-navy);
  color: #fff;
  display: grid; place-items: center;
  font-weight: 800; font-size: 22px;
  letter-spacing: 0.5px;
}
.brand-name { font-size: 20px; font-weight: 800; color: var(--verti-navy); }
.brand-sub  { font-size: 12px; color: var(--muted); }
.login-title { font-size: 22px; font-weight: 700; margin: 0 0 4px; }
.login-subtitle { color: var(--muted); margin: 0 0 22px; font-size: 14px; }
.login-foot { margin-top: 18px; font-size: 12px; color: var(--muted); text-align: center; }

@media (min-width: 880px) {
  .login-shell { grid-template-columns: 1.1fr 1fr; }
  .login-hero {
    display: flex;
    align-items: center;
    justify-content: center;
    background:
      radial-gradient(circle at 20% 20%, rgba(232,90,42,0.25), transparent 45%),
      radial-gradient(circle at 80% 80%, rgba(122,46,107,0.25), transparent 45%),
      linear-gradient(135deg, var(--verti-navy), var(--verti-navy-700));
    color: #fff;
    padding: 48px;
  }
  .hero-inner { max-width: 480px; }
  .hero-inner h1 { font-size: 36px; line-height: 1.15; margin: 0 0 12px; }
  .hero-inner p  { font-size: 16px; opacity: 0.85; }
  .hero-tag {
    display: inline-block; padding: 4px 10px;
    background: rgba(255,255,255,0.15);
    border: 1px solid rgba(255,255,255,0.25);
    border-radius: 999px; font-size: 12px; margin-bottom: 16px;
    letter-spacing: 0.4px; text-transform: uppercase;
  }
}

/* ---------- Branded print header / footer (Sprint 200) ---------- */
.print-brand, .print-foot { display: none; }
@media print {
  /* Hide the app chrome so a report prints clean. */
  .topbar, .sidebar, .menu-btn, .offline-chip, .lang-switch, .user-chip,
  .ai-chip, .notif-bubble, .brand-program-name, .fab, [data-no-print],
  .page-header .btn, .page-header button,
  /* Sprint 473: the back chip is navigation chrome — never printed. */
  .ph-back,
  /* Sprint 466: on the merged tabbed pages the action controls no longer sit
     inside .page-header (there is no title row any more) — they live in a
     .pane-toolbar under the tab strip. Hide both, plus the strip itself: a
     printout of one tab should not carry the other tabs' names. */
  .pb-tabs, .pane-toolbar, .ptab-toolbar { display: none !important; }
  .layout { display: block !important; }
  main, .content { padding: 0 !important; }
  .page-header { position: static !important; }
  /* Sprint 473: the house-standard rule is screen chrome; a printout keeps
     the plain navy title only. (Sprint 475 removed the second, title-width
     rule, so there is just the one to hide here now.) */
  .page-header.ph-std::after { display: none !important; }
  /* Same reason the header is un-stickied: a sticky strip repeats on every
     printed page. */
  .pb-tabs, .pane-toolbar, .ptab-toolbar, table.data thead th { position: static !important; }
  /* Reveal the branded header + footer (visibility:visible covers report
     pages that print via a body * { visibility:hidden } rule). */
  .print-brand {
    display: flex !important; visibility: visible !important;
    align-items: center; justify-content: space-between;
    border-bottom: 2px solid var(--verti-navy); padding-bottom: 8px; margin-bottom: 14px;
  }
  .print-brand * { visibility: visible !important; }
  .print-brand img { height: 34px; width: auto; }
  .print-brand .pb-meta { text-align: right; font-size: 11px; color: #374151; }
  .print-foot {
    display: block !important; visibility: visible !important;
    position: fixed; bottom: 0; left: 0; right: 0; text-align: center;
    padding-top: 6px; border-top: 3px solid; border-image: var(--brand-gradient) 1;
  }
  .print-foot * { visibility: visible !important; }
  .print-foot .pf-www  { font-weight: 800; color: var(--verti-navy); font-size: 13px; }
  .print-foot .pf-line { font-weight: 700; color: var(--verti-navy); font-size: 10px; }
}

/* ---------- App shell ---------- */
.app {
  min-height: 100vh;
  display: grid;
  grid-template-rows: auto 1fr;
}
.topbar {
  background: var(--surface);
  border-bottom: 1px solid var(--border);
  padding: 10px 16px;
  display: flex;
  align-items: center;
  gap: 12px;
  position: sticky; top: 0; z-index: 10;
}
/* Two-brand accent strip along the bottom of the topbar (every page). */
.topbar::after {
  content: '';
  position: absolute; left: 0; right: 0; bottom: -3px; height: 3px;
  background: var(--brand-gradient);
}
.topbar .brand-row { margin: 0; gap: 10px; align-items: center; }
.brand-program-name {
  /* Sprint 95: the title now lives in the normal flex flow as a
     flex:1 centered block between the logo and the right-side controls,
     so it can never overlap them (the old absolute+center could ride
     over the language switcher on mid widths). The flexible width
     absorbs the slack; text truncates with an ellipsis rather than
     pushing neighbours. */
  flex: 1 1 auto;
  min-width: 0;
  text-align: center;
  font-size: 24px; font-weight: 700;
  color: var(--verti-navy); letter-spacing: 0.3px;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
  pointer-events: none;
}
/* Hide once the topbar gets crowded so it never squeezes the controls. */
@media (max-width: 1100px) { .brand-program-name { display: none; } }
.topbar .brand-logo-img {
  height: 32px;
  width: auto;
  max-width: 270px;
  display: block;
}
@media (max-width: 879px) {
  .topbar .brand-logo-img { height: 26px; max-width: 210px; }
}
/* The flexible title (.brand-program-name) absorbs the slack on wide
   screens, so the spacer only needs to grow once the title is hidden. */
.topbar .spacer { flex: 0 0 0; }
@media (max-width: 1100px) { .topbar .spacer { flex: 1; } }
.topbar .user-chip {
  display: inline-flex; align-items: center; gap: 8px;
  padding: 6px 10px; border-radius: 999px;
  background: var(--verti-navy-50); color: var(--verti-navy);
  font-size: 13px; font-weight: 600;
  text-decoration: none;
  transition: background 0.15s;
}
.topbar a.user-chip:hover { background: #DDE5F1; text-decoration: none; }
.topbar .ai-chip {
  display: inline-flex; align-items: center; gap: 6px;
  padding: 6px 10px; border-radius: 999px; cursor: pointer;
  background: linear-gradient(120deg, #d65f1f 0%, #b8430f 100%);
  color: #fff; border: 0; font-size: 13px; font-weight: 700;
  margin-right: 8px;
  transition: filter 0.15s;
}
.topbar .ai-chip:hover { filter: brightness(1.08); }
.avatar {
  width: 26px; height: 26px; border-radius: 50%;
  background: var(--verti-navy); color: #fff;
  display: grid; place-items: center; font-size: 12px; font-weight: 700;
}
.menu-btn {
  border: 1px solid var(--border);
  background: #fff; border-radius: var(--radius-sm);
  padding: 6px 10px; cursor: pointer; font-size: 18px;
}
/* Sprint 124: the hamburger is now shown on desktop too, where it collapses
   / expands the pinned sidebar (it kept its mobile overlay role as well). */

.layout { display: grid; grid-template-columns: 1fr; min-height: 0; }
@media (min-width: 880px) { .layout { grid-template-columns: 240px 1fr; } }

.sidebar {
  background: var(--surface);
  border-right: 1px solid var(--border);
  padding: 16px 10px;
  display: none;
}
@media (min-width: 880px) {
  .sidebar {
    display: block;
    /* Pin the sidebar to the viewport on desktop so the nav stays
       reachable while the content area scrolls. top:56px lands it
       just under the global .topbar (sticky at top:0). The fixed
       height + overflow-y:auto let the nav itself scroll internally
       if more links are added than fit the viewport. align-self:start
       keeps the grid item its natural height (otherwise grid would
       stretch it and sticky would have nothing to slide against). */
    position: sticky;
    top: 56px;
    align-self: start;
    height: calc(100vh - 56px);
    overflow-y: auto;
  }
}
.sidebar.open { display: block; position: fixed; inset: 56px 0 0 0; z-index: 20; }

/* Sprint 124: desktop collapsible sidebar (default expanded). The #menuToggle
   in the topbar flips .sidebar-collapsed on .app; the state is persisted in
   localStorage so it survives page navigation. */
@media (min-width: 880px) {
  .app.sidebar-collapsed .layout { grid-template-columns: 1fr; }
  .app.sidebar-collapsed .sidebar { display: none; }
}

.nav a {
  display: flex; align-items: center; gap: 10px;
  padding: 10px 12px;
  border-radius: var(--radius-sm);
  color: var(--verti-navy);
  font-weight: 600; font-size: 13px;
}
.nav a:hover { background: var(--verti-navy-50); text-decoration: none; }
.nav a.active { background: var(--verti-navy); color: #fff; }
.nav-section { font-size: 13px; font-weight: 400; text-transform: uppercase; letter-spacing: 0.6px;
  color: var(--verti-navy); padding: 10px 12px 4px; }
/* Sprint 138: collapsible nav sections (accordion). The heading is a
   full-width button with a chevron; its links live in .nav-group-body
   which is hidden when the group has .collapsed. */
button.nav-section.nav-toggle {
  display: flex; align-items: center; justify-content: space-between;
  width: 100%; background: none; border: 0; cursor: pointer;
  font: inherit; font-size: 13px; font-weight: 400; text-transform: uppercase;
  letter-spacing: 0.6px; color: var(--verti-navy); text-align: left;
}
button.nav-section.nav-toggle:hover { color: var(--verti-navy); }
.nav-chevron { font-size: 12px; transition: transform .15s ease; opacity: .7; }
.nav-group.collapsed .nav-chevron { transform: rotate(-90deg); }
.nav-group.collapsed .nav-group-body { display: none; }

main.content { padding: 20px 16px 40px; min-width: 0; }
@media (min-width: 880px) { main.content { padding: 28px 32px; } }

.page-header {
  display: flex; align-items: center; gap: 12px;
  flex-wrap: wrap;
  margin-bottom: 20px;
  /* Sprint 30: pin the page title + button row to the top of the
     content area on every page. top:56px lands it just under the
     global .topbar (sticky at top:0 / z-index:10). z-index:5 stacks
     below the topbar but above the page content. Solid bg so
     scrolling content doesn't bleed through. The negative margin +
     padding pair extends the bg upward to cover the gap the original
     20px content padding leaves -- otherwise a strip of background
     shows above the header when scrolled. */
  position: sticky;
  top: 56px;
  z-index: 5;
  background: var(--bg);
  padding: 10px 0;
  margin: -10px 0 12px;
}
.page-header h1 { margin: 0; font-size: 22px; position: relative; padding-bottom: 9px; }
/* Sprint 475 (operator): the old 64px gradient underline that used to sit
   under every page title is GONE. Every real page title is a `.ph-titlerow h1`
   and had this cancelled already, so nothing changes on screen today — but
   leaving the rule in place meant a future page-header whose h1 was not
   wrapped in a title row would silently sprout a SECOND brand line under the
   full-width one, which is exactly what this sprint removed. One gradient
   rule per page, and now the CSS cannot produce a second one. */
.page-header .spacer { flex: 1; }

/* ── Page header — the HOUSE STANDARD (Sprints 467-473; full spec + the
   measured font-size/line-height pair table live in CLAUDE.md). Lifted out of
   map.html's inline <style> the moment a second page adopted it, per the
   CLAUDE.md rule — no page carries a private copy.
   .ph-std marks a page's TOP header only: .page-header is also reused as a
   section h2 row on some pages (Sprint 30 pattern) and the closing rule must
   never appear there. */
.page-header.ph-std { padding: 10px 0 12px; }
.page-header.ph-std::after { content: ''; position: absolute; left: 0; right: 0;
  bottom: 0; height: 3px; background: var(--brand-gradient); pointer-events: none; }
/* The title set row: back chip + title (+ optional .ph-link shortcut pills).

   Sprint 475 (operator): this row NO LONGER draws its own gradient rule.
   Sprints 471-473 gave the header two of them — a short one tracing the title
   set, and the full-width one closing the header — and side by side they read
   as a mistake rather than a hierarchy, especially on a page like
   installation-projects where the title is short and the two lines sit a few
   pixels apart. There is now exactly ONE brand rule per page: the full-width
   `.page-header.ph-std::after` above. Do not reintroduce a second one.
   `position: relative` stays — it is the containing block for the row. */
.ph-titlerow { position: relative; display: flex; align-items: center; gap: 8px;
  flex-wrap: nowrap; min-width: 0; }
/* 22px pairs with line-height 14px — a SOLVED PAIR, never move one alone. */
.ph-titlerow h1 { flex: 0 0 auto; white-space: nowrap; height: 28px; margin: 0;
  font-size: 22px; line-height: 14px; padding-bottom: 0; color: var(--verti-navy); }
/* Data-driven titles (a project name, a person) may be arbitrarily long — the
   .ph-fit variant lets ONLY the title give way (ellipsis) so the row never
   wraps and the page never scrolls sideways. Static titles keep flex:0 0 auto. */
.ph-titlerow h1.ph-fit { flex: 0 1 auto; min-width: 0; overflow: hidden;
  text-overflow: ellipsis; }
/* Shortcut pills — the SAME Apple-glass material as the mobile bar's active
   pill (.msh-tab.on, Sprint 462). One material, one source. */
.ph-links { display: flex; align-items: center; gap: 6px; flex-wrap: nowrap; min-width: 0; }
.ph-link { position: relative; display: inline-flex; align-items: center; gap: 6px;
  flex: 0 0 auto; white-space: nowrap; height: 28px; padding: 0 11px; border: 0;
  border-radius: 999px; background: var(--brand-gradient); color: #fff;
  font-size: 11.5px; font-weight: 700; text-decoration: none;
  box-shadow: 0 5px 14px rgba(122,46,107,.32),
              inset 0 1px 0 rgba(255,255,255,.4),
              inset 0 -4px 9px rgba(13,21,38,.14),
              inset 0 0 0 1px rgba(255,255,255,.2); }
.ph-link::before { content: ''; position: absolute; left: 6%; right: 6%; top: 2px;
  height: 44%; border-radius: 999px 999px 40% 40%; pointer-events: none;
  background: linear-gradient(180deg, rgba(255,255,255,.28), rgba(255,255,255,.03)); }
.ph-link > * { position: relative; z-index: 1; }
.ph-link i { font-size: 11px; opacity: .9; }
.ph-link:hover { filter: brightness(1.08); text-decoration: none; }
.ph-link:active { transform: scale(.97); }
.ph-link:focus-visible { outline: 2px solid #fff; outline-offset: 2px; }
/* Back chip — same 28px geometry, deliberately QUIET (white ground, navy ink):
   forward shortcuts are glass and sit RIGHT of the title; back is quiet and
   sits LEFT (the Sprint 209 back-link rule). Icon pair "← + destination
   glyph"; .ph-lbl names the destination and drops with the shared breakpoint. */
.ph-back { display: inline-flex; align-items: center; gap: 6px; flex: 0 0 auto;
  white-space: nowrap; height: 28px; padding: 0 10px; border: 1px solid #CBD5E4;
  border-radius: 999px; background: #fff; color: var(--verti-navy);
  font-size: 11.5px; font-weight: 700; text-decoration: none;
  box-shadow: 0 1px 3px rgba(29,51,96,.10); }
.ph-back i { font-size: 11px; }
.ph-back i.fa-arrow-left { font-size: 10px; opacity: .85; }
.ph-back:hover { background: var(--verti-navy-50); border-color: #AFBDD4; text-decoration: none; }
.ph-back:active { transform: scale(.97); }
.ph-back:focus-visible { outline: 2px solid var(--verti-navy); outline-offset: 2px; }
/* On the Sprint 466 tabbed pages there is no title row — the chip docks at the
   left edge of the sticky .pb-tabs strip instead. */
.pb-tabs .ph-back { margin-right: 6px; }
/* Labels drop below 1500px — measured on the map's worst-case row (CLAUDE.md:
   the breakpoint tracks the title width). One shared breakpoint so chips
   behave identically on every page. */
@media (max-width: 1500px) {
  .ph-lbl { display: none; }
  .ph-link { padding: 0 9px; }
  .ph-back { padding: 0 9px; }
}
@media (max-width: 980px) {
  .ph-links { gap: 4px; }
  .ph-link { padding: 0 7px; }
}

/* Reusable accent helpers — a full-width strip and a vertical section tick. */
.brand-accent-bar { height: 3px; border-radius: 2px; background: var(--brand-gradient); border: 0; }
.section-tick { display: inline-block; width: 5px; height: 1em; border-radius: 2px;
  background: var(--brand-gradient); vertical-align: -2px; margin-right: 9px; }

/* Per-column filter row in data tables -- used by Projects, Service
   Records (Sprint 30), and any future list page that wants Projects-
   style narrowing. Selects + text inputs share the same look so a
   "Filter…" field next to dropdowns stays visually consistent. */
table.data thead tr.col-filters th { background:#fff; padding:6px 8px; }
table.data thead tr.col-filters select,
table.data thead tr.col-filters input {
  width:100%; min-width:0; box-sizing:border-box;
  font-size:12px; padding:4px 6px;
  border:1px solid var(--border); border-radius:6px; background:#fff;
}

/* Sprint 507 (fixed 508): shared column-resize handle (assets/js/col-resize.js)
   — a drag strip pinned to a header cell's right edge, added by the module at
   mount time, not baked into markup. It draws a faint separator AT REST: the
   first version was invisible until hover, which made the feature impossible
   to discover — people dragged the header text instead and concluded nothing
   was resizable. Colour is `currentColor`, so it inherits the header's own ink
   (white on a gradient banner, navy on a plain header) and needs no per-table
   override. */
.col-resize-handle { position:absolute; top:0; right:0; height:100%; width:8px;
  cursor:col-resize; z-index:5; touch-action:none;
  display:flex; align-items:center; justify-content:center; }
.col-resize-handle::before { content:''; width:1px; height:58%; border-radius:1px;
  background:currentColor; opacity:.34; transition:opacity .12s, width .12s; }
.col-resize-handle:hover::before,
.col-resize-handle.dragging::before { width:3px; height:100%; opacity:.95; }
/* Under fixed layout a caption longer than its column would spill into the
   next one, so a resized table ellipsises its headers (the handle sits at
   right:0, INSIDE the cell, so this never clips it). Body cells stay each
   table's own business — table.rv already ellipsises its name column. */
table.col-resized thead th { overflow:hidden; text-overflow:ellipsis; }

/* Sprint 508: the filter row closes with a rule across the full table width,
   so the header block (captions + filters) reads as one unit separated from
   the data. Same 2px navy the grand-total row uses to close a table. */
table.data thead tr.col-filters th { border-bottom:2px solid var(--verti-navy); }

/* ==========================================================================
   THE TABLE STANDARD — the shared header block (assets/js/data-table.js)
   Sprint 512. Operator-approved from the interactive mockup. Every rule here
   animates ONLY transform/opacity (plus colour on single elements) — the
   compositor does those for free, and animating layout across a list is
   exactly where a table stutters. `prefers-reduced-motion` kills all of it.
   ========================================================================== */

/* --- caption row: label rail + a FIXED glyph rail ------------------------- */
/* The caption never wraps. A two-word caption used to push the sort glyph onto
   its own line, which is what made the old header read ragged; columns are
   resizable now, so widening is the answer, not wrapping. */
/* The module PAINTS ITS OWN caption row. Relying on `table.data thead
   tr:first-child` for the banner only works on tables that wear `.data` —
   items.html's catalog is `table.itt`, so its captions came out dark-on-white
   with no banner at all. A shared component cannot assume the host's class.
   The gradient goes on the ROW (Sprint 394: per-cell restarts the ramp at every
   column); a page whose header is STICKY overrides this with a flat opaque
   cell, because a sticky TH leaves the row's background behind (see §6). */
table thead tr.dt-caps { background-image: var(--brand-gradient); }
table thead tr.dt-caps th { background: transparent; color: #fff; }

/* NO `position` here on purpose. col-resize.js sets `relative` on a header
   only when its computed position is `static`, which is the correct, targeted
   behaviour — the handle needs a containing block, nothing more. Declaring it
   in CSS looked equivalent and was not: `table thead tr.dt-caps th` is FOUR
   elements + a class (0,0,1,4) and beat a page's own
   `table.data thead th { position:sticky }` (0,0,1,3). The page's `top:106px`
   then applied to a `relative` box and shoved the whole caption row 106px DOWN,
   onto the data. Same specificity trap as Sprint 513, from the other side: the
   shared component must not out-specify the page it is a guest in. */
table thead tr.dt-caps th { overflow:hidden; cursor:pointer;
  user-select:none; transition:opacity 150ms var(--ease-enter); }
.dt-th { display:grid; grid-template-columns:minmax(0,1fr) 12px; align-items:center;
  gap:7px; height:100%; }
table thead tr.dt-caps th.num .dt-cap { text-align:right; }
.dt-cap { overflow:hidden; text-overflow:ellipsis; white-space:nowrap; }
/* the "this column is filtered" dot — the quiet half of the two-place signal */
.dt-cap .dt-dot { display:inline-block; width:5px; height:5px; border-radius:50%;
  background:currentColor; margin-left:5px; vertical-align:middle;
  animation:dt-pop var(--dur-pill) var(--ease-spring) both; }
@keyframes dt-pop { from { transform:scale(0); opacity:0 } to { transform:scale(1); opacity:1 } }

.dt-sort { position:relative; width:12px; height:14px; flex:none; opacity:.4;
  transition:opacity var(--dur-pill) var(--ease-spring); }
.dt-sort i { position:absolute; left:50%; width:0; height:0; margin-left:-4px;
  border-left:4px solid transparent; border-right:4px solid transparent;
  transition:transform var(--dur-pill) var(--ease-spring),
             opacity var(--dur-pill) var(--ease-spring); }
.dt-sort i.up   { top:1px;    border-bottom:4.5px solid currentColor; }
.dt-sort i.down { bottom:1px; border-top:4.5px solid currentColor; }
.dt-sort.on { opacity:1; }
tr.dt-caps th:hover .dt-sort:not(.on) { opacity:.8; }
th.dt-asc  .dt-sort i.down { opacity:0; transform:translateY(4px) scale(.6); }
th.dt-asc  .dt-sort i.up   { transform:translateY(4px) scale(1.18); }
th.dt-desc .dt-sort i.up   { opacity:0; transform:translateY(-4px) scale(.6); }
th.dt-desc .dt-sort i.down { transform:translateY(-4px) scale(1.18); }
/* the sorted column reads at a glance — the arrow alone is too quiet */
table thead tr.dt-caps th.dt-sorted::after { content:''; position:absolute; inset:0;
  background:rgba(255,255,255,.17); pointer-events:none;
  animation:dt-wash var(--dur-pill) var(--ease-spring) both; }
@keyframes dt-wash { from { opacity:0 } to { opacity:1 } }
th.dt-lift { opacity:.28; }   /* the column being dragged to a new position */

/* --- filter row ----------------------------------------------------------- */
/* Self-contained on purpose. This row deliberately does NOT also wear
   `col-filters`: that older class is styled by `table.data thead tr.col-filters
   input`, which outranks `.dt-fx input` on specificity, and wearing both let the
   old `padding:4px 6px` win — which slid the placeholder underneath the
   magnifier. A shared component styles itself through its own class only. */
table thead tr.dt-filters th { background:#fff; padding:6px 7px;
  border-bottom:2px solid var(--verti-navy); }
.dt-fx { position:relative; display:flex; align-items:center; height:28px; }
.dt-fx input { width:100%; box-sizing:border-box; font:inherit; font-size:11.5px;
  height:28px; padding:0 24px 0 26px; color:var(--text);
  border:1px solid var(--border); border-radius:8px; background:#F7F9FC;
  transition:border-color 170ms var(--ease-enter), background 170ms var(--ease-enter),
             box-shadow 170ms var(--ease-enter); }
.dt-fx input::placeholder { color:var(--muted); }
.dt-fx input:focus { outline:none; border-color:var(--verti-navy); background:#fff;
  box-shadow:0 0 0 3px rgba(29,51,96,.15); }
.dt-mag { position:absolute; left:8px; width:12px; height:12px; color:var(--muted);
  pointer-events:none; transition:color 170ms var(--ease-enter),
                                  transform 260ms var(--ease-spring); }
.dt-fx input:focus ~ .dt-mag { color:var(--verti-navy); transform:scale(1.12); }
.dt-fx.has input { border-color:var(--verti-navy); background:#fff; font-weight:650; }
.dt-x { position:absolute; right:6px; width:15px; height:15px; padding:0; border:0;
  border-radius:999px; background:var(--muted); color:#fff; cursor:pointer;
  display:none; place-items:center; font-size:9px; line-height:1;
  animation:dt-pop 200ms var(--ease-spring) both;
  transition:background 140ms var(--ease-enter), transform 160ms var(--ease-spring); }
.dt-fx.has .dt-x { display:grid; }
.dt-x:hover { background:#B91C1C; transform:rotate(90deg); }

/* a chip stands in for a filter that needs more than a text box (range, list) */
.dt-chip { width:100%; height:28px; box-sizing:border-box; display:flex;
  align-items:center; gap:6px; padding:0 8px; text-align:left; cursor:pointer;
  font:inherit; font-size:11.5px; color:var(--muted); background:#F7F9FC;
  border:1px solid var(--border); border-radius:8px;
  transition:border-color 170ms var(--ease-enter), background 170ms var(--ease-enter),
             transform 160ms var(--ease-spring); }
.dt-chip:hover { border-color:var(--verti-navy); background:#fff; }
.dt-chip:active { transform:scale(.97); }
.dt-chip .dt-lbl { flex:1; overflow:hidden; text-overflow:ellipsis; white-space:nowrap; }
.dt-cv { width:11px; height:11px; flex:none; opacity:.7;
  transition:transform var(--dur-pill) var(--ease-spring); }
.dt-chip.open .dt-cv { transform:rotate(180deg); }
.dt-chip.has { border-color:var(--verti-navy); background:#fff; color:var(--verti-navy); font-weight:650; }
/* an unfilterable column keeps an EMPTY cell — an honest signal, not a dead box */
.dt-nofilter { height:28px; display:flex; align-items:center; justify-content:center; }
.dt-nofilter span { width:14px; height:1.5px; border-radius:1px; background:var(--border); }

/* --- toolbar: Columns button + active-filter badges ----------------------- */
/* One row shared with whatever the page already prints there (a row count, a
   note). A toolbar on a line of its own spends a whole row of vertical space on
   one button — on a dense list that is a row of data the user cannot see. */
.dt-bar { display:flex; align-items:center; gap:9px; flex-wrap:wrap; margin:0 0 8px; }
.dt-bar .dt-left { flex:1 1 auto; min-width:0; }
/* Sprint 529 — badges on the SAME row as the Columns button, for a page whose
   table sits under a KPI card with room to spare. The left content no longer
   grows to fill the bar (that would shove the badges to the right edge, away
   from the summary they qualify); the button is pinned right instead. */
.dt-bar-inline .dt-left { flex:0 1 auto; }
.dt-bar-inline > [data-dtcols] { margin-left:auto; }
/* In a row, the badges' reveal must not change the bar's height — a badge is
   shorter than the button beside it, so the strip grows inside the line and
   nothing below it moves. Its vertical padding would have undone exactly that. */
.dt-bar-inline .dt-badges.on { padding:0; }
.dt-btn { position:relative; display:inline-flex; align-items:center; gap:7px;
  padding:7px 13px; border-radius:9px; cursor:pointer; font:inherit;
  font-size:12.5px; font-weight:650; color:var(--verti-navy);
  background:#fff; border:1px solid var(--border);
  transition:border-color 160ms var(--ease-enter), background 160ms var(--ease-enter),
             transform 160ms var(--ease-spring); }
.dt-btn:hover { border-color:var(--verti-navy); background:var(--verti-navy-50); }
.dt-btn:active { transform:scale(.965); }
.dt-ico { width:14px; height:14px; flex:none; display:block; }
/* how many columns the user has hidden — absent means "table as shipped" */
.dt-dotn { position:absolute; top:-4px; right:-4px; min-width:17px; height:17px;
  padding:0 4px; box-sizing:border-box; border-radius:999px; background:var(--tke-orange);
  color:#fff; font-size:10px; font-weight:800; display:grid; place-items:center;
  animation:dt-pop var(--dur-pill) var(--ease-spring) both; }

/* The loud half of the two-place filter signal. The field failure is always the
   same — a forgotten filter read as an empty table — so an active filter is
   never only a dot on a header. */
.dt-badges { display:flex; align-items:center; gap:7px; flex-wrap:wrap;
  max-height:0; opacity:0; overflow:hidden; padding:0;
  transition:max-height var(--dur-pill) var(--ease-spring),
             opacity var(--dur-out) var(--ease-enter),
             padding var(--dur-pill) var(--ease-spring); }
.dt-badges.on { max-height:140px; opacity:1; padding:10px 0 2px; }
.dt-badge { display:inline-flex; align-items:center; gap:7px; padding:4px 5px 4px 11px;
  border-radius:999px; font-size:11.5px; font-weight:650; color:var(--verti-navy);
  background:var(--verti-navy-50); border:1px solid var(--border);
  animation:dt-badge-in var(--dur-pill) var(--ease-spring) both; }
@keyframes dt-badge-in { from { opacity:0; transform:translateY(4px) scale(.92) }
                         to   { opacity:1; transform:none } }
.dt-badge .k { color:var(--muted); font-weight:600; }
.dt-badge button { width:17px; height:17px; padding:0; border:0; cursor:pointer;
  border-radius:999px; background:none; color:var(--muted);
  display:grid; place-items:center;
  transition:background 140ms var(--ease-enter), color 140ms var(--ease-enter),
             transform 140ms var(--ease-spring); }
.dt-badge button:hover { background:#B91C1C; color:#fff; transform:rotate(90deg); }
.dt-badge.clear { background:none; border-style:dashed; cursor:pointer;
  padding:4px 11px; color:var(--muted); }
.dt-badge.clear:hover { color:#B91C1C; border-color:#B91C1C; }

/* --- popovers ------------------------------------------------------------- */
.dt-veil { position:fixed; inset:0; z-index:79; }
.dt-pop { position:fixed; z-index:80; min-width:212px; padding:9px;
  background:#fff; border:1px solid var(--border); border-radius:13px;
  box-shadow:0 2px 6px rgba(29,51,96,.10), 0 18px 44px rgba(29,51,96,.20);
  transform-origin:top left; animation:dt-pop-in 230ms var(--ease-spring) both; }
@keyframes dt-pop-in { from { opacity:0; transform:translateY(-7px) scale(.95) }
                       to   { opacity:1; transform:none } }
.dt-ph { padding:3px 7px 8px; font-size:10px; font-weight:700; letter-spacing:.13em;
  text-transform:uppercase; color:var(--muted); }
.dt-row2 { display:flex; gap:7px; padding:0 4px 4px; }
.dt-pop input { width:100%; box-sizing:border-box; font:inherit; font-size:12px;
  padding:7px 9px; border:1px solid var(--border); border-radius:8px;
  background:#F7F9FC; color:var(--text);
  transition:border-color 160ms var(--ease-enter), box-shadow 160ms var(--ease-enter); }
.dt-pop input:focus { outline:none; border-color:var(--verti-navy); background:#fff;
  box-shadow:0 0 0 3px rgba(29,51,96,.15); }
.dt-opts { display:grid; gap:1px; max-height:260px; overflow-y:auto; }
.dt-opt { display:flex; align-items:center; gap:9px; width:100%; padding:7px 8px;
  border:0; border-radius:8px; background:none; cursor:pointer; text-align:left;
  font-family:inherit; font-size:12.5px; color:var(--text);
  transition:background 130ms var(--ease-enter); }
.dt-opt:hover { background:var(--verti-navy-50); }
.dt-box { width:16px; height:16px; flex:none; border-radius:5px; background:#fff;
  border:1.5px solid var(--border); display:grid; place-items:center;
  transition:background var(--dur-pill) var(--ease-spring),
             border-color var(--dur-pill) var(--ease-spring); }
.dt-opt.on .dt-box { background:var(--verti-navy); border-color:var(--verti-navy); }
/* the tick DRAWS itself rather than blinking on — the one flourish worth its cost */
.dt-box svg { width:10px; height:10px; color:#fff; stroke-dasharray:16;
  stroke-dashoffset:16; transition:stroke-dashoffset 240ms var(--ease-spring); }
.dt-opt.on .dt-box svg { stroke-dashoffset:0; }
.dt-nm { flex:1; overflow:hidden; text-overflow:ellipsis; white-space:nowrap; }
.dt-ct { font-size:10.5px; color:var(--muted); }
.dt-opt.locked { opacity:.45; cursor:not-allowed; }
.dt-pf { display:flex; gap:7px; margin-top:5px; padding:8px 4px 2px;
  border-top:1px solid var(--border); }
.dt-pf button { flex:1; padding:7px; border-radius:8px; cursor:pointer; font:inherit;
  font-size:11.5px; font-weight:650; color:var(--verti-navy);
  background:#fff; border:1px solid var(--border);
  transition:background 140ms var(--ease-enter), transform 140ms var(--ease-spring); }
.dt-pf button:active { transform:scale(.97); }
.dt-pf button.pri { background:var(--verti-navy); border-color:var(--verti-navy); color:#fff; }

/* --- column reorder chrome ------------------------------------------------ */
.dt-ghost { position:fixed; top:0; left:0; z-index:90; display:none;
  padding:8px 14px; border-radius:10px; background:var(--verti-navy); color:#fff;
  font-size:10.5px; font-weight:700; letter-spacing:.055em; text-transform:uppercase;
  pointer-events:none; box-shadow:0 12px 30px rgba(16,24,52,.4);
  animation:dt-ghost-in 180ms var(--ease-spring) both; }
@keyframes dt-ghost-in { from { opacity:0; transform:scale(.9) } to { opacity:1 } }
/* deliberately un-transitioned: in direct manipulation, lag reads as a bug */
.dt-drop { position:absolute; top:0; bottom:0; width:3px; border-radius:2px;
  background:var(--tke-orange); display:none; pointer-events:none; z-index:10;
  box-shadow:0 0 0 4px rgba(232,90,42,.18); }

@media print { .dt-bar, .dt-badges, .dt-filters, .col-resize-handle { display:none !important; } }

/* ---------- Card ---------- */
.card {
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: var(--radius);
  box-shadow: var(--shadow-sm);
}
.card-pad { padding: 18px; }

/* ---------- Toolbar ---------- */
.toolbar {
  display: flex; gap: 10px; flex-wrap: wrap;
  padding: 14px 16px;
  border-bottom: 1px solid var(--border);
}
.toolbar .search,
.toolbar select {
  padding: 10px 12px;
  border: 1px solid var(--border);
  border-radius: var(--radius-sm);
  background: #fff;
  font: inherit;
}
.toolbar .search { flex: 1; min-width: 200px; }
.toolbar select  { min-width: 140px; }
.toolbar .search:focus,
.toolbar select:focus { outline: none; border-color: var(--verti-navy); }

/* ---------- Tables (responsive: cards on mobile) ---------- */
/* Sprint 199: two-brand gradient line across the top of every data table. */
.table-wrap { overflow-x: auto; border-top: 3px solid; border-image: var(--brand-gradient) 1; }
table.data {
  width: 100%;
  border-collapse: collapse;
  font-size: 14px;
}
table.data th, table.data td {
  text-align: left;
  padding: 12px 14px;
  border-bottom: 1px solid var(--border);
  vertical-align: middle;
}
table.data th {
  font-size: 12px; text-transform: uppercase;
  letter-spacing: 0.4px; color: var(--muted);
  background: #FAFBFD;
}
/* Sprint 200: the main header row carries the two-brand gradient itself
   (continuous across the row — the gradient sits on the <tr>, cells are
   transparent so it shows through). White labels. The per-column filter
   row keeps its light style. */
/* Sprint 200: every table's caption header is a continuous two-brand gradient
   banner (gradient on the <tr>, cells transparent, white labels). Pages with
   an in-header column-filter row keep that filter row on a clean WHITE
   background just below, so it reads as part of the table. */
table.data thead tr:first-child { background-image: var(--brand-gradient); }
table.data thead tr:first-child th { background: transparent; color: #fff; }
/* The top gradient line above the table is redundant under a gradient header. */
.table-wrap { border-top: 0; }
/* Sprint 200 follow-up: default table cell text in a calm dark grey — the
   navy reading colour was tiring in dense lists. Coloured badges / pills
   inside cells still override via higher specificity. */
table.data td { color: #374151; }
table.data tbody tr:hover { background: #FBFCFE; }
/* Contract-end alerts: warn = ends within 30 days, danger = expired. */
table.data tbody tr.row-warn,
table.data tbody tr.row-warn:hover   { background: #FBF2DA; }
table.data tbody tr.row-danger,
table.data tbody tr.row-danger:hover { background: #FBE9E9; }
.actions-cell { display: flex; gap: 6px; flex-wrap: wrap; }

.badge {
  display: inline-block;
  padding: 3px 8px;
  border-radius: 999px;
  font-size: 11px; font-weight: 700;
  letter-spacing: 0.3px; text-transform: uppercase;
}
.badge-admin      { background: #FCE3D9; color: #9C3712; }
.badge-manager    { background: #E7E0F2; color: #4F2D6F; }
.badge-technician { background: #DCEAF7; color: #214E7E; }
.badge-viewer     { background: #E8ECF2; color: #495568; }
.badge-status-on      { background: #D9F1E4; color: #1E6C49; }
.badge-status-off     { background: #F0E2E2; color: #7A2828; }
.badge-status-install { background: #DCEAF7; color: #1D3360; }
.badge-agreement-R { background: #DCEAF7; color: #214E7E; }
.badge-agreement-G { background: #FCE3D9; color: #9C3712; }
.badge-type-elevator     { background: #DCEAF7; color: #214E7E; }
.badge-type-escalator    { background: #E7E0F2; color: #4F2D6F; }
.badge-type-mww          { background: #FCE3D9; color: #9C3712; }
.badge-type-other        { background: #E8ECF2; color: #495568; }

/* Service-record work types */
.badge-work-maintenance         { background: #DCEAF7; color: #214E7E; }
.badge-work-maintenance_support { background: #E1ECF7; color: #2F5B85; }
.badge-work-fault               { background: #F7E7EB; color: #A6394F; }
.badge-work-fault_support       { background: #FCE3D9; color: #9C3712; }
.badge-work-correction          { background: #F7E9E1; color: #B8623F; }
.badge-work-part_replacement    { background: #F3E7F1; color: #7D3A72; }
.badge-work-inspection          { background: #EEEAF7; color: #5B4A8A; }
.badge-work-meeting             { background: #E8ECF2; color: #495568; }

/* ---------- Modal ---------- */
/* Sprint 248: shared message box (showMessage in api.js) — a centered,
   blocking, localized result/error dialog that stays until the user taps OK.
   Replaces transient toasts for messages the user must actually read. */
.msgbox-overlay {
  position: fixed; inset: 0; z-index: 10050;
  background: rgba(15, 27, 51, 0.50);
  display: flex; align-items: center; justify-content: center; padding: 22px;
  animation: msgboxIn 0.12s ease-out;
}
@keyframes msgboxIn { from { opacity: 0; } to { opacity: 1; } }
.msgbox {
  width: 100%; max-width: 340px; background: #fff; border-radius: 16px;
  overflow: hidden; box-shadow: 0 18px 44px rgba(15, 27, 51, 0.38);
}
.msgbox-cap { height: 6px; background: var(--brand-gradient); }
.msgbox-body { padding: 22px 22px 18px; text-align: center; }
.msgbox-ic {
  width: 54px; height: 54px; border-radius: 50%; margin: 2px auto 13px;
  display: flex; align-items: center; justify-content: center;
  font-size: 26px; font-weight: 800;
}
.msgbox-ic.success { background: #E6F4EA; color: #15803D; }
.msgbox-ic.error   { background: #FEECEC; color: #B91C1C; }
.msgbox-ic.info    { background: #EFF6FF; color: #1D4ED8; }
.msgbox-title { font-size: 16px; font-weight: 800; color: var(--verti-navy); margin-bottom: 6px; }
.msgbox-msg { font-size: 13.5px; color: #475569; line-height: 1.5; }
.msgbox-ok {
  margin-top: 17px; width: 100%; border: 0; cursor: pointer;
  background: var(--brand-gradient); color: #fff;
  font-size: 14px; font-weight: 800; padding: 12px; border-radius: 11px;
}
.msgbox-ok:active { transform: scale(.98); }

/* Sprint 255: hidden manager exit PIN pad (triple-tap the mobile-home V mark).
   Built by assets/js/pin-exit.js; mirrors the .msgbox modal look + the home-
   screen widget's PIN screen. */
.pinx-overlay {
  position: fixed; inset: 0; z-index: 10060;
  background: rgba(15, 27, 51, 0.55);
  display: flex; align-items: center; justify-content: center; padding: 22px;
  animation: msgboxIn 0.12s ease-out;
}
.pinx {
  width: 100%; max-width: 300px; background: #fff; border-radius: 18px;
  overflow: hidden; box-shadow: 0 20px 50px rgba(15, 27, 51, 0.42);
}
.pinx-cap { height: 5px; background: var(--brand-gradient); }
.pinx-head { display: flex; align-items: center; gap: 9px; padding: 15px 16px 4px; }
.pinx-lock {
  width: 34px; height: 34px; border-radius: 10px; flex: 0 0 auto;
  background: #FDECE5; color: var(--tke-orange);
  display: grid; place-items: center; font-size: 16px;
}
.pinx-title { font-size: 17px; font-weight: 800; color: var(--verti-navy); }
.pinx-sub { font-size: 12px; color: var(--muted); padding: 0 16px 10px; }
.pinx-sub.err { color: #B91C1C; font-weight: 700; }
.pinx-dots { display: flex; justify-content: center; gap: 14px; margin: 4px 0 14px; }
.pinx-dot { width: 14px; height: 14px; border-radius: 50%; border: 2px solid #C3CDDC; }
.pinx-dot.on { background: var(--verti-navy); border-color: var(--verti-navy); }
.pinx-pad { display: grid; grid-template-columns: repeat(3, 1fr); gap: 8px; padding: 0 16px 12px; }
.pinx-key {
  height: 46px; border-radius: 11px; background: #F1F4F9; border: 1px solid var(--border);
  font-size: 20px; font-weight: 800; color: var(--verti-navy); cursor: pointer;
}
.pinx-key:active { transform: scale(.96); }
.pinx-key.muted { background: transparent; border: 0; color: var(--muted); font-size: 17px; font-weight: 700; }
.pinx-key.muted:disabled { cursor: default; }
.pinx-foot { display: flex; padding: 4px 16px 16px; }
.pinx-cancel {
  flex: 1; height: 42px; border-radius: 11px; cursor: pointer;
  background: #fff; border: 1px solid var(--border); color: var(--muted);
  font-size: 14px; font-weight: 800;
}
.pinx-cancel:active { transform: scale(.98); }
.pinx.shake { animation: pinxShake 0.34s ease; }
@keyframes pinxShake {
  0%, 100% { transform: translateX(0); }
  20%, 60% { transform: translateX(-7px); }
  40%, 80% { transform: translateX(7px); }
}

/* Sprint 290 — impersonation ("act as user"): top banner + user picker. */
.imp-banner {
  position: fixed; top: 0; left: 0; right: 0; z-index: 100000;
  height: 34px; display: flex; align-items: center; justify-content: space-between;
  gap: 8px; padding: 0 12px; background: var(--verti-navy); color: #fff;
  box-shadow: 0 2px 8px rgba(15, 27, 51, 0.3);
}
.imp-banner::after { content: ""; position: absolute; left: 0; right: 0; bottom: 0; height: 3px; background: var(--brand-gradient); }
.imp-b-txt { flex: 0 0 auto; font-size: 12px; font-weight: 700; white-space: nowrap; }
.imp-b-txt i { color: #FFD9C7; margin-right: 5px; }
.imp-b-sel {
  flex: 1 1 auto; min-width: 0; max-width: 52%; margin: 0 auto 0 2px;
  height: 24px; padding: 0 8px; border-radius: 8px; cursor: pointer;
  background: rgba(255, 255, 255, 0.16); color: #fff;
  border: 1px solid rgba(255, 255, 255, 0.4);
  font-size: 12px; font-weight: 700;
}
.imp-b-sel option { color: var(--verti-navy); background: #fff; }
.imp-b-back {
  flex: 0 0 auto; background: var(--tke-orange); color: #fff; border: 0; cursor: pointer;
  font-size: 11px; font-weight: 800; padding: 5px 11px; border-radius: 8px;
}
.imp-b-back:active { transform: scale(.97); }
/* make room so the fixed banner never covers the topbar (sticky at top:0) */
body.imp-on { padding-top: 34px; }
body.imp-on .topbar, body.imp-on .mobile-topbar { top: 34px; }

/* Sprint 445 (operator design; supersedes the Sprint-443 mobile strip): on
   mobile NOTHING is added to the layout while impersonating — the topbar
   avatar becomes the switch button (orange ring + ⇄ corner badge, single tap
   opens the quick-switch panel) and a small ↩ return button appears to its
   right. Zero layout shift = zero overlap, whatever the page structure. */
.mobile-user-chip .avatar.imp-av-switch {
  box-shadow: 0 0 0 2.5px var(--tke-orange);
  position: relative; cursor: pointer;
}
.mobile-user-chip .avatar.imp-av-switch::after {
  content: "\21c4";                       /* ⇄ — no icon-font dependency */
  position: absolute; right: -6px; bottom: -6px;
  width: 16px; height: 16px; border-radius: 50%;
  background: var(--tke-orange); color: #fff;
  font-size: 10px; line-height: 16px; text-align: center; font-weight: 800;
}
.mobile-user-chip .imp-ret-ic {
  width: 30px; height: 30px; border-radius: 50%; border: 0; cursor: pointer;
  background: #fff; color: var(--verti-navy); font-size: 13px;
  margin-left: 8px; flex: 0 0 auto;
  box-shadow: 0 1px 5px rgba(15, 27, 51, 0.35);
}

/* Sprint 443 — the return / quick-switch panel (triple-tap while acting). */
.imp-home {
  display: flex; align-items: center; justify-content: center; gap: 8px;
  width: calc(100% - 32px); margin: 10px 16px 4px; padding: 11px;
  border: 0; border-radius: 12px; background-image: var(--brand-gradient);
  color: #fff; font-size: 14px; font-weight: 800; cursor: pointer;
}
.imp-qs-lbl {
  margin: 10px 18px 4px; font-size: 10.5px; font-weight: 700; color: var(--muted);
  text-transform: uppercase; letter-spacing: 0.05em;
}

.imp-card.imp-pick { max-width: 360px; }
.imp-card .imp-x {
  flex: 0 0 auto; margin-left: auto; width: 30px; height: 30px; border-radius: 8px;
  background: #F1F4F9; border: 1px solid var(--border); color: var(--muted);
  font-size: 14px; font-weight: 800; cursor: pointer;
}
.imp-search {
  width: calc(100% - 32px); margin: 4px 16px 8px; padding: 9px 11px;
  border: 1px solid var(--border); border-radius: 10px; background: #F8FAFC;
  font-size: 13px; color: var(--text);
}
.imp-list { max-height: 46vh; overflow-y: auto; padding: 0 8px 12px; }
.imp-row {
  width: 100%; display: flex; align-items: center; gap: 10px; text-align: left;
  padding: 9px 8px; background: none; border: 0; border-radius: 10px; cursor: pointer;
}
.imp-row + .imp-row { border-top: 1px solid #EEF1F6; }
.imp-row:hover { background: #F4F6FA; }
.imp-av {
  flex: 0 0 auto; width: 32px; height: 32px; border-radius: 50%;
  background: var(--verti-navy); color: #fff; font-weight: 800; font-size: 12px;
  display: grid; place-items: center;
}
.imp-ut { flex: 1; min-width: 0; display: flex; flex-direction: column; }
.imp-un { font-size: 13.5px; font-weight: 700; color: var(--verti-navy); white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.imp-ur { font-size: 10.5px; color: var(--muted); text-transform: capitalize; }
.imp-go { flex: 0 0 auto; font-size: 11px; font-weight: 800; color: #fff; background: var(--verti-navy); padding: 6px 12px; border-radius: 8px; }
.imp-row.imp-going { opacity: .5; pointer-events: none; }
.imp-empty { padding: 22px 16px; text-align: center; color: var(--muted); font-size: 12.5px; }

.modal-backdrop {
  position: fixed; inset: 0;
  background: rgba(15, 23, 42, 0.45);
  display: none; align-items: center; justify-content: center;
  /* Sprint 90: bumped to 10000 so the help modal (and every other modal)
     overlays Leaflet, which paints internal panes up to z-index 700. The
     old value (50) left the ? help modal hidden under the map on map.html. */
  z-index: 10000; padding: 16px;
}
.modal-backdrop.open { display: flex; }
.modal {
  background: #fff;
  border-radius: var(--radius);
  width: 100%;
  max-width: 480px;
  box-shadow: var(--shadow-md);
  /* Sprint 138: footer actions (Save / Close / Cancel) moved to the TOP
     of every modal so the user never has to scroll to reach them. The
     modal is a flex column — the title stays, the .modal-foot is ordered
     right under it (fixed), and the .modal-body scrolls underneath. */
  max-height: 90vh;
  display: flex; flex-direction: column; overflow: hidden;
  position: relative;
}
/* Sprint 200: two-brand gradient cap along the top of every modal (clipped
   to the rounded corners by the modal's overflow:hidden). */
.modal::before {
  content: '';
  position: absolute; top: 0; left: 0; right: 0; height: 4px;
  background: var(--brand-gradient); z-index: 2;
}
.modal h2 {
  margin: 0;
  padding: 16px 20px;
  font-size: 18px;
  color: var(--verti-navy);
  border-bottom: 1px solid var(--border);
  order: 0; flex: 0 0 auto;
}
.modal .modal-body {
  padding: 18px 20px;
  order: 1; flex: 1 1 auto; overflow-y: auto; min-height: 0;
}
.modal .modal-foot {
  padding: 12px 20px;
  display: flex; gap: 10px; justify-content: flex-end;
  border-bottom: 1px solid var(--border);
  order: 0; flex: 0 0 auto;
}

/* ---------- Toast ---------- */
.toasts {
  /* Sprint 224: above everything — Leaflet panes/controls climb to ~700-1000
     and the map grid/panels sit at 560-601, so the old z-index:100 buried
     every toast behind the map. 100000 keeps notifications on top app-wide. */
  position: fixed; right: 16px; bottom: 16px; z-index: 100000;
  display: flex; flex-direction: column; gap: 8px;
}
.toast {
  background: #1F2A40; color: #fff;
  padding: 10px 14px; border-radius: var(--radius-sm);
  box-shadow: var(--shadow-md);
  font-size: 14px; max-width: 320px;
}
.toast.success { background: var(--success); }
.toast.error   { background: var(--danger); }

/* ---------- Empty state ---------- */
.empty {
  text-align: center; padding: 40px 16px; color: var(--muted);
}

/* ---------- Utilities ---------- */
.row { display: flex; gap: 12px; flex-wrap: wrap; }
.row .col { flex: 1; min-width: 180px; }
/* Sprint 138: inside a modal, let row columns shrink below 180px so 3-4
   fields stay on a single line instead of wrapping a field to the next
   row (the 180px floor above made narrow modals wrap). */
.modal .row .col { min-width: 0; }
.text-muted { color: var(--muted); }
.text-right { text-align: right; }
.mt-2 { margin-top: 14px; }
/* `display: revert` restores the user-agent default for the element type
   (table-cell for <th>/<td>, inline for <span>, etc.) — `display: initial`
   would force everything to `inline`, which broke the user list table. */
.hide-on-mobile { display: none; }
@media (min-width: 720px) { .hide-on-mobile { display: revert; } }

/* ---------- Inline filter bar (Reports / Raw Records) ---------- */
.filter-bar {
  display: flex; flex-wrap: wrap; gap: 8px;
  align-items: center;
}
.filter-bar .filter-label {
  font-size: 11px; text-transform: uppercase; letter-spacing: 0.4px;
  color: var(--muted); font-weight: 700;
  margin: 0 4px 0 4px;
}
.filter-bar input[type="date"],
.filter-bar input[type="text"],
.filter-bar select {
  height: 36px;
  padding: 0 10px;
  border: 1px solid var(--border);
  border-radius: var(--radius-sm);
  background: #fff;
  font: inherit;
}
.filter-bar input[type="date"]:focus,
.filter-bar select:focus { outline: none; border-color: var(--verti-navy); }
.filter-bar .btn,
.filter-bar .btn-sm {
  height: 36px;
  padding: 0 14px;
  font-size: 13px;
}
.filter-sep {
  width: 1px; align-self: stretch;
  background: var(--border);
  margin: 0 6px;
}

/* Icon-only buttons (Raw Records action column) */
.btn-icon {
  display: inline-flex; align-items: center; justify-content: center;
  width: 32px; height: 32px;
  border: 1px solid var(--border);
  border-radius: var(--radius-sm);
  background: #fff;
  cursor: pointer;
  font-size: 16px;
  transition: background 0.15s, border-color 0.15s, color 0.15s;
  color: var(--text);
}
.btn-icon:hover { background: var(--verti-navy-50); border-color: var(--verti-navy); }
.btn-icon.btn-icon-danger { color: var(--danger); }
.btn-icon.btn-icon-danger:hover { background: #FBE9E9; border-color: var(--danger); }
.btn-icon:disabled { opacity: 0.5; cursor: not-allowed; }

/* ---------- Summary page ---------- */
.stat-grid {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(180px, 1fr));
  gap: 14px;
  margin-bottom: 20px;
}
.stat-card {
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: var(--radius);
  padding: 18px 18px 16px;
  box-shadow: var(--shadow-sm);
  position: relative;
  overflow: hidden;
}
/* Sprint 199: KPI cards carry a two-brand gradient strip across the top.
   Toned variants keep a solid colour strip to stay meaningful (orange /
   purple / green KPIs). */
.stat-card::before {
  content: '';
  position: absolute;
  inset: 0 0 auto 0;
  height: 4px;
  background: var(--brand-gradient);
}
.stat-card.accent::before { background: var(--tke-orange); }
.stat-card.purple::before { background: var(--tke-purple); }
.stat-card.green::before  { background: var(--success); }
/* Sprint 199 — gradient "hero" KPI card (full brand gradient, white text). */
.stat-card.hero { background: var(--brand-gradient); border-color: transparent; }
.stat-card.hero::before { display: none; }
.stat-card.hero .stat-value,
.stat-card.hero .stat-label,
.stat-card.hero .stat-sub { color: #fff; }

/* Sprint 199 — brand design-system utilities (opt-in per page).
   Gradient progress meter: <div class="brand-progress"><span style="width:72%"></span></div> */
.brand-progress { height: 14px; border-radius: 7px; background: #E8ECF3; overflow: hidden; }
.brand-progress > span { display: block; height: 100%; border-radius: 7px; background: var(--brand-gradient); }
/* Gradient ring (for a % gauge or an avatar): <div class="brand-ring"><span>52%</span></div> */
.brand-ring { width: 56px; height: 56px; border-radius: 50%; display: grid; place-items: center;
  background: conic-gradient(from -90deg, #1D3360, #7A2E6B, #E85A2A); }
.brand-ring::before { content: ''; grid-area: 1 / 1; width: 44px; height: 44px; border-radius: 50%; background: var(--surface); }
.brand-ring > * { grid-area: 1 / 1; z-index: 1; font-weight: 800; font-size: 14px; color: var(--verti-navy); }
/* Active tab/pill in the brand gradient. */
.brand-tab.active { background: var(--brand-gradient); color: #fff; border-color: transparent; }
.stat-card .stat-value {
  font-size: 30px; font-weight: 800; color: var(--text);
  line-height: 1.1; margin-bottom: 4px;
}
.stat-card .stat-label {
  font-size: 12px; text-transform: uppercase; letter-spacing: 0.5px;
  color: var(--muted); font-weight: 600;
}
.stat-card .stat-sub {
  font-size: 12px; color: var(--muted); margin-top: 6px;
}

.breakdown-grid {
  display: grid;
  grid-template-columns: 1fr;
  gap: 16px;
}
@media (min-width: 880px) {
  .breakdown-grid { grid-template-columns: repeat(2, 1fr); }
}

.breakdown {
  display: flex; flex-direction: column; gap: 8px;
}
.bar-row {
  display: grid;
  grid-template-columns: minmax(110px, 30%) 1fr 50px;
  align-items: center;
  gap: 10px;
  font-size: 13px;
}
.bar-row .bar-label {
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
  color: var(--text);
}
.bar-row .bar-track {
  background: var(--verti-navy-50);
  border-radius: 999px;
  height: 10px; overflow: hidden;
}
.bar-row .bar-fill {
  background: var(--verti-navy);
  height: 100%; border-radius: 999px;
  transition: width 0.4s ease;
}
.bar-row.accent .bar-fill { background: var(--tke-orange); }
.bar-row.purple .bar-fill { background: var(--tke-purple); }
.bar-row.green  .bar-fill { background: var(--success); }
.bar-row .bar-count {
  text-align: right; font-weight: 700; color: var(--text);
}

/* Spinner */
.spinner {
  width: 18px; height: 18px;
  border: 2px solid rgba(255,255,255,0.4);
  border-top-color: #fff;
  border-radius: 50%;
  animation: spin 0.7s linear infinite;
}
@keyframes spin { to { transform: rotate(360deg); } }

/* ---------------- Map page ---------------- */
/* Sprint 124: the map fills the full width; the Coordinates / Missing
 * Coordinates panel is a collapsible drawer overlaying its right edge. */
.map-layout {
  position: relative;
  overflow: hidden;            /* clip the drawer when it slides off-right */
}
.map-canvas {
  height: calc(100vh - 180px);
  min-height: 420px;
  width: 100%;
  border-radius: var(--radius);
  overflow: hidden;
  border: 1px solid var(--border);
  background: var(--surface);
}
.map-side {
  position: absolute;
  top: 10px;
  right: 10px;
  z-index: 600;
  width: 320px;
  max-width: 86vw;
  max-height: calc(100% - 20px);
  display: flex;
  flex-direction: column;
  overflow: auto;
  transition: transform .25s ease, opacity .2s ease;
}
.map-side.collapsed {
  transform: translateX(calc(100% + 16px));
  opacity: 0;
  pointer-events: none;
}
/* Drawer handle: sits at the panel's left edge; when the panel is collapsed
 * it stays at the map's right edge (sibling combinator keys off .collapsed). */
.map-side-toggle {
  position: absolute;
  top: 10px;
  right: 338px;
  z-index: 601;
  width: 30px;
  height: 40px;
  border: 1px solid var(--border);
  border-radius: 8px;
  background: #fff;
  color: var(--verti-navy);
  cursor: pointer;
  box-shadow: var(--shadow-sm);
  display: flex;
  align-items: center;
  justify-content: center;
  transition: right .25s ease;
}
.map-side.collapsed ~ .map-side-toggle { right: 10px; }
@media (max-width: 880px) {
  .map-side { width: 300px; }
  .map-side-toggle { right: 318px; }
  .map-side.collapsed ~ .map-side-toggle { right: 10px; }
}

/* Sprint 124: header toolbar that carries the three map controls on the
 * title row (Pick user | Live tracking | GPS events). */
.map-headrow {
  display: flex;
  align-items: center;
  gap: 14px;
  flex-wrap: wrap;
}
.map-toolbar {
  display: flex;
  align-items: center;
  gap: 8px;
  flex-wrap: wrap;
}
.map-tool {
  display: flex;
  align-items: center;
  gap: 6px;
  margin: 0;
  background: #fff;
  border: 1px solid var(--border);
  border-radius: 8px;
  padding: 5px 9px;
  font-size: 13px;
  font-weight: 600;
  box-shadow: var(--shadow-sm);
}
.map-tool-check { cursor: pointer; }
.map-tool-check input { margin: 0; }
/* Sprint 161: drag-to-reposition visual cue. The marker the user armed
   via its popup's "Edit position" link gets .vpin-edit — an amber halo
   + grab cursor — so it's obvious which pin is the one being moved. */
.leaflet-marker-icon.vpin-edit { cursor: grab; }
.leaflet-marker-icon.vpin-edit.leaflet-marker-dragging { cursor: grabbing; }
.leaflet-marker-icon.vpin-edit .vpin {
  box-shadow: 0 0 0 2px #fff, 0 0 0 4px #B45309, 0 2px 6px rgba(0,0,0,.25);
}
/* Sprint 164: the focused marker (clicked on the map, in the Coordinates
   list, or as a GPS event) gets a navy halo — distinct from the amber
   edit halo above. Works for both project pins (.vpin) and event dots
   (.vevent). */
.leaflet-marker-icon.marker-selected .vpin,
.leaflet-marker-icon.marker-selected .vevent {
  box-shadow: 0 0 0 2px #fff, 0 0 0 4px #1D3360, 0 2px 8px rgba(27,58,107,.4);
}
.leaflet-marker-icon.marker-selected { z-index: 10000 !important; }
/* Sprint 164: GPS-events toolbar group — date input + no-location badge. */
#gpsTool { display: inline-flex; align-items: center; gap: 8px; }
.gps-noloc-badge {
  background: #FEF3C7; color: #92400E; border: 1px solid #FDE68A;
  font-size: 11px; font-weight: 700; padding: 2px 8px; border-radius: 999px;
  white-space: nowrap;
}
/* Sprint 164: project + unit rows inside the GPS-event popup. */
.leaflet-popup-content .gps-pop-meta {
  margin: 6px 0; padding: 6px 0; border-top: 1px solid var(--border);
  border-bottom: 1px solid var(--border); font-size: 12px; line-height: 1.6;
}
.leaflet-popup-content .gps-pop-meta i { color: var(--verti-navy); margin-right: 4px; }
.map-edit-banner {
  position: fixed;
  top: 88px;
  left: 50%;
  transform: translateX(-50%);
  z-index: 1500;
  background: #FFF7ED;
  color: #7C2D12;
  border: 1px solid #FDBA74;
  border-radius: 999px;
  padding: 6px 14px 6px 12px;
  font-size: 13px;
  display: flex;
  gap: 10px;
  align-items: center;
  box-shadow: 0 6px 18px rgba(180,83,9,.18);
}
.map-edit-banner i { color: #B45309; }
.map-edit-banner .btn { padding: 2px 10px; }
/* Sprint 161: click-to-place. While armed, the whole Leaflet pane gets
   a crosshair cursor so the user knows the next click drops the pin. */
body.map-set-location .leaflet-container,
body.map-set-location .leaflet-grab,
body.map-set-location .leaflet-clickable { cursor: crosshair !important; }
.missing-list .set-loc-link {
  display: inline-block;
  margin-top: 4px;
  font-size: 12px;
  font-weight: 600;
  color: #B45309;
  text-decoration: none;
}
.missing-list .set-loc-link:hover { text-decoration: underline; }
.missing-list .set-loc-link i { margin-right: 3px; }
.map-tool-sel,
.map-tool-date {
  font-size: 13px;
  padding: 2px 4px;
  border: 1px solid var(--border);
  border-radius: 6px;
  background: #fff;
}
.map-tool .btn-sm { padding: 2px 8px; }
.map-side .missing-list {
  list-style: none; padding: 0; margin: 0;
}
.map-side .missing-list li {
  padding: 8px 0;
  border-bottom: 1px solid var(--border);
  font-size: 13px;
}
.map-side .missing-list li:last-child { border-bottom: 0; }
.map-side .missing-list a { color: var(--verti-navy); font-weight: 600; }
.map-side .missing-list .region { color: var(--muted); font-size: 12px; }

/* Marker popup contents */
.leaflet-popup-content {
  margin: 12px 14px;
  font-family: inherit;
  font-size: 13px;
  line-height: 1.45;
  color: var(--text);
  max-width: 280px;
}
.leaflet-popup-content h3 {
  margin: 0 0 6px;
  font-size: 15px;
  color: var(--verti-navy);
}
.leaflet-popup-content .popup-region {
  color: var(--muted); font-size: 12px; margin-bottom: 8px;
}
.leaflet-popup-content .popup-notes { margin: 8px 0; }
.leaflet-popup-content .popup-notes p:first-child { margin-top: 0; }
.leaflet-popup-content .popup-notes p:last-child  { margin-bottom: 0; }
.leaflet-popup-content .popup-actions {
  margin-top: 8px; padding-top: 8px; border-top: 1px solid var(--border);
}
.leaflet-popup-content .popup-actions a {
  color: var(--verti-navy); font-weight: 600;
}
/* Sprint 161: popup actions collapsed to a row of compact icon buttons —
   tooltip carries the label so the popup stays small. */
.leaflet-popup-content .popup-icon-actions {
  display: flex; gap: 8px; align-items: center;
}
.leaflet-popup-content .popup-icon-btn {
  display: inline-flex; align-items: center; justify-content: center;
  width: 30px; height: 30px; border-radius: 6px;
  background: var(--verti-navy-50); color: var(--verti-navy);
  text-decoration: none; transition: background .12s, color .12s;
}
.leaflet-popup-content .popup-icon-btn:hover {
  background: var(--verti-navy); color: #fff;
}
.leaflet-popup-content .popup-icon-btn.popup-icon-danger {
  background: #FEE2E2; color: #B91C1C;
}
.leaflet-popup-content .popup-icon-btn.popup-icon-danger:hover {
  background: #B91C1C; color: #fff;
}

/* ---------------- Chart cards (Summary / Reports / Dashboard) ------- */
.chart-box {
  min-height: 300px;
}
.chart-box .empty { text-align: center; }
/* ApexCharts inherits the page font; keep its text on-brand. */
.apexcharts-tooltip {
  border: 1px solid var(--border) !important;
  box-shadow: var(--shadow-md) !important;
}
.apexcharts-tooltip-title {
  background: var(--verti-navy-50) !important;
  border-bottom: 1px solid var(--border) !important;
  font-weight: 600;
}

/* ============================================================== */
/* Mobile actions shell (Sprint 88) — four big buttons + scanner   */
/* ============================================================== */
.mobile-body { background: var(--verti-navy-50); margin: 0; }
.mobile-shell {
  min-height: 100vh; min-height: 100dvh;
  display: flex; flex-direction: column;
  max-width: 480px; margin: 0 auto;
  padding: 14px 8px 24px;                            /* Sprint 278: side gap 16→8px */
  position: relative;
}
.mobile-topbar {
  display: flex; align-items: center; justify-content: space-between;
  padding-bottom: 12px; border-bottom: 1px solid var(--border);
}
/* Sprint 459: the Sprint-89 wide-banner topbar (.mobile-brand /
   .mobile-brand-logo / .mobile-brand-tagline) was dead CSS — the topbar was
   rebuilt around .mobile-brand-mark + .mobile-wordmark long ago. Removed. */
/* Sprint 129: chip just hosts the round avatar now (the full name is
 * gone from the topbar — see mobile.html). The .avatar class handles the
 * round navy circle + initials, so this wrapper only needs to keep its
 * place in the topbar's flex row. */
.mobile-user-chip { flex-shrink: 0; display: flex; align-items: center; }

.mobile-prefill {
  display: flex; align-items: center; gap: 10px;
  margin: 14px 0 4px;
  padding: 10px 12px;
  background: #E5F4EC; color: #235E45;
  border: 1px solid #B7E1CC; border-radius: 10px;
}
.mobile-prefill > i { font-size: 20px; }
.mobile-prefill-text { flex: 1; min-width: 0; }
.mobile-prefill-title { font-weight: 700; font-size: 15px; }
.mobile-prefill-sub { font-size: 12px; color: var(--text-muted); }
.mobile-prefill-clear {
  background: none; border: 0; font-size: 22px;
  color: #235E45; cursor: pointer; line-height: 1; padding: 0 4px;
}

.mobile-actions {
  flex: 1; display: flex; flex-direction: column; gap: 14px;
  padding: 18px 0 0;
}
.mobile-action-btn {
  position: relative;
  background: #fff; border: 1px solid var(--border); border-radius: 14px;
  padding: 18px 18px 18px 22px;
  display: flex; align-items: center; gap: 16px;
  min-height: 78px;
  font: 600 18px/1.2 inherit; color: var(--verti-navy);
  text-align: left; cursor: pointer;
  box-shadow: 0 1px 2px rgba(0,0,0,0.04);
  transition: transform .08s ease, box-shadow .15s ease;
}
.mobile-action-btn:active { transform: scale(0.985); box-shadow: 0 1px 1px rgba(0,0,0,0.06); }
.mobile-action-btn[disabled] { opacity: 0.5; cursor: progress; }
.mobile-action-icon { font-size: 28px; color: var(--verti-navy); width: 32px; text-align: center; }
.mobile-action-label { flex: 1; }
.mobile-action-scan {
  font-size: 22px; color: #E85A2A;
  background: #FFF4EE; border-radius: 8px; padding: 6px 8px;
}
/* Sprint 127: FORM badge (pen-to-square) — sits to the LEFT of the QR
 * badge on each data-entry button. Opens the matching APK form page on
 * tap; its handler stops propagation so the parent button's QR scan
 * does not fire. */
.mobile-action-form {
  display: inline-flex; align-items: center; justify-content: center;
  font-size: 20px; color: var(--verti-navy);
  background: #DCE6F4; border-radius: 8px; padding: 6px 8px;
  cursor: pointer;
  margin-left: auto;
}
.mobile-action-form:active { transform: scale(0.95); }
.mobile-action-btn .mobile-action-form + .mobile-action-scan { margin-left: 6px; }
/* When the active-service card is on screen, S/F/P tiles are dimmed
 * but still tappable (we intercept the click to show a toast so the
 * user knows why). Photo, Map and Manual stay live. */
.mobile-action-btn.disabled-by-active { opacity: 0.45; }
.mobile-action-btn.disabled-by-active .mobile-action-icon,
.mobile-action-btn.disabled-by-active .mobile-action-label,
.mobile-action-btn.disabled-by-active .mobile-action-form,
.mobile-action-btn.disabled-by-active .mobile-action-scan {
  color: #9CA3AF !important; background: #F3F4F6 !important;
}
/* Sprint 127: notification bubbles in the mobile topbar (replacing the
 * brand logo + tagline). One per category — S/F/P — sized to be
 * thumb-friendly. Hidden via [hidden] until the user has unread items
 * in that category. */
/* Sprint 129 (revised): the V-shaped Verti mark on the far-left of the
 * mobile topbar — a navy rounded square with a white V, mirroring the APK
 * launcher icon.  Rendered as inline SVG inside this span, so size +
 * shrink-resist live here. */
.mobile-brand-mark {
  display: inline-flex; align-items: center; justify-content: center;
  width: 28px; height: 28px;
  flex-shrink: 0;
  /* Sprint 283: the real VERTI V badge (circular — navy V + a thin white ring)
     replaces the inline chevron SVG. One CSS swap applies it to every mobile
     page; the original topbar size/layout is unchanged. */
  background: url('../img/verti-v-badge.png') center / contain no-repeat;
}
/* Sprint 459: the inline chevron SVG left TOPBAR_HTML (it had been display:none
   since the Sprint-283 PNG badge), and the Sprint-129 floating home FAB left
   the pages (it flashed on every load until mountMobileChrome removed it) —
   both rule sets removed. */
/* Sprint 129: common form-page H1 — coloured swatch + bold title. */
.mobile-page-h1 {
  display: flex; align-items: center; gap: 10px;
  margin: 14px 0 10px;
}
.mobile-page-swatch { width: 4px; height: 22px; border-radius: 2px; }
/* Sprint 234: the old `font: 800 18px/1.1 inherit` was an INVALID shorthand
   (inherit is not a valid family there) → the whole rule was dropped and the
   title fell back to the browser's huge default h1. Match the VERTI AIM
   wordmark (16px / 900), navy. */
.mobile-page-h1 h1 { font-size: 16px; font-weight: 900; line-height: 1.1; color: var(--verti-navy); margin: 0; }
/* Sprint 299: "new record" action in the page-title row — sits at the far
   right of the h1, title-height, brand gradient (operator's placement rule:
   every mobile page's create button lives top-right beside the title). */
.mobile-h1-action { margin-left: auto; border: 0; cursor: pointer; border-radius: 9px;
  padding: 9px 13px; font-size: 12px; font-weight: 800; color: #fff;
  background: var(--brand-gradient); display: inline-flex; align-items: center;
  gap: 6px; text-decoration: none; white-space: nowrap; }
/* Common select / input bits used inside the form pages. */
.mobile-field { margin-bottom: 12px; }
.mobile-field label { display: block; font-size: 11px; font-weight: 700;
  color: #374151; margin-bottom: 5px; }
.mobile-field select, .mobile-field input[type="text"],
.mobile-field input[type="date"], .mobile-field input[type="datetime-local"],
.mobile-field textarea {
  width: 100%; padding: 11px 12px; border: 1px solid var(--border);
  border-radius: 10px; font-size: 14px; background: #fff; box-sizing: border-box;
  font-family: inherit;
}
.mobile-field textarea { min-height: 80px; resize: vertical; }
/* Status filter chips row reused across list pages. */
.mobile-chips { display: flex; gap: 6px; flex-wrap: wrap; margin: 0 0 12px; }
.mobile-chip {
  padding: 6px 12px; border-radius: 999px; font-size: 11px; font-weight: 700;
  background: #fff; color: #374151; border: 1px solid var(--border); cursor: pointer;
}
.mobile-chip.active { background: var(--verti-navy); color: #fff; border-color: var(--verti-navy); }
/* Card row used by the list-style pages (Service / Part / Fault open list). */
.mobile-card {
  background: #fff; border: 1px solid var(--border); border-radius: 10px;
  padding: 12px 14px; margin-bottom: 10px;
  display: flex; align-items: center; gap: 10px;
  border-left-width: 4px;
}
.mobile-card .body { flex: 1; min-width: 0; }
.mobile-card .title { font: 700 13px/1.2 inherit; color: #1D3360; }
.mobile-card .sub   { font: 500 11px/1.3 inherit; color: #6B7280; margin-top: 3px; }
.mobile-card .meta  { font: 600 10px/1 inherit; margin-top: 5px; }
.mobile-card.s-open    { border-left-color: #C0392B; }
.mobile-card.s-prog    { border-left-color: #FFB300; }
.mobile-card.s-done    { border-left-color: #2E7D32; }
.mobile-card.s-pending { border-left-color: #1D3360; }
.mobile-card .meta.s-open    { color: #991B1B; }
.mobile-card .meta.s-prog    { color: #B7791F; }
.mobile-card .meta.s-done    { color: #15803D; }
.mobile-card .meta.s-pending { color: #1E40AF; }
.mobile-card .card-action {
  /* Sprint 239: bigger tap target for gloved / dirty field hands (was 42px). */
  width: 52px; height: 52px; border-radius: 12px; border: 0;
  display: flex; align-items: center; justify-content: center; cursor: pointer;
  font-size: 20px; flex-shrink: 0;
}
.mobile-card .card-action.start    { background: #FFB300; color: #fff; }
.mobile-card .card-action.complete { background: #16A34A; color: #fff; }
.mobile-card .card-action.view     { background: #fff; color: #374151; border: 1px solid var(--border); }
.mobile-topbar { gap: 10px; }
/* Sprint 459: the legacy S/F/P .mobile-notif-bubble cluster was removed from
   TOPBAR_HTML (never filled by any code, permanently hidden under .msh-app) —
   its CSS went with it. */
/* Sprint 127: live "active operation" card. Lives between the topbar
 * and the action grid; visible only while the user has an in-progress
 * service (initially) or fault / part-replacement (later). The clock
 * ticks in JS (mobile-actions.js). */
.mobile-active-card {
  margin: 14px 0 0;
  padding: 14px 16px;
  background: #E6F4EA;
  border: 1.6px solid #2E7D32;
  border-radius: 14px;
  display: flex; flex-direction: column; gap: 6px;
}
.mobile-active-card[hidden] { display: none !important; }
.mobile-active-head {
  display: flex; align-items: center; justify-content: space-between;
}
.mobile-active-type {
  font-size: 11px; font-weight: 800; letter-spacing: 0.6px;
  color: #15803D;
}
.mobile-active-live {
  display: flex; align-items: center; gap: 5px;
  font-size: 9px; font-weight: 800; color: #15803D;
}
.mobile-active-dot {
  width: 10px; height: 10px; border-radius: 50%;
  background: #10B981;
  /* Sprint 243: brighter, ringed "LED" glow so the live state reads at a
     glance on a dirty field phone. */
  box-shadow: 0 0 0 3px rgba(16,185,129,.28), 0 0 8px 2px rgba(16,185,129,.80);
  animation: mActiveDotPulse 1.5s ease-in-out infinite;
}
@keyframes mActiveDotPulse { 0%,100% { opacity: .35; } 50% { opacity: 1; } }
.mobile-active-title { font-size: 14px; font-weight: 700; color: var(--verti-navy); }
.mobile-active-sub   { font-size: 11px; font-weight: 500; color: #475569; }
.mobile-active-foot {
  display: flex; align-items: center; justify-content: space-between;
  margin-top: 4px;
}
.mobile-active-clock {
  font-family: ui-monospace, Menlo, Monaco, Consolas, monospace;
  font-size: 18px; font-weight: 800; color: #15803D;
}
.mobile-active-complete {
  display: inline-flex; align-items: center; gap: 6px;
  padding: 8px 16px; border-radius: 10px;
  background: #2E7D32; color: #fff;
  border: 0; cursor: pointer;
  font-size: 13px; font-weight: 700;
}
.mobile-active-complete:active { transform: scale(.97); }
.mobile-active-complete[disabled] { opacity: 0.6; cursor: progress; }

/* Sprint 252: the close-action pair — QR "Complete" + a "Close without QR"
   ghost fallback for a technician who can't / forgot to scan. */
.mobile-active-btns { display: flex; align-items: center; gap: 8px; }
/* Sprint 432 hotfix: `hidden` attr loses to display:flex without this
   (the classic display-vs-[hidden] pattern). Without it, the
   Finish buttons (service-only, live-card.js hides them for fault/part/
   inspection/correction ops) stayed fully visible on a non-service active
   card; tapping them silently no-op'd (the click handler already guards on
   activeOp.type !== 'service'), reading as "the close button doesn't work". */
.mobile-active-btns[hidden] { display: none !important; }
.mobile-active-manualclose {
  display: inline-flex; align-items: center; justify-content: center; gap: 6px;
  padding: 8px 12px; border-radius: 10px;
  background: #fff; color: var(--verti-navy);
  border: 1.4px solid #C7D2E6; cursor: pointer;
  font-size: 12px; font-weight: 700;
}
.mobile-active-manualclose:active { transform: scale(.97); }
.mobile-active-manualclose[disabled] { opacity: 0.6; cursor: progress; }
.mobile-active-card.is-warn .mobile-active-manualclose { border-color: #F3C0C0; color: #B91C1C; }

/* Sprint 246: tiered "warn" state — a service open ≥ 2h, or one whose device
   left the project geofence. The whole card flips to a red treatment, the LED
   blinks red, and a reminder line appears so the technician closes it. */
.mobile-active-card.is-warn {
  background: #FEECEC;
  border-color: #DC2626;
}
.mobile-active-card.is-warn .mobile-active-type,
.mobile-active-card.is-warn .mobile-active-live,
.mobile-active-card.is-warn .mobile-active-clock { color: #B91C1C; }
.mobile-active-card.is-warn .mobile-active-dot {
  background: #EF4444;
  box-shadow: 0 0 0 3px rgba(239,68,68,.30), 0 0 8px 2px rgba(239,68,68,.85);
}
.mobile-active-card.is-warn .mobile-active-complete { background: #DC2626; }
.mobile-active-warn {
  display: flex; align-items: center; gap: 7px;
  font-size: 11.5px; font-weight: 700; color: #B91C1C;
  background: #fff; border: 1px solid #F3C0C0; border-radius: 8px;
  padding: 6px 10px;
}
.mobile-active-warn[hidden] { display: none !important; }
.mobile-active-warn::before { content: "\26A0"; font-size: 12px; } /* ⚠ */

/* Sprint 127: pages opened from the APK launcher (`?apk=1` -> body.is-apk)
 * strip the desktop chrome: the sidebar, the brand banner, the language
 * switcher, the help button and the hamburger all disappear; the content
 * grid drops to a single column; only the small notification chips and
 * the user/logout cluster remain in the topbar. The page-level scripts
 * keep working unchanged. */
@media (min-width: 0) {
  body.is-apk .sidebar,
  body.is-apk .menu-btn,
  body.is-apk .brand-row,
  body.is-apk .lang-switch,
  body.is-apk .brand-program-name,
  body.is-apk #helpBtn { display: none !important; }
  body.is-apk .layout { grid-template-columns: 1fr !important; }
  body.is-apk .topbar { padding: 6px 12px; gap: 6px; }
  body.is-apk main.content { padding: 12px 12px 24px; }
}
/* Sprint 459: dead .mobile-manual / .mobile-logout-btn rules removed (no
   matching markup anywhere). */

/* Make the [hidden] attribute decisively win over .mobile-prefill,
   .scan-overlay and .modal-backdrop, all of which set display:flex.
   Author single-class selectors otherwise out-rank the UA's [hidden]
   single-attr rule (same specificity, later in cascade) and the modal
   pops open on first render. */
.mobile-prefill[hidden],
#confirmModal[hidden] { display: none !important; }

/* Scanner overlay — fullscreen camera view with framed center */
.scan-overlay {
  position: fixed; inset: 0; z-index: 9999;
  background: #000; display: flex; align-items: center; justify-content: center;
}
.scan-overlay[hidden] { display: none !important; }
.scan-overlay video {
  width: 100%; height: 100%; object-fit: cover;
}
.scan-frame {
  position: absolute; top: 50%; left: 50%; transform: translate(-50%, -50%);
  width: min(70vw, 320px); height: min(70vw, 320px);
  border: 3px solid #FFF; border-radius: 16px;
  box-shadow: 0 0 0 9999px rgba(0,0,0,0.45);
}
.scan-help {
  position: absolute; bottom: 14%; left: 0; right: 0;
  text-align: center; color: #fff; font-weight: 600; font-size: 15px;
  text-shadow: 0 1px 3px rgba(0,0,0,0.6);
  padding: 0 16px;
}
.scan-cancel {
  position: absolute; top: 14px; right: 14px;
  width: 44px; height: 44px; border-radius: 50%;
  border: 0; background: rgba(255,255,255,0.18); color: #fff;
  font-size: 28px; line-height: 1; cursor: pointer;
}

/* Sprint 123: branding line above the unit-barcode QR. Same font
 * character as the label lines below it (Arial, weight 700) — just a
 * slightly larger point size. One line (white-space:nowrap), solid
 * black, overflow:hidden as a hard backstop against an unexpected spill. */
.qr-url {
  width: 100%;
  margin: 0 auto 2px;
  text-align: center;
  font-weight: 700;
  font-size: 7pt;
  line-height: 1.2;
  color: #1D3360;
  white-space: nowrap;
  overflow: hidden;
}


/* ===== Sprint 136: GPS Reports ===== */
.gpsr-filters {
  display: flex; gap: 12px; align-items: flex-end; flex-wrap: wrap;
  padding: 14px 16px; border-bottom: 1px solid var(--border);
}
.gpsr-filters .pf { display: flex; flex-direction: column; gap: 4px; }
.gpsr-filters label { font-size: 12px; font-weight: 600; color: var(--muted); }
.gpsr-filters input, .gpsr-filters select {
  padding: 8px 10px; border: 1px solid var(--border); border-radius: var(--radius-sm); font: inherit;
}
.gpsr-gran { display: inline-flex; border: 1px solid var(--border); border-radius: 999px; overflow: hidden; }
.gpsr-gran button {
  padding: 8px 14px; border: 0; background: #fff; cursor: pointer;
  font: inherit; font-weight: 700; font-size: 12px; color: var(--verti-navy);
}
.gpsr-gran button.active { background: var(--verti-navy); color: #fff; }

.gpsr-kpis { display: grid; grid-template-columns: repeat(auto-fit, minmax(150px, 1fr)); gap: 12px; padding: 16px; }
.gpsr-kpi {
  position: relative; background: #fff; border: 1px solid var(--border);
  border-radius: var(--radius-sm); padding: 12px 14px; overflow: hidden;
}
/* Sprint 200: GPS report KPI accent moved to a top strip — the two-brand
   gradient by default, or a meaningful solid colour when one is passed. */
.gpsr-kpi::before { content: ""; position: absolute; left: 0; right: 0; top: 0; height: 4px; background: var(--accent, var(--brand-gradient)); }
.gpsr-kpi .k-label { font-size: 10px; font-weight: 700; letter-spacing: .5px; color: var(--muted); text-transform: uppercase; }
.gpsr-kpi .k-value { font-size: 22px; font-weight: 800; color: var(--text); margin-top: 4px; }
.gpsr-kpi .k-sub { font-size: 10px; color: var(--muted); margin-top: 2px; }

/* Timeline bands — Sprint 144 unified palette (shared with the live map
 * via Gps::liveStateFor: at_project=green, travel=blue, idle=red,
 * offline=grey, lunch=white pill with "LUNCH" label). */
.gpsr-timeline { position: relative; height: 30px; background: #F3F4F6; border-radius: 6px; overflow: hidden; }
.gpsr-band { position: absolute; top: 0; bottom: 0; }
.gpsr-band.at_project { background: #16A34A; }
.gpsr-band.travel     { background: #2563EB; }
.gpsr-band.idle       { background: #DC2626; }
.gpsr-band.offline    { background: #9CA3AF; }
.gpsr-band.lunch      { background: #FFFFFF; border-top:1px solid #9CA3AF; border-bottom:1px solid #9CA3AF;
  color:#28344A; font:800 9px/30px Arial, sans-serif; text-align:center; letter-spacing:.4px; overflow:hidden; }
.gpsr-band.lunch::before { content:'LUNCH'; }
.gpsr-band.off_clock  { background: #E5E7EB; }
.gpsr-axis { position: relative; height: 16px; margin-top: 2px; }
.gpsr-axis span { position: absolute; font-size: 9px; color: var(--muted); transform: translateX(-50%); }
.gpsr-legend { display: flex; gap: 14px; flex-wrap: wrap; margin: 10px 0; font-size: 11px; }
.gpsr-legend i { display: inline-block; width: 12px; height: 10px; border-radius: 2px; margin-right: 4px; vertical-align: middle; }

/* Diverging balance bar */
.gpsr-bal { position: relative; height: 16px; }
.gpsr-bal .deficit { position: absolute; right: 50%; height: 16px; background: #DC2626; border-radius: 2px 0 0 2px; }
.gpsr-bal .overtime { position: absolute; left: 50%; height: 16px; background: #059669; border-radius: 0 2px 2px 0; }
.gpsr-bal .axis { position: absolute; left: 50%; top: -2px; bottom: -2px; width: 1px; background: var(--muted); }

.gpsr-net-pos { color: #059669; font-weight: 800; }
.gpsr-net-neg { color: #DC2626; font-weight: 800; }
.gpsr-flag { color: #DC2626; font-weight: 700; }
.gpsr-bar { height: 12px; border-radius: 6px; background: #F3F4F6; overflow: hidden; }
.gpsr-bar > span { display: block; height: 100%; border-radius: 6px; }

/* ---- Reusable file-upload field (Sprint 181, assets/js/file-upload.js) ---- */
.upl-field { margin-bottom: 12px; }
.upl-label { font-weight: 600; font-size: 12px; color: var(--muted); margin-bottom: 5px; }
.upl-zone {
  display: flex; align-items: center; gap: 8px;
  padding: 9px 12px; border: 1.5px dashed #A9BEDC; border-radius: 7px;
  background: #F5F9FE; color: var(--verti-navy); font-size: 12px; font-weight: 600;
  cursor: pointer; transition: background .12s, border-color .12s;
}
.upl-zone:hover, .upl-zone:focus { background: #ECF3FC; border-color: var(--verti-navy); outline: none; }
.upl-zone.drag { background: #FBE6DD; border-color: var(--tke-orange); color: var(--tke-orange); }
.upl-zone i { font-size: 14px; }
.upl-list { margin-top: 6px; display: flex; flex-direction: column; gap: 5px; }
.upl-empty { font-size: 11px; color: var(--muted); padding: 2px 2px; }
.upl-item {
  display: flex; align-items: center; gap: 8px;
  padding: 5px 8px; border: 1px solid var(--border); border-radius: 6px;
  background: var(--surface); font-size: 12px;
}
.upl-item.busy { opacity: .7; }
.upl-item.editing { border-color: var(--tke-orange); }
.upl-name { flex: 1; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
  color: var(--text); text-decoration: none; }
a.upl-name { color: var(--verti-navy); }
a.upl-name:hover { text-decoration: underline; }
.upl-busy { font-size: 11px; color: var(--muted); font-style: italic; }
.upl-edit { flex: 1; min-width: 0; padding: 3px 6px; border: 1px solid var(--tke-orange);
  border-radius: 5px; font-size: 12px; }
.upl-ico {
  flex: 0 0 auto; width: 24px; height: 24px; display: inline-flex; align-items: center;
  justify-content: center; border: 1px solid var(--border); border-radius: 5px;
  background: var(--surface); color: var(--muted); cursor: pointer; font-size: 11px;
}
.upl-ico:hover { background: var(--bg); color: var(--text); }
.upl-ico.danger:hover { color: var(--danger); border-color: var(--danger); }
.upl-ico.ok { color: var(--success); border-color: var(--success); }

/* ===== Puantaj (Sprint 183) ===== */
.btn.ghost { background: var(--surface); color: var(--verti-navy); border-color: var(--border); }
.btn.ghost:hover { background: var(--bg); }
.btn.small { padding: 6px 10px; font-size: 13px; }
.btn.icon { padding: 8px 10px; }
.btn.small.icon { padding: 5px 8px; }
.btn.danger { background: var(--danger); }
.btn.danger.ghost { background: var(--surface); color: var(--danger); border-color: var(--border); }
.btn.danger.ghost:hover { border-color: var(--danger); }

.pnt-wrap { padding: 0 0 24px; }
.pnt-controls { display: flex; align-items: center; gap: 10px; flex-wrap: wrap; padding: 14px 16px 4px; }
.pnt-datenav { display: flex; align-items: center; gap: 6px; }
.pnt-datenav input[type=date] { padding: 7px 10px; border: 1px solid var(--border); border-radius: var(--radius-sm); }
.pnt-userwrap { display: flex; align-items: center; gap: 6px; }
.pnt-userwrap label { font-size: 12px; color: var(--muted); }
.pnt-userwrap select { padding: 7px 10px; border: 1px solid var(--border); border-radius: var(--radius-sm); max-width: 60vw; }
.pnt-head { padding: 2px 16px; }
.pnt-shift { font-size: 12px; color: var(--muted); }

.pnt-kpis { display: grid; grid-template-columns: repeat(3,1fr); gap: 8px; padding: 10px 16px; }
.pnt-kpi { border-radius: var(--radius-sm); padding: 12px 8px; text-align: center; }
.pnt-kpi b { display: block; font-size: 18px; }
.pnt-kpi span { font-size: 11px; }
.pnt-kpi.a { background: #DEF1E8; color: #2E9E6B; }
.pnt-kpi.b { background: var(--verti-navy-50); color: var(--verti-navy); }
.pnt-kpi.c { background: #FEF3C7; color: #B45309; }

.pnt-flow-head { display: flex; align-items: center; justify-content: space-between; padding: 8px 16px 2px; }
.pnt-flow-head span { font-size: 11px; font-weight: 700; letter-spacing: .04em; color: var(--muted); }
.pnt-bar { padding: 4px 16px 8px; }

.pnt-flow { padding: 4px 12px 0; position: relative; }
.pnt-flow::before { content: ''; position: absolute; left: 60px; top: 6px; bottom: 6px; width: 2px; background: var(--border); }
.pnt-item { display: flex; align-items: flex-start; gap: 8px; position: relative; margin-bottom: 8px; }
.pnt-time { width: 42px; flex: none; text-align: right; font-variant-numeric: tabular-nums; font-size: 11px; color: var(--muted); padding-top: 6px; }
.pnt-time span { display: block; }
.pnt-dot { width: 10px; height: 10px; border-radius: 50%; margin: 9px 0 0 4px; flex: none; z-index: 1; box-shadow: 0 0 0 2px var(--surface); }
.pnt-card { flex: 1; min-width: 0; background: var(--surface); border: 1px solid var(--border); border-left-width: 3px; border-radius: var(--radius-sm); padding: 8px 10px; cursor: pointer; }
.pnt-card:hover { box-shadow: var(--shadow-sm); }
.pnt-card-head { display: flex; align-items: center; gap: 8px; }
.pnt-name { font-weight: 600; font-size: 14px; flex: 1; min-width: 0; }
.pnt-src { font-size: 10px; font-weight: 700; padding: 2px 7px; border-radius: 9px; flex: none; }
.pnt-src.gps { background: var(--verti-navy-50); color: var(--verti-navy); }
.pnt-src.manual { background: #EDEFF3; color: var(--muted); }
.pnt-cmeta { font-size: 11px; color: var(--muted); margin-top: 2px; }
.pnt-note { font-size: 12px; color: var(--text); margin-top: 5px; }
.pnt-hint { font-size: 11px; color: var(--muted); margin-top: 4px; font-style: italic; }
.pnt-empty { padding: 26px 16px; text-align: center; color: var(--muted); }

/* modal bits */
.pnt-lbl { display: block; font-size: 11px; font-weight: 700; color: var(--muted); margin: 12px 0 5px; }
.pnt-subhead { font-size: 13px; color: var(--text); display: flex; align-items: center; gap: 8px; }
.pnt-chips { display: flex; flex-wrap: wrap; gap: 6px; }
.pnt-chip { border: 1px solid var(--border); background: var(--surface); color: var(--text); border-radius: 16px; padding: 7px 12px; font-size: 13px; cursor: pointer; }
.pnt-chip.on { background: var(--verti-navy-50); border-color: var(--verti-navy); color: var(--verti-navy); font-weight: 600; }
.pnt-newcat { display: flex; gap: 8px; margin-bottom: 10px; }
.pnt-newcat input { flex: 1; padding: 9px 11px; border: 1px solid var(--border); border-radius: var(--radius-sm); }
.pnt-muted { font-size: 12px; color: var(--muted); margin: 0 0 10px; }
.pnt-catlist { display: flex; flex-direction: column; gap: 6px; }
.pnt-catrow { display: flex; align-items: center; gap: 8px; border: 1px solid var(--border); border-radius: var(--radius-sm); padding: 6px 8px; }
.pnt-catrow.off { background: #F7F7F9; }
.pnt-catrow .cat-id { width: 22px; text-align: center; font-size: 11px; color: var(--muted); background: var(--bg); border-radius: 8px; padding: 2px 0; }
.pnt-catrow .cat-input { flex: 1; min-width: 0; padding: 7px 9px; border: 1px solid var(--border); border-radius: var(--radius-sm); }
.pnt-catrow.off .cat-input { border-color: transparent; background: transparent; color: var(--muted); text-decoration: line-through; }
.pnt-retiredtag { font-size: 10px; color: var(--danger); }

@media (min-width: 720px) {
  .pnt-kpis { max-width: 520px; }
}

/* Sales-activity bridge — read-only cards in puantaj timeline. */
.pnt-src.sales { background: #FEE8DC; color: var(--tke-orange); }
.pnt-link { text-decoration: none; color: inherit; }
.pnt-link .pnt-card::after { content: '↗'; position: absolute; top: 6px; right: 8px; color: var(--tke-orange); font-size: 12px; opacity: .6; }
.pnt-link .pnt-card { position: relative; }
.pnt-untimed-head { padding: 14px 6px 6px 60px; font-size: 11px; font-weight: 700; letter-spacing: .04em; color: var(--muted); }

/* Sales puantaj accordion (Sprint 183) — embedded day timeline on sales.html */
.sp-acc { padding: 0; overflow: hidden; }
.sp-acc-head { width: 100%; display: flex; align-items: center; gap: 10px; padding: 14px 18px;
  background: #F8FAFC; border: none; cursor: pointer; text-align: left; font: inherit; }
.sp-acc-head:hover { background: #F1F5F9; }
.sp-chev { color: var(--tke-navy, #1D3360); width: 14px; }
.sp-acc-title { font-weight: 700; color: var(--tke-navy, #1D3360); font-size: 15px; }
.sp-acc-sub { color: var(--muted, #6B7280); font-size: 12px; }
.sp-acc-body { padding: 14px 18px 18px; border-top: 1px solid var(--border, #E5E7EB); }
.sp-loading, .sp-empty { padding: 18px; color: var(--muted, #6B7280); }
.sp-controls { display: flex; flex-wrap: wrap; gap: 14px; align-items: center; margin-bottom: 8px; }
.sp-datenav { display: flex; align-items: center; gap: 6px; }
.sp-datenav input[type=date] { padding: 6px 8px; border: 1px solid var(--border, #D1D5DB); border-radius: 7px; }
.sp-userwrap { display: flex; align-items: center; gap: 8px; }
.sp-userwrap select { padding: 6px 8px; border: 1px solid var(--border, #D1D5DB); border-radius: 7px; }
.sp-lbl { font-size: 12px; color: var(--muted, #6B7280); }
.sp-head { font-size: 13px; color: var(--muted, #6B7280); margin: 6px 0 10px; }
.sp-bar { margin-bottom: 12px; }
.sp-composer { display: flex; flex-wrap: wrap; gap: 8px; align-items: center;
  padding: 10px; background: #FAFBFD; border: 1px solid var(--border, #E5E7EB); border-radius: 10px; }
.sp-composer select, .sp-composer input { padding: 7px 9px; border: 1px solid var(--border, #D1D5DB);
  border-radius: 7px; font-size: 13px; }
.sp-c-dept { color: var(--tke-orange, #E85A2A); font-weight: 700; }
.sp-c-kind { min-width: 180px; }
.sp-c-note { flex: 1; min-width: 140px; }
.sp-c-time { width: 92px; }
.sp-dash { color: var(--muted, #6B7280); }
.sp-c-hint { font-size: 12px; color: var(--tke-orange, #E85A2A); margin: 6px 2px 0; min-height: 14px; }
.sp-flow { margin-top: 8px; }
.sp-lunch { background: #FFFBF0; }

/* Sprint 183: clickable puantaj bar — tap a free gap to fill the composer. */
.sp-bar .gpsr-timeline.sp-bar-click { cursor: pointer; }
.sp-bar .gpsr-timeline.sp-bar-click:hover { outline: 2px solid #2563EB55; outline-offset: 1px; }

/* Sprint 183: per-row edit/delete on the puantaj flow cards. */
.sp-actions { display: inline-flex; gap: 4px; margin-left: 8px; }
.sp-icon { border: none; background: transparent; cursor: pointer; padding: 3px 5px; border-radius: 5px;
  color: #9CA3AF; font-size: 12px; line-height: 1; }
.sp-icon:hover { background: #F1F5F9; color: #374151; }
.sp-icon.sp-del:hover { background: #FEE2E2; color: #B91C1C; }

/* Sprint 210: Daily Work Log MATRIX — the day's records as one grid (replaces
   the vertical flow cards). GPS gap rows carry an inline category + note + log
   ✓ so a location segment is logged in one tap, no time typing. */
.wl-matrix { width: 100%; border-collapse: collapse; font-size: 13px; }
.wl-matrix thead tr { background: var(--brand-gradient); }
.wl-matrix thead th { color: #fff; font-weight: 800; font-size: 11px; text-align: left;
  padding: 8px 10px; white-space: nowrap; letter-spacing: .02em; }
.wl-matrix thead th:first-child { border-radius: 6px 0 0 6px; }
.wl-matrix thead th:last-child  { border-radius: 0 6px 6px 0; text-align: right; }
.wl-matrix tbody td { border-bottom: 1px solid var(--border); padding: 7px 10px;
  vertical-align: middle; color: #374151; }
.wl-matrix tbody tr:hover { background: #F8FAFC; }
.wl-matrix tbody tr.wl-hl { background: #EBF4FF; box-shadow: inset 3px 0 0 #2563EB; }
/* Sprint 211: highlight the folded composer ("+ Add" block) when the bar
   click landed on a free gap. Fades after a couple of seconds. */
.wl-addrow { border: 1px solid var(--border); border-radius: var(--radius-sm);
  padding: 8px 10px; margin-top: 8px; background: var(--surface); transition: background .3s, box-shadow .3s; }
.wl-addrow.wl-hl { background: #EBF4FF; box-shadow: inset 3px 0 0 #2563EB; }
.wl-addrow .sp-composer { margin: 0; }
.wl-place { border: 1px solid var(--tke-orange); background: #FFF1E7; color: var(--tke-orange);
  border-radius: 6px; padding: 4px 9px; font-weight: 700; font-size: 12px; cursor: pointer; white-space: nowrap; }
.wl-place:hover { background: var(--tke-orange); color: #fff; }
/* While "place mode" is armed, the bar shows a crosshair so the user knows
   the next click drops the activity. */
.wl-placing .sp-bar .gpsr-timeline { cursor: crosshair !important; outline: 2px dashed var(--tke-orange); outline-offset: 1px; }
.wl-time { font-variant-numeric: tabular-nums; font-weight: 700; color: var(--verti-navy); white-space: nowrap; }
.wl-time small { display: block; font-weight: 400; color: var(--muted); }
.wl-kind { font-weight: 600; color: var(--verti-navy); }
.wl-dur { color: var(--muted); font-variant-numeric: tabular-nums; white-space: nowrap; }
.wl-detail { color: var(--muted); }
.wl-src { font-size: 10px; font-weight: 800; padding: 2px 8px; border-radius: 9px; white-space: nowrap; }
.wl-src.gps { background: var(--verti-navy-50); color: var(--verti-navy); }
.wl-src.sales { background: #FEE8DC; color: var(--tke-orange); }
.wl-src.manual { background: #EDEFF3; color: var(--muted); }
.wl-src.lunch { background: #FFF7E0; color: #B45309; }
.wl-matrix tbody tr.wl-gap td { background: #F7FAFF; }
.wl-matrix tbody tr.wl-gap:hover td { background: #EEF5FF; }
.wl-logcell { display: flex; gap: 6px; align-items: center; }
.wl-logcell select, .wl-logcell input { padding: 5px 7px; border: 1px solid var(--border); border-radius: 6px; font-size: 12px; }
.wl-logcell select { flex: 0 0 auto; max-width: 160px; }
.wl-logcell input { flex: 1; min-width: 80px; }
.wl-log { border: 0; background: #047857; color: #fff; border-radius: 6px; padding: 5px 10px;
  cursor: pointer; font-weight: 800; font-size: 12px; white-space: nowrap; }
.wl-log:hover { filter: brightness(1.08); }
.wl-log[disabled] { opacity: .5; cursor: default; }
.wl-act { white-space: nowrap; text-align: right; }
@media (max-width: 720px) {
  /* On a phone the grid collapses to the source + kind + note essentials. */
  .wl-matrix .wl-hide-sm { display: none; }
  .wl-matrix { font-size: 12px; }
}

/* ============================================================
   Sprint 232 — Mobile VERTI/TKE brand layer + department-aware home.
   Most of the brand signal lives here (shared) so every mobile-*.html
   page inherits the gradient identity; mobile.html adds the wordmark,
   the department pill and the per-department action sections.
   ============================================================ */
/* Topbar: brand-gradient strip under the bar (mirrors the web .topbar::after). */
.mobile-topbar { position: relative; gap: 10px; }
.mobile-topbar::after {
  content: ''; position: absolute; left: 0; right: 0; bottom: 0;
  height: 3px; border-radius: 2px; background: var(--brand-gradient);
}
/* V + VERTI AIM lockup, sitting right of the V mark. */
.mobile-wordmark { display: flex; flex-direction: column; line-height: 1.04; flex: 1; min-width: 0; }
.mobile-wordmark b { font-size: 16px; font-weight: 900; color: var(--verti-navy); letter-spacing: .3px; }
.mobile-wordmark small { font-size: 7px; font-weight: 700; color: var(--muted); letter-spacing: 0;
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
/* Sprint 459: dead .mobile-dept-pill rules removed (no matching markup). */

/* Sprint 232: the department wrappers must keep their cards full-width.
   A <button> is a form control, so with width:auto it shrinks to fit its
   content instead of filling — when the buttons stopped being direct flex
   items of .mobile-actions (now nested in these wrappers) the cards
   staircased to content width. Making each wrapper a flex column restores
   align-items:stretch (full-width cards) + the same 14px gap. */
#mServiceSection, #mSalesSection { display: flex; flex-direction: column; gap: 14px; }

/* Action cards: left gradient accent + the icon becomes a gradient chip. */
.mobile-action-btn { overflow: hidden; }
.mobile-action-btn::before {
  content: ''; position: absolute; left: 0; top: 0; bottom: 0; width: 5px;
  background: var(--brand-gradient);
}
.mobile-action-icon {
  width: 44px; height: 44px; border-radius: 12px; display: grid; place-items: center;
  font-size: 21px; color: #fff !important; background: var(--brand-gradient);
  box-shadow: 0 3px 8px rgba(122, 46, 107, .25); flex: 0 0 auto;
}
/* Sprint 459: dead .mobile-actions-head rules removed (the assigned-list
   heading renders via #mAssignedHead + .mobile-atabs now). */

/* Form pages: gradient page-title underline + gradient save + gradient FAB. */
.mobile-page-h1 h1 { position: relative; padding-bottom: 9px; }
.mobile-page-h1 h1::after {
  content: ''; position: absolute; left: 0; bottom: 0; width: 56px; height: 3px;
  border-radius: 2px; background: var(--brand-gradient);
}
.mobile-save {
  width: 100%; border: none; border-radius: 12px; padding: 15px; font-size: 16px;
  font-weight: 800; color: #fff; background: var(--brand-gradient); cursor: pointer;
  box-shadow: 0 4px 12px rgba(122, 46, 107, .3);
}
.mobile-save:active { transform: scale(0.99); }

/* Department empty-state note (modules with no mobile actions yet). */
.mobile-dept-empty {
  margin: 22px 4px; padding: 16px; border: 1px dashed var(--border); border-radius: 14px;
  background: #fff; color: var(--muted); font-size: 13px; text-align: center;
}

/* Sprint 232: bring the mobile sub-pages' primary surfaces on-brand.
   The page-header swatch becomes a vertical brand gradient (matching the
   title's gradient underline); the collapsible "add" toggle on the list
   pages (service / part) becomes a gradient pill. Replaces the old
   per-page semantic inline colours (green / navy / red / purple). */
.mobile-page-swatch {
  background: linear-gradient(180deg, var(--verti-navy), var(--tke-purple), var(--tke-orange));
}
/* Sprint 459: dead .mobile-add-toggle rules removed (no matching <details>
   markup anywhere). */

/* ===== Sprint 233 — GPS record-location mismatch flag ===== */

/* Pre-commit warning modal (field-entry guard). */
.gps-warn-overlay {
  position: fixed; inset: 0; z-index: 4000; background: rgba(15, 23, 42, .55);
  display: flex; align-items: flex-start; justify-content: center; padding: 80px 18px;
}
.gps-warn {
  width: 100%; max-width: 380px; background: #fff; border-radius: 18px; overflow: hidden;
  box-shadow: 0 18px 50px rgba(0, 0, 0, .4);
}
.gps-warn-top {
  background: linear-gradient(135deg, #DC2626, #991B1B); color: #fff; padding: 16px 18px;
  display: flex; align-items: center; gap: 12px;
}
.gps-warn-top i { font-size: 24px; }
.gps-warn-top b { font: 800 16px/1.2 inherit; }
.gps-warn-body { padding: 16px 18px; }
.gps-warn-body p { margin: 0 0 12px; font: 600 13.5px/1.5 inherit; color: #374151; }
.gps-warn-dist {
  display: flex; align-items: center; gap: 8px; background: #FEF2F2;
  border: 1px solid #FCA5A5; border-radius: 10px; padding: 10px 12px; margin-bottom: 14px;
  font: 800 14px/1 inherit; color: #991B1B;
}
.gps-warn-btns { display: flex; gap: 10px; }
.gps-warn-btns button { flex: 1; border: 0; border-radius: 11px; padding: 13px; font: 800 14px/1 inherit; cursor: pointer; }
.gps-warn-go { background: var(--brand-gradient); color: #fff; box-shadow: 0 4px 12px rgba(122, 46, 107, .3); }
.gps-warn-cancel { background: #F3F4F6; color: #374151; border: 1px solid var(--border); }

/* List / detail badge — red mismatch, green verified. */
.gps-badge {
  display: inline-flex; align-items: center; gap: 5px; border-radius: 999px;
  padding: 2px 9px; font: 800 11px/1.6 inherit; white-space: nowrap;
}
.gps-badge-bad { background: #FEE2E2; color: #991B1B; border: 1px solid #FCA5A5; }
.gps-badge-ok  { background: #D1FAE5; color: #065F46; border: 1px solid #6EE7B7; }
/* Sprint 268 — open/close phase tag prefixing the badge (e.g. "Açılış ✓"). */
.gps-badge-ph { font-weight: 800; opacity: .8; letter-spacing: .2px; }

/* Personal-dashboard "your flagged records" card (red left accent). */
.gps-flag-card { position: relative; overflow: hidden; }
.gps-flag-card::before {
  content: ''; position: absolute; left: 0; top: 0; bottom: 0; width: 5px;
  background: linear-gradient(180deg, #DC2626, #991B1B);
}
.gps-flag-row {
  display: flex; justify-content: space-between; align-items: center; gap: 10px;
  padding: 8px 0; border-top: 1px solid #f0f2f5; font-size: 12.5px; color: #374151;
}
.gps-flag-row:first-of-type { border-top: 0; }

/* ===== Sprint 234 — technician mobile: full-bleed gradient topbar, safe-area,
   A56-fit compact cards, header home button + topbar minimize button ===== */

/* The topbar spans edge-to-edge and sits BELOW the phone status bar
   (safe-area). Propagates to every mobile page via the shared .mobile-topbar. */
.mobile-shell { padding-top: 0; }
.mobile-topbar {
  margin: 0 -8px;                                    /* Sprint 278: break out of the shell's 8px side padding */
  /* Sprint 273: hug the phone status bar — keep the safe-area inset (so the
     wordmark never collides with the clock/battery) but drop the extra 12px
     band that left a big empty gap above VERTI AIM. */
  padding: calc(env(safe-area-inset-top, 0px) + 4px) 8px 10px;
  background: var(--brand-gradient);
  border-bottom: none;
  box-shadow: 0 3px 12px rgba(29, 51, 96, .22);
}
.mobile-topbar::after { display: none; }             /* whole bar is gradient now — drop the strip */
/* Invert the topbar elements so they read on the gradient. (Sprint 459: the
   dead brand-mark SVG recolors and .mobile-dept-pill overrides are gone.) */
.mobile-wordmark b     { color: #fff; }
.mobile-wordmark small { color: rgba(255, 255, 255, .85); }
/* Sprint 278/280: the user avatar matches the home button EXACTLY (same 32px
   outer diameter — both box-sizing:border-box; Sprint 280 +15% from 28→32px). */
.mobile-topbar .mobile-user-chip .avatar { background: #fff; color: var(--verti-navy); width: 32px; height: 32px; font-size: 15px; }

/* Sprint 459: the injected .mobile-homebtn is gone (it was inject-then-removed
   waste on app-shell pages). The wordmark home affordance stays (Sprint 278):
   the wordmark + the empty header space to its right trigger home. */
.mobile-wordmark { cursor: pointer; }

/* Page-head row: section title + a right-aligned HOME button (on every page). */
.mobile-headrow { display: flex; align-items: center; gap: 10px; margin: 12px 0 9px; }
.mobile-headrow-title {
  flex: 1; font: 800 16px/1.1 inherit; color: var(--verti-navy);
  position: relative; padding-left: 11px;
}
.mobile-headrow-title::before {
  content: ''; position: absolute; left: 0; top: 50%; transform: translateY(-50%);
  width: 4px; height: 16px; border-radius: 2px; background: var(--brand-gradient);
}

/* Sprint 459: the Sprint-254 .svc-bubbles / .svc-bub / .svc-ring stat-bubble
   family was dead CSS — its renderer (service-stats.js) and its last mount
   point (#mSvcStats) are both gone. Removed. */

/* ===== Sprint 273 — mobile home: grouped white cards + inline KPIs + tabs +
   one VERTI+TKE-branded 1-year fault badge (operator layout request). =====
   (Sprint 459: the .mobile-action-row white-card variant was removed with the
   dead #mActionRow markup.) */
/* the whole service section sits in ONE white card. */
.mobile-svccard {
  background: #fff; border: 1px solid var(--border); border-radius: 15px;
  box-shadow: 0 4px 14px rgba(20, 30, 60, .05); padding: 2px 12px 12px; margin-bottom: 14px;
  min-width: 0;   /* Sprint 280: flex-column child — don't grow to content min-content (let the KPI grid clip/fit) */
}
/* 3) heading row inside the card: title at natural width, tabs at the end. */
.mobile-svccard > .mobile-headrow { margin: 9px 0 4px; gap: 8px; }
.mobile-svccard > .mobile-headrow .mobile-headrow-title { flex: 0 1 auto; font-size: 15px; }
/* 4) "Atanmamış Servis Başlat" — bold, title-ish font, one line (Sprint 274). */
.mobile-action-btn.mobile-start { padding: 13px 12px; margin: 4px 0 0; }
.mobile-action-btn.mobile-start .mobile-action-label {
  /* Sprint 280: was `font: 800 13px/1.1 inherit` — an INVALID shorthand (a
     global keyword can't be the font-family), so the 13px was dropped and the
     label inherited the button's 16px (rendering BIGGER than the "Servis İşləri"
     heading). Use valid longhands so the intended 13px actually applies. */
  flex: 1; font-weight: 800; font-size: 13px; line-height: 1.1; color: var(--verti-navy); text-align: center; white-space: nowrap;
}
/* Sprint 274: the manual-start + QR actions match the assigned-row buttons
   EXACTLY (same function → same look): a brand-gradient ▶ play + an orange ▦ QR. */
.mobile-action-btn.mobile-start .mobile-action-form,
.mobile-action-btn.mobile-start .mobile-action-scan {
  width: 38px; height: 38px; border-radius: 10px; padding: 0; margin: 0;
  display: grid; place-items: center; font-size: 15px; border: 0;
}
.mobile-action-btn.mobile-start .mobile-action-form { background: var(--brand-gradient); color: #fff; }
.mobile-action-btn.mobile-start .mobile-action-scan { background: #FFF4EE; color: var(--tke-orange); }
.mobile-action-btn.mobile-start .mobile-action-form + .mobile-action-scan { margin-left: 7px; }
/* 5) Sprint 280: the two tabs are VERTI-navy BUTTONS (active = filled navy,
   inactive = navy outline) with an orange total-count badge — replaces the old
   underline tabs. */
.mobile-atabs { display: flex; gap: 8px; margin: 6px 0 2px; }  /* Sprint 278 gap kept */
.mobile-atab {
  position: relative; flex: 1; cursor: pointer; border-radius: 11px;
  padding: 10px 8px; font: 800 12.5px/1.1 inherit;
  background: #fff; color: var(--verti-navy); border: 1.6px solid var(--verti-navy);
}
.mobile-atab.on { background: var(--verti-navy); color: #fff; }
.mobile-atab:active { transform: scale(0.97); }
.mobile-atab-badge {
  position: absolute; top: -7px; right: -7px; background: var(--tke-orange);
  color: #fff; font-size: 10px; font-weight: 900; border-radius: 10px;
  padding: 0 6px; border: 2px solid #F2F5FA; line-height: 1.5;
}
/* 6) ONE fault badge — last-year count, VERTI+TKE brand gradient. */
.maa-fbs { display: inline-flex; }
.maa-fb1 {
  display: inline-flex; align-items: center; gap: 3px; background: #A6394F;   /* Sprint 304: Arıza colour */
  color: #fff; font-size: 9.5px; font-weight: 800; padding: 2px 8px; border-radius: 7px; cursor: pointer;
}
.maa-fb1 i { font-size: 9px; }
.maa-fb1 em { font-style: normal; opacity: .9; margin-left: 1px; }

/* Sprint 459: dead .mobile-headbtn rules removed (superseded by the msh-nav /
   msh-badges header redesign). */

/* Compact action cards — a SERVICE home (6 actions + map) fits an A56 with no
   scroll. */
.mobile-actions { gap: 9px; padding-top: 2px; }
/* Sprint 239: bigger tap targets for gloved / dirty field hands. */
.mobile-action-btn { min-height: 70px; padding: 11px 14px 11px 17px; gap: 14px; border-radius: 15px; }
.mobile-action-icon { width: 54px; height: 54px; border-radius: 14px; font-size: 26px; }
.mobile-action-scan,
.mobile-action-form { font-size: 22px; padding: 11px 13px; border-radius: 10px; }
/* Sprint 234 tweak: tighten the per-department wrapper gap to match the
   compact cards. */
#mServiceSection, #mSalesSection { gap: 9px; }
/* Sprint 234 fix: the per-department wrappers set display:flex, which defeats
   the [hidden] attribute (a service user kept seeing the gated Sales section).
   Restore [hidden] = display:none for them. */
#mServiceSection[hidden], #mSalesSection[hidden] { display: none !important; }

/* Sprint 459: dead headrow-button era removed — .mobile-mapbtn (S234),
   .mobile-puantajbtn (S265), .mobile-otherbtn + its badge (S259), the
   .mobile-action-row / .mact quick-icon strip (S265) and the compact
   .svc-bubbles overrides. All superseded by the Sprint-303 bottom nav +
   Diğer sheet; none had matching markup anymore. */

/* Sprint 259: assigned-maintenance list (under Servis Ekle). Left accent
   encodes the due state — red overdue, green TODAY (light-green card so it
   stands out), navy future. Each row carries manual (▶) + QR (▦) start. */
.mobile-assigned { margin: 2px 0 4px; }
.mobile-assigned[hidden] { display: none; }
.mobile-assigned-row {
  background: #fff; border: 1px solid var(--border); border-radius: 12px;
  padding: 9px 11px; display: flex; align-items: center; gap: 10px;
  margin-bottom: 8px; position: relative; overflow: hidden;
  box-shadow: 0 1px 2px rgba(0, 0, 0, .04);
}
.mobile-assigned-row::before {
  content: ''; position: absolute; left: 0; top: 0; bottom: 0; width: 4px;
  background: var(--verti-navy);
}
.mobile-assigned-row.over::before  { background: #C0392B; }
.mobile-assigned-row.today::before { background: #16A34A; }
.mobile-assigned-row.today { background: #F0FBF4; border-color: #BFE6CD; }
.maa-tx { flex: 1; min-width: 0; }
.maa-tx b {
  display: block; font-size: 13px; font-weight: 800; color: var(--verti-navy);
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}
.maa-tx small { font-size: 11px; font-weight: 700; color: var(--muted); }
.mobile-assigned-row.over  .maa-tx small { color: #C0392B; }
.mobile-assigned-row.today .maa-tx small { color: #15803D; font-weight: 800; }
.maa-acts { display: flex; gap: 7px; flex: 0 0 auto; }
.maa-st {
  width: 38px; height: 38px; border-radius: 10px; border: 0; cursor: pointer;
  display: grid; place-items: center; font-size: 15px;
}
.maa-st.maa-man { background: var(--brand-gradient); color: #fff; }
.maa-st.maa-qr  { background: #FFF4EE; color: var(--tke-orange); }
.maa-st:active { transform: scale(0.94); }
.maa-st.disabled-by-active { opacity: 0.4; }

.mobile-assigned-empty { font-size: 12px; color: var(--muted); padding: 4px 2px 10px; }

/* Sprint 264: each assigned row now stacks title + date + the fault badges,
   so align the start/✓ actions to the top of that taller block. */
.mobile-assigned-row { align-items: flex-start; }
.maa-acts { padding-top: 1px; }
/* Completed maintenances: grey accent + a green ✓ instead of start buttons. */
.mobile-assigned-row.done::before { background: #94A3B8; }
.mobile-assigned-row.done .maa-tx small { color: #64748B; }
.maa-doneic {
  width: 38px; height: 38px; border-radius: 10px; display: grid; place-items: center;
  background: #E8F5EC; color: #15803D; font-size: 15px;
}

/* Sprint 264: VERTI-branded per-unit fault badges inside each assigned row
   (last 3 months / last year / all-time). Recency drives the colour. */
.maa-fbs { display: flex; gap: 5px; margin-top: 6px; flex-wrap: wrap; }
.maa-fb {
  display: inline-flex; align-items: center; gap: 4px; cursor: pointer;
  user-select: none; font-size: 10px; font-weight: 800; line-height: 1.3;
  padding: 3px 8px; border-radius: 999px;
}
.maa-fb i { font-size: 9px; }
.maa-fb em {
  font-style: normal; font-weight: 700; font-size: 8px; letter-spacing: .4px;
  text-transform: uppercase; opacity: .9;
}
.maa-fb.fb3 { background: #C0392B; color: #fff; box-shadow: 0 1px 3px rgba(192, 57, 43, .3); }
.maa-fb.fb1 { background: #FCE4E4; color: #B91C1C; }
.maa-fb.fba { background: #FEF3C7; color: #92400E; }
.maa-fb:active { transform: scale(.95); }

/* ============================================================================
   Sprint 269 — mobile HOME layout tightening (reduce empty space, gain area).
   Everything is scoped to .mobile-actions so other mobile pages are untouched.
   Hard rule (operator): nothing crushes / overflows — cells flex + clamp,
   titles + dates ellipsize, fault badges never clip.
   ============================================================================ */
/* (1,3,4) one tight ~8px vertical rhythm — the flex `gap` was being doubled by
   each child's own margin; zero those so spacing is the gap alone. */
.mobile-actions { gap: 8px; padding-top: 9px; }
.mobile-actions > .mp-tlcard,
.mobile-actions > .mobile-headrow { margin: 0; }
/* (2) puantaj card — tighter top/bottom inner padding. */
.mobile-actions .mp-tlcard { padding: 7px 9px; }
/* (1) shorter "Servis Ekle" button (78px → ~52px), icon/label intact. */
.mobile-actions .mobile-action-btn { padding: 12px 14px; min-height: 0; font-size: 16px; }
.mobile-actions .mobile-action-icon { font-size: 23px; width: 28px; }
/* (Sprint 459: the .svc-bubbles.compact KPI-slot overrides and the
   .mobile-assigned-head / .mobile-afilters filter-icon era were dead — the
   heading renders via #mAssignedHead + .mobile-atabs since Sprint 273/303.) */
/* gap between the Add-Service button and the assigned list. */
.mobile-actions .mobile-assigned { margin: 4px 0 0; }

/* (7) assigned row — title on line 1 (ellipsis); date + fault badges share
   line 2, badges right-aligned at the row's height, no clip. */
.mobile-actions .mobile-assigned-row { align-items: center; }
.mobile-actions .maa-acts { padding-top: 0; }
.maa-tx { display: flex; flex-direction: column; gap: 3px; }
.maa-l2 { display: flex; align-items: center; gap: 6px; min-width: 0; }
.maa-l2 small {
  flex: 1 1 auto; min-width: 40px;
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}
.maa-l2 .maa-fbs { margin: 0 0 0 auto; flex: 0 0 auto; flex-wrap: nowrap; gap: 3px; }
/* compact the inline fault badges so the full date + all three fit one line
   without crushing the date (recency colour stays; caption + bolt shrink). */
.maa-l2 .maa-fb { font-size: 9px; padding: 2px 5px; gap: 2px; }
.maa-l2 .maa-fb i { font-size: 7.5px; }
.maa-l2 .maa-fb em { font-size: 7px; }

/* Sprint 264: fault-history sheet rows (reuses the shared bottom-sheet). */
.mfh-row { padding: 8px 4px; border-bottom: 1px solid #EEF1F6; font-size: 12px; color: var(--text); }
.mfh-row:last-child { border-bottom: 0; }
.mfh-meta { color: var(--muted); font-size: 11px; margin-top: 2px; }
.mfh-loading, .mfh-empty { color: var(--muted); font-size: 12px; text-align: center; padding: 14px; }
.mfh-err { color: #C0392B; font-size: 12px; padding: 8px; }

/* Sprint 264: the Add-Service page is now a single direct form card. */
.mobile-form-card {
  background: #fff; border: 1px solid var(--border); border-radius: 12px;
  padding: 14px; margin-bottom: 14px;
}

/* ===== Sprint 265: shared puantaj timeline (puantaj-timeline.js) =====
   ONE design used everywhere (mobile home, mobile puantaj, sales, desktop).
   Filled bands + striped GAP bands (unfilled time → tap to fill). */
/* Sprint 272: compact card — title + "?" help on the left, "doldur" on the right;
   the bar sits flush under the header (no gap). */
.pt-host { position: relative; }
/* Sprint 380: title + "?" + ✓/⚠ badge all cluster on the LEFT (the live clock
   chip owns the strip's right end now). */
.pt-head { display: flex; justify-content: flex-start; flex-wrap: wrap; align-items: center; gap: 5px 8px; margin-bottom: 3px; }
.pt-htitle { display: flex; align-items: center; gap: 6px; min-width: 0; }
.pt-lab  { color: var(--verti-navy); font-weight: 800; font-size: 11px; }
.pt-help {
  flex: 0 0 auto; width: 16px; height: 16px; border-radius: 50%; border: 1.5px solid #B9C2D4;
  background: #fff; color: #7B859B; font-size: 10px; font-weight: 900; line-height: 13px;
  text-align: center; cursor: pointer; padding: 0;
}
.pt-help:hover { border-color: var(--verti-navy); color: var(--verti-navy); }
.pt-miss { color: #B91C1C; font-weight: 800; font-size: 10px; white-space: nowrap; }
.pt-ok   { color: #15803D; font-weight: 800; font-size: 10px; white-space: nowrap; }
.pt-bar  {
  position: relative; height: 38px; background: #F4F0EA; border-radius: 6px;
  overflow: hidden; border: 1px solid #E1E6EF;                /* Sprint 278: +25% (30→38px) */
  display: flex; align-items: stretch;                        /* Sprint 439: segmented axis */
}
/* Sprint 439 — one .pt-seg per axis segment; a ⫽ break column between far
   off-shift blocks. The pad / off-shift zone is tinted; the dashed lines mark
   the real mesai edges. */
.pt-seg  { position: relative; min-width: 0; height: 100%; }
.pt-brk  { display: flex; align-items: center; justify-content: center; color: #8A94A8;
  font-weight: 900; font-size: 13px; letter-spacing: -2px; user-select: none;
  background: #FBFAF7; border-left: 1px solid #E1E6EF; border-right: 1px solid #E1E6EF; }
.pt-padzone { position: absolute; top: 0; bottom: 0; background: #EAEDF4; z-index: 0; }
.pt-mesln { position: absolute; top: -2px; bottom: -2px; width: 0; z-index: 1;
  border-left: 1.5px dashed rgba(29,51,96,.42); }
.pt-offnote { margin-top: 2px; text-align: right; font-size: 10px; font-weight: 700;
  color: #7B859B; font-variant-numeric: tabular-nums; }
.pt-band { position: absolute; top: 0; bottom: 0; display: flex; align-items: center; justify-content: center; z-index: 2; }
/* Sprint 272: "No Signal" gap — GREY stripes (was orange). */
.pt-gap  {
  position: absolute; top: 0; bottom: 0; cursor: pointer; z-index: 2;
  display: flex; align-items: center; justify-content: center;
  background: repeating-linear-gradient(45deg, #C3CAD6 0 5px, #AEB6C4 5px 10px);
}
/* Sprint 272: the "+" fill marker INSIDE a fill segment (idle red / gap grey). */
.pt-plus {
  width: 15px; height: 15px; border-radius: 50%; font-size: 12px; font-weight: 900;
  line-height: 13px; text-align: center; flex: 0 0 auto; pointer-events: none;
}
.pt-plus-idle { background: rgba(255,255,255,.92); color: #DC2626; border: 1px solid #fff; }
.pt-plus-gap  { background: var(--verti-navy); color: #fff; }
/* Sprint 272: the NOW marker line at the current time (today only). */
.pt-now { position: absolute; top: -2px; bottom: -2px; width: 2.5px; background: var(--verti-navy); z-index: 6; }
/* Sprint 380: a pulsing dot (colour = current activity, set inline) rides the NOW line. */
.pt-now-mk { position: absolute; top: -6px; left: 50%; transform: translateX(-50%); width: 11px; height: 11px;
  border-radius: 50%; border: 1.5px solid #fff; background: var(--verti-navy);
  box-shadow: 0 0 0 2px rgba(29,51,96,.16), 0 1px 3px rgba(0,0,0,.25); animation: pt-beat 1.7s ease-in-out infinite; }
@keyframes pt-beat { 0%,100% { box-shadow: 0 0 0 2px rgba(29,51,96,.16), 0 1px 3px rgba(0,0,0,.25); }
  50% { box-shadow: 0 0 0 5px rgba(29,51,96,.03), 0 1px 3px rgba(0,0,0,.25); } }
/* Sprint 382: axis is a SINGLE row — each segment/op START gets a tick; a label
   that would collide is dropped. The NOW wall-clock sits at the right end in its
   OWN format (navy/bold), replacing the old top-right live-clock time. */
.pt-axis { position: relative; height: 16px; margin-top: 4px; display: flex; align-items: stretch; } /* Sprint 439: mirrors the bar's segments */
.pt-axseg { position: relative; min-width: 0; height: 100%; }
.pt-axbrk { height: 100%; }
.pt-axtk { position: absolute; top: 0; width: 1px; height: 4px; background: #C7CEDA; transform: translateX(-50%); }
.pt-axlb { position: absolute; top: 6px; transform: translateX(-50%); font-size: 8.5px; font-weight: 700; color: var(--muted);
  white-space: nowrap; font-variant-numeric: tabular-nums; }
.pt-axnow { color: var(--verti-navy); font-weight: 800; transform: translateX(-100%); }

/* Sprint 265/272: the work bar + the connectivity strip are ONE merged component
   (the connectivity strip — with a leading signal glyph — sits flush under it). */
/* Sprint 380: overflow VISIBLE so the live clock chip sitting ABOVE the strip is
   not clipped; the inner work bar + GPS strip re-round their own outer corners. */
.pt-bars { position: relative; border: 1px solid #E1E6EF; border-radius: 7px; overflow: visible; }
.pt-bars .pt-bar { border: 0; border-radius: 6px 6px 0 0; }
.pt-bars .pt-gpsrow { border-radius: 0 0 6px 6px; overflow: hidden; }
/* Sprint 278: the work-bar state blocks are now SEPARATED rounded blocks like
   the GPS connectivity strip — a 2px vertical inset, rounded corners, and a 2px
   gap (a right border in the bar's own background colour) between consecutive
   blocks. Applies to the state bands AND the No-Signal gap block. */
.pt-bars .pt-band, .pt-bars .pt-gap {
  top: 2px; bottom: 2px; border-radius: 3px;
  border-right: 2px solid #F4F0EA; box-sizing: border-box;
}
.pt-fill { cursor: pointer; }                       /* idle band = a fill target */
/* Sprint 283: the GPS connectivity strip is FULL-WIDTH (the leading signal glyph
   moved onto the first hour-axis label — see .pt-ax0 below). */
/* Sprint 304: thin, elegant GPS strip glued directly under the work bar
   (operator request; was a chunky 20px). Same width as the bar (both fill
   .pt-bars), so the two align exactly. */
.pt-gpsrow { display: flex; align-items: center; background: #fff; border-top: 1px solid #EEF1F6; }
.pt-conn { position: relative; flex: 1; height: 4px; background: #E7ECF3;
  display: flex; align-items: stretch; }                        /* Sprint 439: mirrors the bar's segments */
.pt-cseg { position: relative; min-width: 0; height: 100%; }
.pt-cbrk { height: 100%; background: #fff; }
/* Sprint 283: the signal-bars glyph rides the first axis label ("09"), to its
   right with its BOTTOM level with the digits (shrunk; baseline-aligned). */
.pt-axis .pt-ax0 { transform: none; color: var(--verti-navy); font-weight: 800; white-space: nowrap; }
.pt-axis .pt-axsig { margin-left: 3px; }
.pt-axis .pt-axsig svg { width: 9px !important; height: 7px !important; fill: var(--verti-navy); vertical-align: baseline; }
.pt-cband { position: absolute; top: 0; bottom: 0; border-radius: 0; }

/* ===== Sprint 380: live clock chip (right end) + hover tooltip ===== */
.pt-nowclock {
  position: absolute; top: -21px; right: -1px; z-index: 9;
  display: inline-flex; align-items: center; gap: 5px; white-space: nowrap;
  padding: 2px 7px; border-radius: 7px; font-weight: 800; font-size: 11px; line-height: 1.35;
  box-shadow: 0 3px 10px rgba(20,30,60,.22); font-variant-numeric: tabular-nums;
  animation: pt-pop .34s ease;
}
.pt-nowclock::after { content: ""; position: absolute; right: 7px; bottom: -3px; width: 7px; height: 7px;
  background: inherit; transform: rotate(45deg); border-radius: 1px; }
.pt-nowlive { width: 5px; height: 5px; border-radius: 50%; flex: 0 0 auto; animation: pt-live 1.7s ease-out infinite; }
.pt-nowcolon { animation: pt-blink 1s steps(1) infinite; }
@keyframes pt-pop  { 0% { transform: scale(1); } 42% { transform: scale(1.09); } 100% { transform: scale(1); } }
@keyframes pt-blink { 0%,50% { opacity: 1; } 51%,100% { opacity: .3; } }
@keyframes pt-live  { 0% { opacity: 1; transform: scale(1); } 60% { opacity: .45; transform: scale(.7); } 100% { opacity: 1; transform: scale(1); } }

.pt-tip {
  position: fixed; z-index: 4000; pointer-events: none; min-width: 148px; max-width: 232px;
  background: #fff; border: 1px solid #D7DEEA; border-radius: 11px; box-shadow: 0 14px 34px rgba(20,30,60,.22);
  padding: 9px 11px; font-size: 12px; color: #37415A;
}
.pt-tip::before { content: ""; position: absolute; left: 0; top: 0; bottom: 0; width: 4px;
  border-radius: 11px 0 0 11px; background: var(--brand-gradient); }
.pt-tip .tt-t { display: flex; align-items: center; gap: 7px; font-weight: 800; color: var(--verti-navy); font-size: 12.5px; margin-bottom: 2px; }
.pt-tip .tt-badge { width: 18px; height: 18px; border-radius: 5px; color: #fff; font-weight: 900; font-size: 11px;
  display: inline-flex; align-items: center; justify-content: center; flex: 0 0 auto; }
.pt-tip .tt-dot { width: 11px; height: 11px; border-radius: 3px; flex: 0 0 auto; }
.pt-tip .tt-dot.tt-nosig { background: repeating-linear-gradient(45deg, #C3CAD6 0 3px, #AEB6C4 3px 6px); }
/* Sprint 384: the title carries the project's FULL name + the unit. The unit
   never shrinks; the project ellipsises when the two don't fit (operator rule). */
.pt-tip .tt-proj { flex: 0 1 auto; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.pt-tip .tt-unit { flex: 0 0 auto; white-space: nowrap; }
.pt-tip .tt-unit::before { content: "·"; margin-right: 5px; color: #9AA4B7; }   /* separator lives here, not in the ellipsised text */
.pt-tip .tt-row { font-variant-numeric: tabular-nums; font-weight: 700; color: #37415A; }
.pt-tip .tt-dur { margin-top: 3px; font-weight: 800; font-size: 11.5px; }
.pt-tip .tt-dur.done { color: #15803D; }
.pt-tip .tt-dur.live { color: #B26A00; }
.pt-tip .tt-dur.gapc { color: #B91C1C; }

@media (prefers-reduced-motion: reduce) {
  .pt-now-mk, .pt-nowclock, .pt-nowlive, .pt-nowcolon { animation: none; }
  .pt-nowcolon { opacity: 1; }
}

/* Sprint 272: the state-key legend popover, opened by the "?" head icon. */
.pt-pop {
  position: absolute; top: 20px; left: 0; z-index: 30; width: min(252px, 94%);
  background: #fff; border: 1px solid #D7DEEA; border-radius: 11px;
  box-shadow: 0 12px 30px rgba(20,30,60,.18); padding: 10px 12px;
}
.pt-leg-title { font-size: 10px; font-weight: 800; color: var(--verti-navy); text-transform: uppercase; letter-spacing: .3px; margin-bottom: 6px; }
.pt-legrow { display: flex; align-items: center; gap: 7px; font-size: 11px; color: #42506b; font-weight: 600; padding: 2px 0; }
.pt-legsw { width: 12px; height: 12px; border-radius: 3px; flex: 0 0 auto; }
.pt-legsw-nosig { background: repeating-linear-gradient(45deg, #C3CAD6 0 3px, #AEB6C4 3px 6px); }
.pt-legsw-pad { background: #EAEDF4; border: 1px dashed rgba(29,51,96,.42); }   /* Sprint 439: view-pad zone */
.pt-legtx { flex: 1; }
.pt-legplus { width: 13px; height: 13px; border-radius: 50%; background: var(--verti-navy); color: #fff; font-size: 8px; font-weight: 900; line-height: 13px; text-align: center; flex: 0 0 auto; }
/* state % + connectivity % breakdown pills (full surfaces). */
.pt-bd { margin-top: 9px; display: flex; flex-direction: column; gap: 5px; }
.pt-bd-row { display: flex; flex-wrap: wrap; gap: 5px; }
.pt-pill {
  display: inline-flex; align-items: center; gap: 5px; font-size: 10px; font-weight: 700;
  color: var(--text); background: #F6F8FB; border: 1px solid #E7ECF3; border-radius: 999px; padding: 3px 8px;
}
.pt-pill b { color: var(--verti-navy); font-weight: 800; }
.pt-pill-lite { background: #FBFAF6; border-style: dashed; color: var(--muted); }   /* Sprint 439: lunch-allowance pill */
.pt-pdur { color: var(--muted); font-weight: 600; } /* Sprint 322: total duration next to the % */
.pt-pdot { width: 10px; height: 10px; border-radius: 3px; flex: 0 0 auto; }
.pt-pdot.pt-gapdot { background: repeating-linear-gradient(45deg, #FCE2C7 0 3px, #FFF1E0 3px 6px); border: 1px dashed #E8924A; }

/* Sprint 266: stacked-bar + legend for the admin/manager puantaj reports. */
.pt-sbar { display: flex; height: 12px; min-width: 110px; border-radius: 4px; overflow: hidden; border: 1px solid #E1E6EF; }
.pt-sbar i { display: block; height: 100%; }
.pt-leg { display: flex; flex-wrap: wrap; gap: 10px; padding: 11px 4px 2px; }
.pt-leg-i { display: flex; align-items: center; gap: 5px; font-size: 11px; color: var(--text); }
.pt-leg-d { width: 12px; height: 12px; border-radius: 3px; flex: 0 0 auto; }
table.data td.num { text-align: right; font-variant-numeric: tabular-nums; font-weight: 700; }
table.data td.barcell, table.data th.barcell { min-width: 120px; }

/* ===== Sprint 265: mobile puantaj page (mobile-puantaj.html) ===== */
.mp-datenav {
  display: flex; align-items: center; justify-content: space-between;
  background: #fff; border-radius: 11px; padding: 7px 11px; margin-bottom: 10px;
  box-shadow: 0 2px 6px rgba(29, 51, 96, .06);
}
.mp-datenav button {
  border: 0; background: #EEF1F8; color: var(--verti-navy); width: 32px; height: 32px;
  border-radius: 8px; font-weight: 800; font-size: 15px; cursor: pointer;
}
.mp-datenav .d { color: var(--verti-navy); font-weight: 800; font-size: 12.5px; }
.mp-tlcard { background: #fff; border-radius: 12px; padding: 11px; margin-bottom: 10px; box-shadow: 0 2px 8px rgba(29, 51, 96, .07); }
.mp-tlcard[hidden] { display: none; }
.mp-row {
  display: flex; gap: 8px; align-items: flex-start; background: #fff;
  border-radius: 11px; padding: 8px 10px; margin-bottom: 7px;
  box-shadow: 0 1px 3px rgba(29, 51, 96, .05);
}
.mp-row .dot { width: 9px; height: 9px; border-radius: 50%; margin-top: 3px; flex: 0 0 auto; }
.mp-row .tx { flex: 1; min-width: 0; }
.mp-row .tt { color: var(--verti-navy); font-weight: 800; font-size: 11.5px; }
.mp-row .mt { color: var(--muted); font-size: 9.5px; font-weight: 700; margin-top: 1px; }
.mp-cat {
  margin-top: 5px; display: inline-flex; align-items: center; gap: 4px;
  font-size: 9.5px; font-weight: 800; border-radius: 999px; padding: 3px 9px; cursor: pointer;
}
.mp-cat.set { background: #EAF0FB; color: var(--verti-navy); }
.mp-cat.add { background: #FFF1E0; color: #C2410C; border: 1px dashed #E8924A; }
.mp-row.gap { background: #FFFBF4; border: 1px dashed #E8924A; }
.mp-row.gap .dot { background: #E8924A; }
.mp-row.gap .tt { color: #C2410C; }
.mp-acts { display: flex; gap: 5px; flex: 0 0 auto; }
.mp-eb {
  width: 26px; height: 26px; border-radius: 7px; background: #EEF1F8; color: var(--verti-navy);
  display: flex; align-items: center; justify-content: center; font-size: 10px; border: 0; cursor: pointer;
}
.mp-empty { color: var(--muted); font-size: 12px; text-align: center; padding: 14px; }
.mp-fab {
  background: var(--brand-gradient); color: #fff; font-weight: 800; font-size: 13px;
  border: 0; border-radius: 11px; padding: 12px; width: 100%; cursor: pointer; margin-top: 2px;
}
/* composer fields inside the shared bottom-sheet */
.mp-comp .mobile-field { margin-bottom: 9px; }
.mp-comp .two { display: flex; gap: 8px; }
.mp-comp .two .mobile-field { flex: 1; }
.mp-catpick { display: flex; flex-wrap: wrap; gap: 6px; }
/* Sprint 459: the Sprint-279 lunch-window editor chips (.mp-lunchdur /
   .mp-durchips / .mp-lunchhint) were dead since the Sprint-439 allowance
   model removed the lunch band — rules deleted. */
/* Sprint 280: an operation (service_records) window in the day list reads as a
   dark-green WORK row, matching the summary bar. */
.mp-row.mp-oprow { background: #EAF7EE; border-color: #BFE6CB; }
.mp-catchip {
  border: 1.5px solid var(--border); background: #fff; border-radius: 999px;
  padding: 6px 11px; font-size: 11px; font-weight: 800; color: var(--verti-navy); cursor: pointer;
}
.mp-catchip.on { background: var(--verti-navy); color: #fff; border-color: var(--verti-navy); }

/* Sprint 259: Other Work bottom-sheet (the 5 non-service actions). */
.mobile-sheet-backdrop {
  position: fixed; inset: 0; z-index: 10040;
  background: rgba(15, 27, 51, .5); display: flex; align-items: flex-end;
}
.mobile-sheet-backdrop[hidden] { display: none; }
.mobile-sheet {
  width: 100%; background: #F4F6FA; border-radius: 18px 18px 0 0;
  max-height: 82vh; overflow: auto; box-shadow: 0 -8px 30px rgba(0, 0, 0, .3);
  animation: sheetUp .18s ease-out;
}
@keyframes sheetUp { from { transform: translateY(28px); } to { transform: translateY(0); } }
.mobile-sheet-head {
  position: sticky; top: 0; background: var(--brand-gradient); color: #fff;
  font-size: 15px; font-weight: 800; padding: 13px 16px;
  display: flex; align-items: center; justify-content: space-between;
}
.mobile-sheet-close {
  background: rgba(255, 255, 255, .2); border: 0; color: #fff;
  width: 30px; height: 30px; border-radius: 50%; font-size: 20px;
  cursor: pointer; line-height: 1;
}
.mobile-sheet-body { padding: 12px 14px 18px; display: flex; flex-direction: column; gap: 9px; }

/* Sprint 459: dead Sprint-261 .mobile-alert-chip rules removed (no matching
   markup — the counts moved to the header badges + bottom-nav badges). */

/* The header title is VERTI navy + bold. */
.mobile-headrow-title { color: var(--verti-navy); font-weight: 800; }

/* ============================================================================
   Sprint 303 — mobile home app-shell (technician + supervisor)
   Fixed top (header + puantaj + connectivity strip + always-on live strip),
   scrollable middle, fixed bottom nav. Applied only when the shell carries
   .msh-app (added in JS for the field roles); every other role keeps the
   original single-column layout untouched.
   ========================================================================== */
.mobile-shell.msh-app {
  height: 100vh; height: 100dvh;
  max-width: 480px; margin: 0 auto; padding: 0;
  display: flex; flex-direction: column; overflow: hidden;
}
.msh-app .msh-top {
  flex: 0 0 auto; position: relative; z-index: 5;
  background: var(--verti-navy-50);
  box-shadow: 0 4px 10px rgba(20, 30, 60, .10);
}
.msh-app .msh-mid {
  flex: 1 1 auto; min-height: 0; overflow-y: auto;
  -webkit-overflow-scrolling: touch; padding: 4px 8px 10px;
}

/* Sprint 444 — pull-to-refresh indicator (VERTI orange ring, approved mockup).
   Sticky inside the scroll container so it rides the top edge; --ptr is the
   live pull distance the JS sets while the finger drags. */
.msh-ptr {
  position: sticky; top: 0; height: 0; z-index: 40;
  display: flex; justify-content: center; overflow: visible;
  pointer-events: none;
}
.msh-ptr-win { position: fixed; left: 0; right: 0; top: 0; }
.msh-ptr .msh-ptr-spin {
  width: 34px; height: 34px; border-radius: 50%; background: #fff;
  box-shadow: 0 3px 10px rgba(29, 51, 96, 0.28);
  display: flex; align-items: center; justify-content: center;
  transform: translateY(calc(var(--ptr, 0px) - 44px));
  opacity: 0; transition: opacity 0.12s;
}
.msh-ptr.show .msh-ptr-spin { opacity: 1; }
.msh-ptr.spin .msh-ptr-spin { transition: transform 0.15s; }
.msh-ptr .msh-ptr-spin i {
  width: 18px; height: 18px; border-radius: 50%;
  border: 2.5px solid var(--border, #DDE3EE); border-top-color: var(--tke-orange);
  transform: rotate(calc(var(--ptr, 0px) * 3.4deg));
}
.msh-ptr.spin .msh-ptr-spin i { animation: msh-ptr-rot 0.9s linear infinite; }
@keyframes msh-ptr-rot { to { transform: rotate(360deg); } }
.msh-app .msh-nav { flex: 0 0 auto; }

/* Header — full-bleed gradient, edge-to-edge into the status bar (native side
   makes the status bar transparent; env(safe-area-inset-top) pads the notch). */
.msh-app .mobile-topbar {
  margin: 0; border-radius: 0; gap: 7px;
  padding: calc(env(safe-area-inset-top, 0px) + 3px) 11px 9px;
}
.msh-app .mobile-brand-mark { width: 32px; height: 32px; }

/* Wordmark lockup: VERTI AIM over TKE DISTRIBUTOR, two lines the SAME width,
   block height = the V-badge ring (32px), lines close but not touching. */
.msh-app .mobile-wordmark {
  height: 32px; justify-content: center; line-height: 1;
  margin-left: -3px; flex: 1; min-width: 0;
}
.msh-app .mobile-wordmark b {
  font-weight: 900; font-size: 17.6px; letter-spacing: .3px; color: #fff;
}
.msh-app .mobile-wordmark small {
  font-weight: 800; font-size: 7.7px; letter-spacing: 1.43px;
  color: rgba(255, 255, 255, .9); margin-top: 1.5px;
  white-space: nowrap; overflow: hidden; text-overflow: clip;
}

/* Header badges (conditional — shown only when their count > 0):
   sync queue · notifications · GPS mismatch (this month's total). */
.msh-badges { display: flex; align-items: center; gap: 7px; flex: 0 0 auto; }
.msh-badge {
  position: relative; width: 25px; height: 25px; border-radius: 50%;
  background: rgba(255, 255, 255, .16); border: 1px solid rgba(255, 255, 255, .5);
  color: #fff; display: grid; place-items: center; font-size: 11px;
  padding: 0; cursor: pointer;
}
.msh-badge[hidden] { display: none !important; }
.msh-badge-n {
  position: absolute; top: -4px; right: -4px;
  background: var(--tke-orange); color: #fff;
  font-size: 8px; font-weight: 800; line-height: 1;
  border-radius: 7px; padding: 1px 3px; min-width: 12px; text-align: center;
}

/* Avatar carries the 2-state GPS dot (green reporting / grey offline). */
.msh-app .mobile-user-chip { position: relative; }
.msh-gps-dot {
  position: absolute; bottom: -1px; right: -1px;
  width: 10px; height: 10px; border-radius: 50%;
  background: #22C55E; border: 2px solid #fff;
}
.msh-gps-dot.off { background: #94A3B8; }

/* Puantaj timeline sits flush under the header. */
.msh-app .msh-top .mp-tlcard { margin: 6px 11px 2px; padding: 5px 9px; }

/* ── Always-visible live-status strip ────────────────────────────────────
   Active op → the existing green active card (relocated, compact). No active
   op → the current puantaj state (colour-coded, no button). */
.msh-app .mobile-active-card { margin: 8px 11px; padding: 9px 12px; border-radius: 13px; }
.msh-live-idle {
  margin: 8px 11px; border-radius: 13px; background: #fff;
  display: flex; align-items: center; gap: 9px; padding: 9px 11px;
  border-left: 4px solid var(--msh-c, #86C99A);
  box-shadow: 0 1px 3px rgba(20, 30, 60, .06);
}
.msh-live-idle[hidden] { display: none !important; }
.msh-live-idle .dot {
  width: 9px; height: 9px; border-radius: 50%;
  background: var(--msh-c, #86C99A); flex: 0 0 auto;
}
.msh-live-idle .tx { flex: 1; min-width: 0; }
.msh-live-idle .tx b {
  display: block; font-size: 12.5px; font-weight: 800; color: var(--verti-navy);
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}
.msh-live-idle .tx small { font-size: 9.5px; color: var(--muted); }
.msh-live-idle .clk {
  font-size: 10px; font-weight: 800; color: var(--muted);
  flex: 0 0 auto; font-family: ui-monospace, Menlo, monospace;
}

/* Sprint 325: THE shared live-status strip (team-card.js `liveStripHtml`) — the
   read-only "canlı kart" on the map.html team cards + the mobile-team selected
   person; one class so a change lands on every live strip. Accent colour comes
   from --ls-c (the active operation, else the puantaj state). */
.live-strip {
  display: flex; align-items: center; gap: 8px;
  background: #fff; border-radius: 10px; padding: 7px 10px;
  border-left: 4px solid var(--ls-c, #94A3B8);
  box-shadow: 0 1px 4px rgba(13, 26, 43, .08);
}
.live-strip .ls-dot { width: 9px; height: 9px; border-radius: 50%; background: var(--ls-c, #94A3B8); flex: 0 0 auto; }
.live-strip .ls-tx { flex: 1; min-width: 0; }
.live-strip .ls-tx b {
  display: block; font-size: 12px; font-weight: 800; color: var(--ls-c, var(--verti-navy));
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}
.live-strip .ls-tx small {
  display: block; font-size: 9.5px; color: var(--muted); font-weight: 600;
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}
.live-strip .ls-clk {
  font-size: 11px; font-weight: 800; color: var(--ls-c); flex: 0 0 auto;
  font-variant-numeric: tabular-nums;
}
/* Sprint 337: project·unit split — project ellipsizes, unit is protected. */
.live-strip .ls-tx small.ls-sub { display: flex; align-items: baseline; gap: 3px; overflow: visible; }
.live-strip .ls-sub .ls-p { flex: 0 1 auto; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.live-strip .ls-sub .ls-u { flex: 0 0 auto; white-space: nowrap; }
.live-strip .ls-sub .ls-sep { flex: 0 0 auto; }
.live-strip .ls-seen { font-size: 10px; font-weight: 700; color: var(--muted); flex: 0 0 auto; }
.live-strip .ls-live {
  display: inline-flex; align-items: center; gap: 4px; flex: 0 0 auto;
  font-size: 8.5px; font-weight: 800; color: var(--ls-c); letter-spacing: .4px;
}
.live-strip .ls-live i {
  width: 7px; height: 7px; border-radius: 50%; background: var(--ls-c);
  animation: ls-blink 1.4s infinite;
}
@keyframes ls-blink { 0%, 100% { opacity: 1; } 50% { opacity: .35; } }

/* ── Fixed bottom navigation ──────────────────────────────────────────── */
.msh-nav {
  display: flex; gap: 1px; background: #fff;
  border-top: 1px solid var(--border);
  box-shadow: 0 -4px 10px rgba(20, 30, 60, .08);
  padding: 7px 4px calc(env(safe-area-inset-bottom, 0px) + 8px);
}
/* Sprint 460: instant navigation feedback — the tapped tab dims and a thin
   indeterminate brand-gradient line sweeps along the top edge until the next
   document paints (full-page navs over a ~220ms-RTT link read as dead taps
   without it). mobile-shell.js wireNavFeedback() drives both. */
.msh-navprog {
  position: fixed; top: 0; left: 0; right: 0; height: 3px; z-index: 12000;
  background: rgba(29, 51, 96, .12); overflow: hidden; pointer-events: none;
}
.msh-navprog[hidden] { display: none; }
.msh-navprog i {
  position: absolute; top: 0; bottom: 0; left: -40%; width: 40%;
  background: var(--brand-gradient); border-radius: 2px;
  animation: msh-navprog-sweep 0.9s ease-in-out infinite;
}
@keyframes msh-navprog-sweep { to { left: 100%; } }
.msh-tab.nav-going, .msh-more-item.nav-going { opacity: .55; }
.msh-tab {
  flex: 1 1 0; min-width: 0; text-decoration: none; position: relative;
  display: flex; flex-direction: column; align-items: center; gap: 3px;
}
.msh-tab .ic {
  width: 30px; height: 22px; border-radius: 8px;
  display: grid; place-items: center; font-size: 14px; color: #8A94A8;
}
.msh-tab .lb {
  font-size: 7px; font-weight: 700; color: #8A94A8;
  white-space: nowrap; max-width: 100%; overflow: hidden; text-overflow: ellipsis;
}
.msh-tab.on .ic { background: var(--brand-gradient); color: #fff; }
.msh-tab.on .lb { color: var(--verti-navy); font-weight: 900; }
.msh-tab:active .ic { transform: scale(.94); }
.msh-tab-badge {
  position: absolute; top: -3px; right: 16%;
  background: var(--tke-orange); color: #fff;
  font-size: 8px; font-weight: 800; line-height: 1;
  border-radius: 7px; padding: 1px 4px;
}
.msh-tab-badge[hidden] { display: none !important; }

/* "Diğer" (more) bottom sheet — the operations not on the nav bar. */
.msh-more-backdrop {
  position: fixed; inset: 0; z-index: 1200;
  background: rgba(15, 23, 42, .45);
  display: flex; align-items: flex-end;
}
.msh-more-backdrop[hidden] { display: none !important; }
.msh-more-sheet {
  width: 100%; max-width: 480px; margin: 0 auto;
  background: #fff; border-radius: 18px 18px 0 0;
  padding: 14px 16px calc(env(safe-area-inset-bottom, 0px) + 18px);
  box-shadow: 0 -8px 26px rgba(15, 23, 42, .28);
}
.msh-more-head {
  display: flex; align-items: center; justify-content: space-between;
  font-weight: 900; color: var(--verti-navy); font-size: 14px; margin-bottom: 12px;
}
.msh-more-close {
  border: 0; background: none; font-size: 26px; line-height: 1;
  color: var(--muted); cursor: pointer; padding: 0 4px;
}
/* Sprint 427: the columns are set inline in the render = the item count, so the
   icons auto-fit ONE row whatever the count. minmax(0,1fr) lets them shrink. */
.msh-more-grid { display: grid; gap: 12px 6px; }
.msh-more-item {
  display: flex; flex-direction: column; align-items: center; gap: 6px;
  text-decoration: none; min-width: 0;
}
.msh-more-ic {
  width: 46px; height: 46px; border-radius: 14px;
  display: grid; place-items: center; font-size: 18px; color: #fff;
  background: var(--brand-gradient); flex: 0 0 auto;
}
.msh-more-lb {
  font-size: 10px; font-weight: 700; color: var(--verti-navy);
  text-align: center; white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
  max-width: 100%;
}

/* The header badge cluster shows only in app-shell mode. (Sprint 459: the old
   .mobile-action-row / .mobile-notif-bubbles hide rule went with their markup.) */
.msh-badges { display: none; }
.msh-app .msh-badges { display: flex; }

/* ── Supervisor "Ekip" (team) page ────────────────────────────────────── */
/* (Sprint 459: dead .msh-teammap / .tm-open / .msh-stchip rules removed — the
   Sprint-324 redesign replaced them with the tsel mini-map + trow-dur.) */
.msh-teamhead { display: flex; align-items: center; gap: 8px; margin: 12px 13px 8px; }
.msh-teamhead .mobile-headrow-title { flex: 1; }
.msh-ttabs { display: flex; background: #E4E8F0; border-radius: 10px; padding: 2px; gap: 2px; flex: 0 0 auto; }
.msh-ttab { font-weight: 800; font-size: 9.5px; padding: 6px 9px; border-radius: 8px; color: var(--muted); cursor: pointer; }
.msh-ttab.on { background: #fff; color: var(--verti-navy); box-shadow: 0 1px 3px rgba(0, 0, 0, .12); }
.msh-trow {
  display: flex; align-items: center; gap: 10px; margin: 0 11px 7px;
  background: #fff; border-radius: 13px; padding: 9px 10px; text-decoration: none;
  box-shadow: 0 1px 2px rgba(20, 30, 60, .05);
}
.msh-tav {
  width: 36px; height: 36px; border-radius: 11px; flex: 0 0 auto;
  display: grid; place-items: center; font-weight: 900; font-size: 12px; color: #fff;
}
.msh-trow .tt { flex: 1; min-width: 0; }
.msh-trow .tt b {
  display: block; font-weight: 800; font-size: 12.5px; color: var(--verti-navy);
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}
.msh-trow .tt small {
  display: block; font-size: 9.5px; color: var(--muted); margin-top: 2px;
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}
.msh-trow .rt { display: flex; flex-direction: column; align-items: flex-end; gap: 3px; flex: 0 0 auto; }
.msh-trow .seen { font-weight: 700; font-size: 8px; color: var(--muted); font-family: ui-monospace, monospace; }
.msh-trow .chev { color: #B9C2D4; font-size: 15px; flex: 0 0 auto; }
.msh-team-empty { margin: 22px 13px; text-align: center; color: var(--muted); font-size: 13px; }

/* ── App-shell home agenda (Sprint 303) ──────────────────────────────── */
/* "Bu Ayın Bakımları" on ONE line (shrinks/ellipsizes, never overflows) with
   the Bekleyen/Tamam tabs inline to its right. */
.msh-app .mobile-headrow { align-items: center; margin: 10px 0 8px; }
.msh-app #mHeadTitle {
  flex: 1 1 auto; min-width: 0; font-size: 15px; line-height: 1.15;
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}
.msh-atabs-slot { flex: 0 0 auto; }
.msh-atabs-slot .mobile-atabs { margin: 0; }
/* Maintenance rows: project (shrinks with …) + unit (ALWAYS visible) on one
   VERTI-navy line; the fault badge sits right after the date (away from the
   button, no misclick); a wide dark-green QR-start button (QR icon + Başlat). */
.msh-app .mobile-assigned-row .maa-tx b {
  display: flex; align-items: baseline; min-width: 0; color: var(--verti-navy);
}
.msh-app .maa-proj { min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.msh-app .maa-sep,
.msh-app .maa-unit { flex: 0 0 auto; white-space: nowrap; }
.msh-app .maa-sep { padding: 0 5px; }
.msh-app .maa-l2 small { flex: 0 1 auto; }   /* don't grow → badge hugs the date */
.msh-app .maa-l2 .maa-fbs { margin-left: 0; }
.msh-app .maa-st.maa-qr {
  width: auto; min-width: 82px; height: 38px; padding: 0 12px;
  display: inline-flex; align-items: center; justify-content: center; gap: 6px;
  background: #15803D; color: #fff; font-size: 14px;
}
.msh-app .maa-qr-lb { font-weight: 800; font-size: 12.5px; }

/* Compact live (active) card — "Bitir" + manual-close icon, the clock rides
   the sub line (Sprint 365: was a single "Bitir" button until the manual
   close was un-hidden, see below). */
.msh-app .mobile-active-card {
  display: flex; flex-direction: row; align-items: center; gap: 10px;
  border: 1.5px solid #BBF0CB; border-left: 4px solid #15803D;
}
.msh-app .mobile-active-card.is-warn { border-color: #F3C0C0; border-left-color: #DC2626; }
.msh-app .mobile-active-head { display: none; }
.msh-app .mobile-active-main {
  flex: 1; min-width: 0; display: flex; flex-direction: column; gap: 2px;
}
.msh-app .mobile-active-title {
  font-size: 13.5px; font-weight: 800; color: var(--verti-navy);
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}
.msh-app .mobile-active-card.is-warn .mobile-active-title { color: #B91C1C; }
.msh-app .mobile-active-sub { font-size: 10.5px; font-weight: 600; color: var(--muted); }
.msh-app .mobile-active-sub .msh-clk {
  font-family: ui-monospace, Menlo, monospace; font-weight: 800; color: #15803D;
}
.msh-app .mobile-active-foot { margin-top: 0; flex: 0 0 auto; }
.msh-app .mobile-active-clock { display: none; }         /* clock now rides the sub */
/* Sprint 365 (operator request): the manual close is no longer hidden — it
   sits as a small icon-only button to the LEFT of "Bitir" (order:-1), same
   confirm + GPS-proximity flow, just no camera step. Icon-only relies on
   data-i18n-title for its accessible name (see mobile.html / live-card.js). */
.msh-app .mobile-active-manualclose {
  display: inline-flex; align-items: center; justify-content: center;
  order: -1; width: 34px; height: 34px; padding: 0; flex: 0 0 auto;
  font-size: 15px;
}
.msh-app .mobile-active-complete {
  background: #15803D; color: #fff; gap: 6px;
  padding: 9px 16px; border-radius: 10px; font-size: 13px;
}
.msh-app .mobile-active-complete i { display: inline; }  /* QR icon + "Bitir" */

/* ============================================================================
   Sprint 304 — mobile home field-fix follow-up (operator real-phone feedback)
   ========================================================================== */
/* item 4: header badges = avatar size (32px), all 3 always shown; only the
   orange count bubble is conditional. */
.msh-app .msh-badge { width: 32px; height: 32px; font-size: 13px; }
.msh-app .msh-badge .msh-badge-n { top: -4px; right: -4px; }
.msh-badge-n[hidden] { display: none !important; }

/* item 2: compact single-row "Açık / Biten" segmented tabs (heading never
   crushed — the tabs sit inline, short, low). */
.msh-app .mobile-atabs.msh-segs { background: #E4E8F0; border-radius: 9px; padding: 2px; gap: 2px; margin: 0; }
.msh-app .msh-segs .mobile-atab {
  height: 26px; padding: 0 9px; border-radius: 7px; font-size: 11px; font-weight: 800;
  background: transparent; color: var(--muted); border: 0;
  display: inline-flex; align-items: center; gap: 5px; box-shadow: none;
}
.msh-app .msh-segs .mobile-atab.on { background: var(--verti-navy); color: #fff; }
.msh-app .msh-segs .mobile-atab-badge {
  position: static; top: auto; right: auto;      /* inline (base rule is absolute) */
  background: #D3D9E4; color: var(--muted);
  font-size: 10px; font-weight: 900; border-radius: 6px; padding: 0 5px; min-width: 0;
}
.msh-app .msh-segs .mobile-atab.on .mobile-atab-badge { background: var(--tke-orange); color: #fff; }

/* item 3: "Servis Başlat" row looks like a service-record card — a left label
   + a Manuel button + a green QR+Başlat button (row height = a maintenance
   card). The button text labels show only in app-shell. */
.msh-sbtn-lb { display: none; }
.msh-app .msh-sbtn-lb { display: inline; }
.msh-app .mobile-start {
  display: flex; align-items: center; gap: 7px; width: 100%; box-sizing: border-box;
  background: #fff; border: 1px solid var(--border); border-radius: 12px;
  padding: 9px 11px; margin: 0 0 8px; position: relative; overflow: hidden;
  box-shadow: 0 1px 2px rgba(0, 0, 0, .04);
}
/* Sprint 304: the navy left bar — identical to a maintenance card's ::before. */
.msh-app .mobile-start::before {
  content: ''; position: absolute; left: 0; top: 0; bottom: 0; width: 4px;
  background: var(--verti-navy);
}
.msh-app .mobile-start .mobile-action-label {
  flex: 1 1 auto; min-width: 0; text-align: left; font-weight: 900; font-size: 13px;
  color: var(--verti-navy); white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}
.msh-app .mobile-start .mobile-action-form {
  position: static; flex: 0 0 auto; width: auto; height: 38px; padding: 0 11px; border-radius: 10px;
  background: #EAF0FB; color: var(--verti-navy); font-weight: 800; font-size: 12px;
  display: inline-flex; align-items: center; gap: 5px;
}
.msh-app .mobile-start .mobile-action-scan {
  position: static; flex: 0 0 auto; width: auto; height: 38px; padding: 0 11px; border-radius: 10px;
  background: #15803D; color: #fff; font-weight: 800; font-size: 12.5px;
  display: inline-flex; align-items: center; gap: 5px;
}

/* item 6: bottom nav — Option A "pill-active" (active tab = full gradient pill,
   icon + label side by side; inactive = compact icon+label). Minimal gap to the
   phone's home/back bar (safe-area + 4px) without overlapping it. */
.msh-nav { padding: 7px 6px calc(env(safe-area-inset-bottom, 0px) + 4px); gap: 4px; align-items: center; }
.msh-tab { flex: 0 0 auto; width: 52px; }
.msh-tab .ic { width: auto; height: auto; background: none; }
.msh-tab.on {
  flex: 1 1 auto; width: auto; flex-direction: row; justify-content: center; gap: 7px;
  background: var(--brand-gradient); border-radius: 12px; padding: 9px 10px;
}
.msh-tab.on .ic { background: none; color: #fff; font-size: 15px; }
.msh-tab.on .lb { color: #fff; font-weight: 900; font-size: 12px; }

/* Sprint 459: the Sprint-304 .msh-greet rules went with the dormant #mGreet
   markup (greeting retired from the home in Sprint 312). */

/* ============================================================================
   Sprint 425 — shared photo / document upload FIELD (assets/js/photo-upload.js)
   ONE look for every "photo of the part / defect" area (part-replacements +
   inspections), so a field user learns a single interaction.
   ========================================================================== */
.pf{ border:1.5px dashed var(--border); border-radius:11px; padding:12px 13px 13px;
  background:#F6F8FB; margin-bottom:13px; }
.pf-h{ display:flex; align-items:center; gap:9px; }
.pf-ic{ width:30px; height:30px; border-radius:8px; display:flex; align-items:center; justify-content:center;
  font-size:15px; color:#fff; flex:0 0 auto; }
.pf-t{ font-size:13.5px; font-weight:800; color:var(--text); line-height:1.2; }
.pf-t small{ display:block; font-weight:600; color:var(--muted); font-size:10.5px; margin-top:1px; }
.pf-badge{ margin-left:auto; min-width:20px; text-align:center; font-size:10px; font-weight:800;
  padding:2px 8px; border-radius:999px; background:var(--verti-navy); color:#fff; }
.pf-actions{ display:flex; gap:8px; margin:10px 0 11px; flex-wrap:wrap; }
.pf-btn{ display:inline-flex; align-items:center; gap:7px; border:1px solid var(--border); background:var(--surface);
  color:var(--verti-navy); font-weight:700; font-size:12px; padding:9px 14px; border-radius:9px; cursor:pointer; font-family:inherit; }
.pf-btn.solid{ background:var(--verti-navy); color:#fff; border-color:var(--verti-navy); }
.pf-btn:disabled{ opacity:.55; cursor:not-allowed; }
.pf-grid{ display:flex; flex-wrap:wrap; gap:9px; }
.pf-grid.pf-busy{ opacity:.5; pointer-events:none; }
.pf-empty{ color:var(--muted); font-size:12px; padding:2px 0; }
.pf-thumb{ position:relative; width:64px; height:64px; border-radius:9px; border:1px solid var(--border); overflow:hidden; background:#E1E8F0; }
.pf-thumb img{ width:100%; height:100%; object-fit:cover; display:block; }
.pf-thumb a{ position:absolute; inset:0; }
.pf-docchip{ position:relative; display:inline-flex; align-items:center; }
.pf-docchip a{ display:inline-flex; align-items:center; gap:6px; padding:8px 28px 8px 11px; background:#FEF2F2;
  border:1px solid #FCC8C8; border-radius:8px; color:#7A2828; font-size:11.5px; text-decoration:none; max-width:210px; }
.pf-x{ position:absolute; top:2px; right:2px; width:18px; height:18px; border-radius:50%; background:rgba(220,38,38,.94);
  color:#fff; font-size:11px; line-height:1; display:flex; align-items:center; justify-content:center; cursor:pointer; border:0; }
.pf-docchip .pf-x{ top:50%; transform:translateY(-50%); right:5px; }

/* Sprint 426 — "AI fix text" button (moved from the per-page inline styles to the
   shared sheet so the mobile field pages can reuse it). */
.btn-ai-fix{ display:inline-flex; align-items:center; gap:4px;
  background:linear-gradient(120deg,#7A2E6B,#E85A2A); color:#fff; border:none;
  border-radius:999px; padding:3px 11px; font-size:11px; font-weight:700;
  cursor:pointer; margin-left:8px; vertical-align:middle; transition:filter .15s; }
.btn-ai-fix:hover{ filter:brightness(1.1); }
.btn-ai-fix:disabled{ opacity:.5; cursor:wait; }

/* Sprint 428 — field tools toolbar (assets/js/ai-fix.js): AI fix · clear · speak,
   auto-added under every textarea on the service/fault/part/inspection pages. */
.ft-bar { display: flex; flex-wrap: wrap; gap: 6px; margin: 5px 0 2px; }
.ft-btn { display: inline-flex; align-items: center; gap: 5px; border: 1px solid var(--border);
  background: var(--surface); color: var(--verti-navy); font-weight: 700; font-size: 11px;
  padding: 6px 11px; border-radius: 999px; cursor: pointer; font-family: inherit; line-height: 1; }
.ft-btn.btn-ai-fix { background: linear-gradient(120deg,#7A2E6B,#E85A2A); color: #fff; border: 0; margin-left: 0; }
.ft-btn:disabled { opacity: .5; cursor: wait; }

/* ============================================================================
   Sprint 462 — fluid transitions ("Secenek B", operator-approved rev4).
   Cross-document View Transitions + light Liquid-Glass bottom bar + skeleton
   first-load. Sprint 463 (operator: "mobildeki apple gorsel tasarimi webe de
   uygula") made the @view-transition OPT-IN global here — the DESKTOP joined,
   so the per-page inline blocks 462 put in the mobile heads were removed.
   Every same-origin navigation (mobile, desktop, login) now animates; the
   named chrome groups below keep each surface's shell visually FIXED while
   only the content transitions. Unsupported browsers/WebViews ignore all of
   it and keep the old hard-cut behaviour.
   ========================================================================= */
@view-transition { navigation: auto; }

/* Named groups: the header + bottom bar snapshot separately, so they read as
   FIXED across the navigation; the ACTIVE nav pill morphs from its old tab to
   its new tab — the shared-element move (the "Apple" feel's centrepiece). */
.msh-app .mobile-topbar       { view-transition-name: msh-topbar; }
.msh-app .msh-nav             { view-transition-name: msh-nav; }
.msh-app .msh-nav .msh-tab.on { view-transition-name: msh-navpill; }

/* Sprint 463 — the DESKTOP's own named chrome: sidebar + topbar stay visually
   FIXED across a menu navigation (the MPA reads like an SPA), and the ACTIVE
   sidebar highlight morphs from its old menu item to the new one — the
   desktop twin of the mobile pill move. Pages without the shell (login)
   simply have no names and take the plain root fade. */
.layout .sidebar { view-transition-name: shell-sidebar; }
.topbar          { view-transition-name: shell-topbar; }
.nav a.active    { view-transition-name: shell-menupill; }

/* Sprint 463 — light glass on the (already sticky) desktop topbar, the same
   "hafif" material as the mobile bar: content scrolls beneath it; the brand
   gradient strip (.topbar::after) stays. Solid navy sidebar deliberately
   keeps its identity — no glass there. */
.topbar {
  background: rgba(255, 255, 255, .78);
  backdrop-filter: blur(12px) saturate(1.5);
  -webkit-backdrop-filter: blur(12px) saturate(1.5);
}

/* Root content — default: gentle rise+fade (used when no direction is known,
   e.g. a Diger-sheet hop or a deep link). */
::view-transition-old(root) { animation: var(--dur-out) var(--ease-exit) both vt-fade-out; }
::view-transition-new(root) { animation: var(--dur-in) var(--ease-spring) both vt-fade-in; }
@keyframes vt-fade-out { to   { opacity: 0; transform: translateY(-6px); } }
@keyframes vt-fade-in  { from { opacity: 0; transform: translateY(8px); } }

/* Directional push (Apple nav curve): the tapped direction is stored by
   mobile-shell.js on the outgoing tap and applied as a transition TYPE on
   pagereveal in the incoming document. Old page parallaxes back and dims;
   the incoming page slides over it. */
html:active-view-transition-type(vt-fwd)::view-transition-old(root)  {
  animation: var(--dur-nav) var(--ease-spring) both vt-push-out-l; }
html:active-view-transition-type(vt-fwd)::view-transition-new(root)  {
  animation: var(--dur-nav) var(--ease-spring) both vt-push-in-r; }
html:active-view-transition-type(vt-back)::view-transition-old(root) {
  animation: var(--dur-nav) var(--ease-spring) both vt-push-out-r; }
html:active-view-transition-type(vt-back)::view-transition-new(root) {
  animation: var(--dur-nav) var(--ease-spring) both vt-push-in-l; }
@keyframes vt-push-in-r  { from { transform: translateX(102%); } }
@keyframes vt-push-in-l  { from { transform: translateX(-102%); } }
@keyframes vt-push-out-l { to { transform: translateX(-26%) scale(.985); filter: brightness(.85); } }
@keyframes vt-push-out-r { to { transform: translateX(26%) scale(.985); filter: brightness(.85); } }

@media (prefers-reduced-motion: reduce) {
  ::view-transition-group(*),
  ::view-transition-old(*),
  ::view-transition-new(*) { animation: none !important; }
}

/* Light Liquid-Glass bottom bar (rev4 values — "cami daha hafif"): the bar
   floats over the content, which now scrolls beneath the glass. */
.mobile-shell.msh-app { position: relative; }
.msh-app .msh-nav {
  position: absolute; bottom: 0; left: 0; right: 0;
  background: rgba(255, 255, 255, .74); border-top: 0;
  backdrop-filter: blur(12px) saturate(1.5);
  -webkit-backdrop-filter: blur(12px) saturate(1.5);
  box-shadow: inset 0 1px 0 rgba(255, 255, 255, .7),   /* ust spekuler cizgi */
              inset 0 -1px 0 rgba(29, 51, 96, .05),    /* alt cam kenari */
              0 -6px 18px rgba(13, 21, 38, .07);       /* camin zemine golgesi */
}
/* Content clears the floating bar (padding inside the scroll container). */
.msh-app .msh-mid { padding-bottom: calc(env(safe-area-inset-bottom, 0px) + 76px); }

/* The active pill as light glass over the brand gradient: top sheen + 1px
   rim + soft inner shade. NO overflow:hidden — the Ariza count badge pokes
   above the pill and must not be clipped. */
.msh-app .msh-tab.on {
  box-shadow: 0 5px 14px rgba(122, 46, 107, .32),
              inset 0 1px 0 rgba(255, 255, 255, .4),
              inset 0 -4px 9px rgba(13, 21, 38, .14),
              inset 0 0 0 1px rgba(255, 255, 255, .2);
}
.msh-app .msh-tab.on::before {
  content: ''; position: absolute; left: 6%; right: 6%; top: 2px; height: 44%;
  border-radius: 10px 10px 40% 40%; pointer-events: none;
  background: linear-gradient(180deg, rgba(255, 255, 255, .28), rgba(255, 255, 255, .03));
}

/* Skeleton first-load (only when swrGet has NO snapshot — see skeletonHtml in
   mobile-shell.js) + the staggered "born in sequence" entry. */
.msh-sk { display: flex; align-items: center; gap: 8px; background: #fff;
  border: 1px solid #EEF1F6; border-left: 4px solid #E3E8F1;
  border-radius: 0 11px 11px 0; padding: 10px; margin-bottom: 8px; }
.msh-sk i { display: block; border-radius: 6px; background: #E9EDF5;
  animation: msh-sk-breathe 1.3s ease-in-out infinite; }
.msh-sk .skx { flex: 1; display: flex; flex-direction: column; gap: 6px; }
.msh-sk .l1 { height: 10px; width: 72%; }
.msh-sk .l2 { height: 8px; width: 48%; }
.msh-sk .btn { width: 64px; height: 34px; border-radius: 9px; flex: 0 0 auto; }
@keyframes msh-sk-breathe { 0%, 100% { opacity: 1; } 50% { opacity: .45; } }
/* Sprint 474 (C3) — desktop skeleton rows (shell.js skeletonRows). Same
   breathing placeholder idea as the mobile .msh-sk above, shaped like a
   desktop list row so the real data lands without moving anything. Only ever
   rendered when swrGet has no snapshot, i.e. a first-ever visit. */
.sk-list { display: flex; flex-direction: column; gap: 8px; }
.sk-row {
  display: flex; align-items: center; gap: 14px;
  background: #fff; border: 1px solid var(--border);
  border-radius: var(--radius-sm); padding: 14px 16px;
}
.sk-row i {
  display: block; height: 11px; border-radius: 5px; background: #E9EDF5;
  animation: msh-sk-breathe 1.3s ease-in-out infinite;
}
.sk-row i:nth-child(2) { animation-delay: .15s; }
.sk-row i:nth-child(3) { animation-delay: .3s; }

.msh-cas { opacity: 0; animation: msh-cas-in 480ms var(--ease-spring) forwards; }
@keyframes msh-cas-in { from { opacity: 0; transform: translateY(16px) scale(.97); }
                        to   { opacity: 1; transform: none; } }
@media (prefers-reduced-motion: reduce) {
  .msh-sk i { animation: none; }
  .msh-cas { animation-duration: 1ms; }
}
