:root {
  --sidebar-width: 220px;
  --bg-body: #f4f6f9;
  --bg-card: #ffffff;
  --bg-sidebar: #1e293b;
  --bg-topbar: #ffffff;
  --text-primary: #1e293b;
  --text-muted: #64748b;
  --text-sidebar: #cbd5e1;
  --text-sidebar-active: #ffffff;
  --border: #e2e8f0;
  --hover: #f1f5f9;
  --shadow: rgba(0,0,0,0.05);
  --bg-sidebar-hover: #334155;
  --bg-input: #ffffff;
  /* The one accent colour: the sidebar marker for the page you are on, and the
     ring that follows the keyboard. Stated once so both themes can answer it.

     The marker is the lighter blue in both themes, because the thing it is
     drawn on is dark in both: the daylight blue on the active row measured
     2.82:1, under the 3:1 a control needs to be made out at all. The focus
     ring is the darker blue, because it is drawn on the page rather than on
     the sidebar, and the page is white. */
  --accent: #60a5fa;
  --focus: #2563eb;
}

[data-theme="dark"] {
  --bg-body: #0f172a;
  --bg-card: #1e293b;
  --bg-sidebar: #0f172a;
  --bg-topbar: #1e293b;
  --text-primary: #e2e8f0;
  --text-muted: #94a3b8;
  --text-sidebar: #94a3b8;
  --text-sidebar-active: #ffffff;
  --border: #334155;
  --hover: #1e293b;
  --shadow: rgba(0,0,0,0.2);
  --bg-sidebar-hover: #1e293b;
  --bg-input: #334155;
  /* The marker is already the lighter blue. The focus ring joins it here: on a
     dark page the daylight blue reads as almost black and the ring vanishes. */
  --accent: #60a5fa;
  --focus: #60a5fa;
}

/* تمنع الـ transition في أول تحميل لمنع وميض الثيم */
html:not(.theme-ready) * {
  transition: none !important;
}

html.theme-ready * { transition: background-color 0.2s, color 0.2s, border-color 0.2s; }

body {
  background: var(--bg-body);
  color: var(--text-primary);
  font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif;
}

/* Topbar */
.topbar {
  background: var(--bg-topbar);
  border-bottom: 1px solid var(--border);
  box-shadow: 0 1px 3px var(--shadow);
  position: fixed;
  top: 0;
  left: 0;
  right: 0;
  z-index: 1030;
  padding: 0.5rem 1rem;
}

.topbar .navbar-brand {
  color: var(--text-primary);
  font-weight: 700;
}

.theme-btn, .user-btn {
  color: var(--text-muted);
  border: 1px solid var(--border);
  background: var(--bg-card);
}

.theme-btn:hover, .user-btn:hover {
  color: var(--text-primary);
  background: var(--hover);
}

/* Layout */
.layout {
  display: flex;
  margin-top: 56px;
  min-height: calc(100vh - 56px);
}

/* Sidebar */
.sidebar {
  width: var(--sidebar-width);
  min-width: var(--sidebar-width);
  background: var(--bg-sidebar);
  padding: 1rem 0;
  position: fixed;
  top: 56px;
  bottom: 0;
  overflow-y: auto;
}

.sidebar .nav-link,
.offcanvas .nav-link {
  color: var(--text-sidebar);
  padding: 0.7rem 1.2rem;
  display: flex;
  align-items: center;
  gap: 0.7rem;
  border-left: 3px solid transparent;
}

.sidebar .nav-link:hover,
.offcanvas .nav-link:hover {
  color: var(--text-sidebar-active);
  background: var(--bg-sidebar-hover);
}

/* The page you are on. There was no active state at all before this - every
   item looked the same whichever page was open - so it is marked three ways at
   once: the accent takes the border that was already reserved for it, the
   background matches hover, and the label is set in a heavier weight. */
.sidebar .nav-link.active,
.offcanvas .nav-link.active {
  color: var(--text-sidebar-active);
  background: var(--bg-sidebar-hover);
  border-left-color: var(--accent);
  font-weight: 600;
}

.sidebar .nav-link i,
.offcanvas .nav-link i { font-size: 1.1rem; width: 20px; text-align: center; }

/* Main */
/* The sidebar is `position: fixed`, so it takes up no space in the flex row and
   cannot hold this column off on its own: the margin does that. A flex item's
   default `min-width: auto` floors it at the width of its widest content -- a
   report table is wider than the screen -- so without `min-width: 0` this column
   grew past the viewport and the whole document scrolled sideways underneath the
   fixed sidebar. `max-width` then states the arithmetic outright: margin plus
   column is exactly the viewport, whatever the content wants. Wide tables scroll
   inside their own `.table-responsive`, which is what it is for. */
.main-content {
  flex: 1;
  min-width: 0;
  max-width: calc(100% - var(--sidebar-width));
  margin-left: var(--sidebar-width);
  padding: 1.5rem;
}

/* Signed out: no sidebar to clear, so the column is the whole width. */
.main-content.full-width {
  margin-left: 0;
  max-width: 100%;
}

/* Charts */
/* A responsive Chart.js canvas with maintainAspectRatio: false takes its height
   from the box it sits in, so that box has to get its height from somewhere
   other than the canvas. Left in a card body, the two asked each other: the
   canvas was handed the whole content box - including the area list sharing it -
   and the body then grew by that much again on the next resize, over and over,
   which is how one chart reached hundreds of pixels past its size and made
   scrolling crawl. These frames are that other source of truth. Cards may still
   stretch to equal heights around them; the chart no longer notices. */
.chart-frame {
  position: relative;
  width: 100%;
}

.chart-frame > canvas { display: block; }

.chart-frame-trend    { height: 280px; }
.chart-frame-area     { height: 240px; }
.chart-frame-people   { height: 260px; }
.chart-frame-location { height: 260px; }
.chart-frame-faults   { height: 260px; }
/* A long fault list earns more room, but from a second fixed height rather than
   from the number of rows: the frame stops where a dashboard can still show it. */
.chart-frame-faults-tall { height: 340px; }
.chart-frame-sla      { height: 240px; }

/* Auth pages */
.auth-container {
  display: flex;
  align-items: center;
  justify-content: center;
  min-height: calc(100vh - 56px);
  padding: 1rem;
}

.auth-card {
  background: var(--bg-card);
  border: 1px solid var(--border);
  border-radius: 12px;
  padding: 2rem;
  width: 100%;
  max-width: 420px;
  box-shadow: 0 4px 20px var(--shadow);
}

/* Cards */
.card {
  background: var(--bg-card);
  border: 1px solid var(--border);
  border-radius: 10px;
  box-shadow: 0 1px 3px var(--shadow);
}

.card-header {
  background: transparent;
  border-bottom: 1px solid var(--border);
  padding: 0.75rem 1.25rem;
}

/* Stats */
.stat-card {
  background: var(--bg-card);
  border: 1px solid var(--border);
  border-radius: 10px;
  padding: 1.2rem;
  display: flex;
  align-items: center;
  gap: 1rem;
  box-shadow: 0 1px 3px var(--shadow);
}

.stat-icon {
  width: 48px;
  height: 48px;
  border-radius: 10px;
  display: flex;
  align-items: center;
  justify-content: center;
  font-size: 1.3rem;
  color: #fff;
}

.stat-info h2 { margin: 0; font-size: 1.6rem; font-weight: 700; }
.stat-info span { font-size: 0.8rem; color: var(--text-muted); }

/* Clinic Analytics */
/* The dashboard reads as six figures for one month, so its cards are levelled
   and its values share a line. That is this page's presentation, not a change
   to the stat card every other page uses: each rule below is scoped to the
   page, and the shared component above is exactly as it was. */
.clinic-analytics .stat-card,
.technician-report .stat-card {
  padding: 1rem 1.1rem;
  /* Top, not centre: every value then starts on the same line across the row,
     whatever the label underneath it costs. */
  align-items: flex-start;
  gap: 0.85rem;
  /* Equal heights come from the row, and a row only equalises the cards on its
     own line. The floor is what keeps the six matching when they wrap onto two
     lines, where one line carries the measured count and the other does not. */
  height: 100%;
  min-height: 6.25rem;
}

/* Never squeezed by a long value: the icons line up down the row. */
.clinic-analytics .stat-icon,
.technician-report .stat-icon { flex: 0 0 48px; }

/* min-width: 0 lets a long value shorten the box rather than widen it. */
.clinic-analytics .stat-info,
.technician-report .stat-info { min-width: 0; }
.clinic-analytics .stat-info h2,
.technician-report .stat-info h2 { line-height: 1.15; }
.clinic-analytics .stat-info span,
.technician-report .stat-info span { display: block; line-height: 1.25; }

/* A measured duration is two words wide; at this size it stays on one line
   instead of wrapping and making its card taller than the four beside it. */
.clinic-analytics .stat-card-measure .stat-info h2,
.technician-report .stat-card-measure .stat-info h2 { font-size: 1.35rem; white-space: nowrap; }

/* The second fact about the same number - how many jobs it was measured over. */
.clinic-analytics .stat-info .stat-note,
.technician-report .stat-info .stat-note { font-size: 0.72rem; }

/* Global Analytics: the SLA row */
/* Six figures on one line, where the labels are not the same length - "SLA Met"
   is one line and "Reports Measured" is two. Left to itself each card was as
   tall as its own label, so the row stepped up and down and the icons went with
   it. Height now comes from the line: a Bootstrap column already stretches to
   the tallest card on its line, so the card fills its column and the six match
   whatever their labels cost. Content starts at the top rather than being
   centred, which is what puts the icons and the values on the same line across
   the row instead of floating in the middle of cards of two different sizes.
   Nothing here changes a figure, and nothing here leaves this row: the four KPI
   cards above it, and the shared stat card every other page uses, are as they
   were. */
.sla-stat-grid .stat-card {
  height: 100%;
  align-items: flex-start;
}

/* The headline KPI row, wherever a page opens with one */
/* Written for Global Analytics and now carried by the dashboard, the clinic
   dashboard and the schedules page too, because all four open with the same
   row and all four had the same problem waiting in it. Opting a row in is a
   class in the markup, so a page that wants the plain card still gets it.

   Why it needed a scope of its own: at a laptop width its four cards are about
   176px wide, and a 48px icon with
   1.2rem of padding left 74px for the text. "1h 48m" broke across two lines,
   "Avg Resolution (33)" onto three, and that card stood 173px tall beside a
   94px neighbour - four figures that no longer read as one row.
   So this row gets the room instead of a smaller typeface: a 40px icon, a
   point less padding, and a value sized to fit the column it actually has.
   Height comes from the line, as it does for the SLA row below, so the four
   match whatever their labels cost. Everything here is scoped to the row: the
   shared stat card that the dashboard, the alerts page and the clinic
   dashboard use is untouched. */
.kpi-grid .stat-card {
  padding: 1rem 1.1rem;
  /* Top, not centre: the four values then sit on one line across the row. */
  align-items: flex-start;
  gap: 0.85rem;
  height: 100%;
}

/* The icon is an accent, not a third of the card. */
.kpi-grid .stat-icon { flex: 0 0 40px; width: 40px; height: 40px; font-size: 1.1rem; }

/* min-width: 0 lets a long value shorten the box rather than widen it. */
.kpi-grid .stat-info { min-width: 0; }
.kpi-grid .stat-info h2 { font-size: 1.45rem; line-height: 1.15; }
.kpi-grid .stat-info span { display: block; line-height: 1.25; }

/* How many reports the average was taken over: a second fact about the figure
   above it, and never competing with it. */
.kpi-grid .stat-info .stat-note { font-size: 0.72rem; }

/* Below the large breakpoint these four cards are about 170px wide, which is
   not enough for "1h 48m" at the size above: it broke across two lines and took
   its card with it. The value steps down one size instead, and the floor keeps
   the four level when they wrap onto two rows on a phone. */
@media (max-width: 991.98px) {
  .kpi-grid .stat-info h2 { font-size: 1.3rem; }
  .kpi-grid .stat-card { min-height: 6.5rem; }
}

/* Filters: the period presets are one control, not five more fields. */
.filter-actions {
  display: flex;
  flex-wrap: wrap;
  justify-content: space-between;
  align-items: center;
  gap: 0.5rem;
  margin-top: 0.9rem;
  padding-top: 0.75rem;
  border-top: 1px solid var(--border);
}

.preset-bar {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 0.3rem;
}

.preset-bar .btn {
  --bs-btn-padding-y: 0.15rem;
  --bs-btn-padding-x: 0.55rem;
  --bs-btn-font-size: 0.78rem;
}

/* Longest Response Jobs: the fault text wraps and takes the slack, so the short
   columns keep their single line and Response and View stay on screen. Nothing
   sets a width here on purpose - a percentage or a minimum on one column makes
   a table size itself around that column, which is how it got wider, not
   narrower.

   The cap is on the fault text, not on its column. Auto table layout hands a
   column as much as its longest line asks for and ignores a max-width on the
   cell, so the text is given a box of its own to be capped in - which lowers
   what the column asks for. A minimum here would do the reverse: it raises the
   table's smallest possible width, and a phone then lays the whole page out
   that wide instead of letting the table scroll.

   On a phone the browser lays the page out wide enough for this table rather
   than letting it scroll, so the table is kept close to the width of the
   screen there: smaller type, tighter cells, and the fault text capped. */
.longest-table .fault-text,
.worklog-table .fault-text { display: inline-block; max-width: 22rem; }
/* Kept on one line where there is room for it. Bootstrap's own nowrap utility
   cannot be released again at a smaller size, so these columns use their own. */
.longest-table .nowrap-wide,
.worklog-table .nowrap-wide { white-space: nowrap; }

/* Page header */
/* One rhythm for every page: the title, a quieter sentence under it if the page
   has one, and the same gap before the first card. The sizes are stated here
   rather than page by page, which is what stopped the descriptions from
   competing with the titles they explain. */
.page-header { margin-bottom: 1.25rem; }
.page-header h4 {
  margin: 0;
  font-weight: 600;
  letter-spacing: -0.01em;
}
.page-header h4 i { margin-right: 0.5rem; }
.page-header p {
  margin-bottom: 0;
  font-size: 0.875rem;
  color: var(--text-muted);
}

/* A card's own heading: present, but a step below a page title. */
.card-header h6 {
  font-weight: 600;
  letter-spacing: 0.01em;
}

/* A block that supports the page rather than leading it. Same card, same
   content, a heading that waits its turn. */
.card-subtle .card-header h6 { color: var(--text-muted); }

/* The actions in a page header are one size on every page. They were btn-sm on
   the setup pages and full size on Reports, Schedules and Analytics, so the
   same control changed size as you moved between pages. What separates them is
   weight - one filled primary action per page and outlines for the rest - and
   that still reads; size is no longer saying a second, different thing. */
.page-header .btn {
  --bs-btn-padding-y: 0.3rem;
  --bs-btn-padding-x: 0.7rem;
  --bs-btn-font-size: 0.8125rem;
}

/* Four counts read at a glance, not four headlines. At h4 they measured 22px -
   exactly the size of the page title above them - so the alert block competed
   with the name of the page it sits on. They are still the first numbers after
   the headline figures, and still coloured when they are not zero. */
.alert-metrics h4 { font-size: 1.15rem; font-weight: 600; }
.alert-metrics p { font-size: 0.75rem; }

/* Keyboard focus stays visible wherever the eye has to follow it. Bootstrap's
   own ring is kept; this only guarantees one on the controls that had none of
   their own, and it is drawn outside the element so nothing shifts. */
.btn:focus-visible,
.nav-link:focus-visible,
.page-link:focus-visible,
.dropdown-item:focus-visible {
  outline: 2px solid var(--focus);
  outline-offset: 2px;
}

/* Tables */
.table {
  color: var(--text-primary);
  margin-bottom: 0;
}

.table thead th {
  border-bottom: 2px solid var(--border);
  color: var(--text-muted);
  font-size: 0.8rem;
  text-transform: uppercase;
  letter-spacing: 0.5px;
}

.table td, .table th {
  border-color: var(--border);
  vertical-align: middle;
}

.table-hover tbody tr:hover {
  background: var(--hover);
}

/* A table that scrolls sideways keeps what is inside it inside it. A
   visually-hidden label is positioned absolutely, and an absolutely positioned
   element is laid out against its nearest positioned ancestor - the card, not
   the cell - so the scroll container never clipped it: it sat at the table's
   right edge in the document's own coordinates and stretched the page to
   reach it. On a phone that pushed the theme and account controls off the
   screen. Positioning the scroller makes it the containing block, so anything
   absolute inside a table now scrolls with the table. Nothing moves visually:
   a static element does not care whether its ancestor is positioned. */
.table-responsive { position: relative; }

/* Tables that list records */
/* Scoped rather than applied to every table: this is the dashboard's two
   lists, the reports list, the two clinic lists and Users & Access, which were
   inspected together. A report row carries eleven columns, and at the body size
   Bootstrap gives it the row asked for more width than a laptop screen has
   before any of it could be read. The head is untouched - it already had a
   size, a weight and its uppercase - and detail tables, forms and the SLA table
   on a report keep the size they have. */
.data-table { font-size: 0.875rem; }

.data-table > :not(caption) > * > * {
  padding-top: 0.55rem;
  padding-bottom: 0.55rem;
}

/* The actions column sits against the right edge and is never broken across
   two lines: a wrapped button reads as a second row of the table. */
.data-table .col-actions { text-align: right; white-space: nowrap; }

/* The controls inside it are one group of equals rather than three separate
   full-size buttons, which is what made a row of them look busier than the
   record they belong to. Every action stays present and stays labelled. */
.table-actions {
  display: flex;
  flex-wrap: wrap;
  gap: 0.35rem;
  justify-content: flex-end;
}

.table-actions .btn {
  --bs-btn-padding-y: 0.2rem;
  --bs-btn-padding-x: 0.5rem;
  --bs-btn-font-size: 0.78rem;
  white-space: nowrap;
}

/* A table with nothing in it says so in one voice, with the same room around
   the sentence wherever it appears. Not scoped to the dense tables: an empty
   state looks the same on every page, dense or not, which is why the rule
   stands on its own class rather than under .data-table. */
.empty-row td {
  padding-top: 1.75rem;
  padding-bottom: 1.75rem;
  color: var(--text-muted);
}

/* Form controls */
.form-control, .form-select {
  background: var(--bg-input);
  color: var(--text-primary);
  border-color: var(--border);
}

.form-control:focus, .form-select:focus {
  background: var(--bg-input);
  color: var(--text-primary);
  border-color: #3b82f6;
  box-shadow: 0 0 0 0.2rem rgba(59,130,246,0.15);
}

/* Dropdown */
.dropdown-menu {
  background: var(--bg-card);
  border: 1px solid var(--border);
}

.dropdown-item {
  color: var(--text-primary);
}

.dropdown-item:hover {
  background: var(--hover);
  color: var(--text-primary);
}

/* Labels */
.form-label { color: var(--text-muted); margin-bottom: 0.25rem; }

/* Badges */
/* A status badge takes its colour from a map inside the template - Open becomes
   bg-warning, In Progress becomes bg-info - and that map is in eight files, so
   no badge built from it can be told there to carry dark text. Every Open in
   the product was therefore white on amber, which measures 1.63:1, and every In
   Progress white on cyan; the four badges written by hand elsewhere did say
   text-dark and were the only legible ones. Only the text colour changes here.
   The amber is still amber and the cyan still cyan, so nothing that reads a
   status by its colour reads it any differently. */
.badge.bg-warning,
.badge.bg-info { color: #1e293b; }

/* One weight for all of them: at Bootstrap's 700 a column of statuses was set
   heavier than the records it described. */
.badge { font-weight: 600; }

/* The same amber, this time as text rather than as a background. On a white
   card it measures 1.63:1 - the identical figure the badges gave, and for the
   identical reason - so the counts that say something needs attention were the
   hardest numbers on the dashboard to read. Darkened for the light theme only:
   on the dark card the original amber already reads at 8.97:1, so the dark
   theme keeps it. Nothing else about the warning changes. */
.text-warning { color: #8a6100 !important; }

/* Badges in setup pages */
.room-badge {
  background: var(--bg-input) !important;
  color: var(--text-primary) !important;
  border: 1px solid var(--border) !important;
  font-weight: normal;
}
.asset-badge {
  background: var(--bg-input) !important;
  color: var(--text-primary) !important;
  border: 1px solid var(--border) !important;
  font-weight: normal;
}

/* Table dark mode overrides */
[data-theme="dark"] .table {
  --bs-table-bg: var(--bg-card);
  --bs-table-color: var(--text-primary);
  --bs-table-hover-bg: var(--hover);
  --bs-table-striped-bg: rgba(255,255,255,0.02);
}

/* Card dark mode overrides */
[data-theme="dark"] .card {
  --bs-card-bg: var(--bg-card);
  --bs-card-color: var(--text-primary);
  --bs-card-cap-bg: transparent;
}

/* Dropdown dark mode overrides */
[data-theme="dark"] .dropdown-menu {
  --bs-dropdown-bg: var(--bg-card);
  --bs-dropdown-color: var(--text-primary);
  --bs-dropdown-border-color: var(--border);
  --bs-dropdown-link-color: var(--text-primary);
  --bs-dropdown-link-hover-bg: var(--hover);
  --bs-dropdown-link-hover-color: var(--text-primary);
}

/* Form dark mode overrides */
[data-theme="dark"] .form-control,
[data-theme="dark"] .form-select {
  --bs-form-control-bg: var(--bg-input);
  --bs-form-control-color: var(--text-primary);
  --bs-form-control-border-color: var(--border);
}

/* List group dark mode */
[data-theme="dark"] .list-group {
  --bs-list-group-bg: var(--bg-card);
  --bs-list-group-color: var(--text-primary);
  --bs-list-group-border-color: var(--border);
}

/* Navbar/topbar dark */
[data-theme="dark"] .navbar {
  --bs-navbar-color: var(--text-primary);
}

/* Pagination dark */
[data-theme="dark"] .pagination {
  --bs-pagination-bg: var(--bg-card);
  --bs-pagination-color: var(--text-primary);
  --bs-pagination-border-color: var(--border);
  --bs-pagination-hover-bg: var(--hover);
  --bs-pagination-hover-color: var(--text-primary);
}

/* Progress dark */
[data-theme="dark"] .progress {
  --bs-progress-bg: var(--bg-input);
}

/* Alerts dark */
[data-theme="dark"] .alert-danger {
  --bs-alert-bg: rgba(220,53,69,0.15);
  --bs-alert-color: #f8919a;
  --bs-alert-border-color: rgba(220,53,69,0.3);
}

/* Danger text dark */
/* Bootstrap's red is mixed to sit on a white page; on the dark card it reads at
   3.23:1, which is under the minimum for text at the size a count is set in -
   and a breached count is the one number on the dashboard nobody should have to
   lean in for. The same lighter red the dark theme already gives alert-danger,
   said once more for the text utility. Light mode keeps Bootstrap's own red. */
[data-theme="dark"] .text-danger { color: #f8919a !important; }

/* And the amber goes back to being amber on a dark card, where it always read
   well. The light theme's darker one would disappear into it. */
[data-theme="dark"] .text-warning { color: #ffc107 !important; }

/* Close button dark */
[data-theme="dark"] .btn-close {
  filter: invert(1) grayscale(100%) brightness(200%);
}

/* Modal dark */
[data-theme="dark"] .modal-content {
  --bs-modal-bg: var(--bg-card);
  --bs-modal-color: var(--text-primary);
  --bs-modal-border-color: var(--border);
}

/* Offcanvas dark */
[data-theme="dark"] .offcanvas {
  --bs-offcanvas-bg: var(--bg-card);
  --bs-offcanvas-color: var(--text-primary);
  --bs-offcanvas-border-color: var(--border);
}

/* Buttons - outline secondary dark mode fix */
[data-theme="dark"] .btn-outline-secondary {
  --bs-btn-color: var(--text-muted);
  --bs-btn-border-color: var(--border);
  --bs-btn-hover-bg: var(--hover);
  --bs-btn-hover-color: var(--text-primary);
}

/* Buttons - primary dark mode fix */
[data-theme="dark"] .btn-primary {
  --bs-btn-bg: #3b82f6;
  --bs-btn-border-color: #3b82f6;
}

/* Scrollbar */
::-webkit-scrollbar { width: 6px; }
::-webkit-scrollbar-track { background: transparent; }
::-webkit-scrollbar-thumb { background: var(--text-muted); border-radius: 3px; }

/* Responsive */
@media (max-width: 768px) {
  .sidebar { display: none; }
  /* The desktop reservation is undone here, both halves of it: leaving the
     max-width behind would keep a sidebar's worth of width taken on a phone,
     where the navigation is the offcanvas drawer instead. */
  .main-content { margin-left: 0; max-width: 100%; padding: 1rem; }

  /* Stacked two to a row on a phone, so this page's cards give back some
     padding. Scoped like the rest of them: the shared card is untouched. */
  .clinic-analytics .stat-card,
  .technician-report .stat-card { padding: 0.85rem; gap: 0.7rem; min-height: 5.5rem; }
  /* This page's longest label runs to two lines beside a measured count, so its
     six cards need a slightly higher floor than the clinic's to stay level when
     they wrap onto three rows. */
  .technician-report .stat-card { min-height: 6.2rem; }
  .clinic-analytics .stat-icon,
  .technician-report .stat-icon { flex-basis: 40px; width: 40px; height: 40px; font-size: 1.1rem; }
  .clinic-analytics .stat-info h2,
  .technician-report .stat-info h2 { font-size: 1.35rem; }
  .clinic-analytics .stat-card-measure .stat-info h2,
  .technician-report .stat-card-measure .stat-info h2 { font-size: 1.15rem; }

  /* On a phone the table gives up the lines it holds on a laptop: the date and
     the chair may wrap, and the fault text is capped. Only the response keeps
     its line, because "31 min" split over two reads as two numbers.

     It also tightens: smaller type and narrower cells. That is not only so
     less of it sits off-screen - a table cannot be laid out narrower than the
     sum of its columns' smallest widths, and a phone sizes the whole page to
     fit that sum rather than letting the table scroll. Tighter columns lower
     the sum. Nothing is dropped or cut short. */
  .longest-table,
  .worklog-table { font-size: 0.75rem; }
  .longest-table > :not(caption) > * > *,
  .worklog-table > :not(caption) > * > * { padding-left: 0.3rem; padding-right: 0.3rem; }
  .longest-table .fault-text,
  .worklog-table .fault-text { max-width: 11rem; }
  .longest-table .nowrap-wide,
  .worklog-table .nowrap-wide { white-space: normal; }

  /* Shorter on a phone, but still tall enough to read the axis labels - and
     still a fixed height, which is the part that matters. */
  .chart-frame-trend,
  .chart-frame-people,
  .chart-frame-location { height: 220px; }
  .chart-frame-area,
  .chart-frame-sla { height: 200px; }
  .chart-frame-faults,
  .chart-frame-faults-tall { height: 260px; }
}

/* Dark mode tweaks for assets page */
[data-theme="dark"] .modal-header,
[data-theme="dark"] .modal-footer {
  background: var(--bg-card);
  color: var(--text-primary);
}
[data-theme="dark"] .asset-badge {
  background: var(--bg-input) !important;
  color: var(--text-primary) !important;
  border-color: var(--border) !important;
}
