@import url('https://fonts.googleapis.com/css2?family=Roboto:ital,wght@0,300;0,400;0,500;0,700;0,900;1,400&family=Roboto+Mono:wght@400;500;700&display=swap');

/* ============================================================================
   SOFT — the chosen direction, applied as a theme layer.
   ----------------------------------------------------------------------------
   Loaded AFTER css/style.css on every page. It changes how the dashboard looks
   and nothing else: no page markup moves, no element id changes, no JavaScript
   is touched. Removing one <link> from a page puts that page back exactly as it
   was, which is the safety property that matters most here.

   The idea: tinted grounds instead of shadows. Each region of a page sits on its
   own pale wash, so structure is read as colour rather than as boxes floating
   above boxes. Rounded, roomy, and deliberately unintimidating — the people
   using this are ward staff and unit heads, not software operators.

   The palette is the phone app's, from apps/survey_app/lib/theme.dart, so the
   two products look like one.
   ========================================================================== */

:root{
  /* ── Surfaces. The old system was dark-first; every one of these is inverted
        rather than tweaked, which is why the whole dashboard turns over at once. */
  /* The page ground is deliberately NOT near-white. At #FAFBFE the whole screen
     was one sheet of white and the cards had nothing to sit against — which is
     what made it glaring to look at for any length of time. A ground with some
     weight in it lets the white cards read as cards and takes the brightness
     down without making anything harder to read. */
  --bg-color:#EDF1F8;
  --bg2:#F4F7FC;
  --sidebar-bg:#0B2447;
  --card-bg:#FFFFFF;
  --elevated-bg:#FFFFFF;
  /* White, not off-white. The labels on these forms are white chips notched into
     the top border of the field; against an off-white field the chip read as a
     little patch sitting on the line rather than part of it. */
  --input-bg:#FFFFFF;

  --border-color:#E8ECF4;
  --border-color-faint:#F0F3F9;
  --border-color-strong:#D8DFEC;

  --text-primary:#171C2B;
  --text-secondary:#4B5566;
  --text-muted:#9AA2B4;

  /* ── Brand and signal, straight from the app ── */
  --primary-color:#1668E3;
  --primary-hover:#0B47A8;
  --primary-light:#EEF4FE;
  --secondary-color:#0D9488;
  --secondary-light:#E6F6F4;

  --success-color:#0F8A4D;
  --success-light:#E7F6EE;
  --warning-color:#B45309;
  --warning-light:#FDF2E3;
  --error-color:#BE3455;
  --error-light:#FDEAEF;
  --info-color:#6B4EE6;
  --info-light:#F0ECFE;

  --grid-line:rgba(22,104,227,.06);

  /* ── Type: ROBOTO, because that is literally what the phone app uses.
        The app sets no font at all, so it inherits Flutter's Material default,
        which on Android is Roboto. Matching it here is the difference between
        two products that share a palette and two that are the same product.
        The serif display face is dropped with it. ── */
  --font-family:'Roboto',system-ui,-apple-system,"Segoe UI",Arial,sans-serif;
  --font-display:'Roboto',system-ui,-apple-system,"Segoe UI",Arial,sans-serif;
  /* Roboto Mono, not JetBrains Mono. Eleven things use the mono face — role
     chips, timestamps, small-caps labels — and in a different typeface from the
     rest of the page they read as bolted on. Roboto Mono is the same family, so
     columns still line up and nothing looks borrowed. */
  --font-mono:'Roboto Mono',ui-monospace,"SF Mono",Menlo,Consolas,monospace;

  /* ── Shape. Soft is defined by its rounding more than by anything else. ── */
  --radius-xs:8px;
  --border-radius-sm:12px;
  --border-radius:16px;
  --radius-lg:22px;
  --radius-pill:999px;

  /* ── Depth. Almost none: this direction uses tint, not shadow. What is left is
        a hairline that reads as a soft edge rather than as a drop shadow. ── */
  --shadow:0 1px 2px rgba(23,28,43,.04);
  --shadow-lg:0 8px 28px rgba(23,28,43,.08);
  --shadow-color:rgba(23,28,43,.06);
}

body{
  background:var(--bg-color);
  color:var(--text-primary);
  -webkit-font-smoothing:antialiased;
}

/* ── Sidebar: deep navy ──────────────────────────────────────────────────────
   The single biggest field of white on the screen, and therefore the single
   biggest source of the glare. Dark here does three things at once: it takes
   the brightness down, it gives the content area an edge to sit against, and it
   leaves the white logo working exactly as it always did — so the one
   JavaScript change this redesign needed has been reverted and no JavaScript is
   touched at all now. */
.sidebar{
  background:var(--sidebar-bg);
  border-right:0;
  box-shadow:none;
}
.sidebar-header{border-bottom:1px solid rgba(255,255,255,.10)}
.sidebar-header p{color:rgba(255,255,255,.46);font-weight:600}
.sidebar-nav a,.sidebar-nav .nav-item,.sidebar-nav button{
  border-radius:12px;margin:1px 8px;
  color:rgba(255,255,255,.72);
  transition:background .15s ease,color .15s ease;
}
.sidebar-nav a:hover{background:rgba(255,255,255,.07);color:#fff}
.sidebar-nav a.active,.sidebar-nav .nav-item.active{
  background:var(--primary-color);
  color:#fff;
  font-weight:700;
  box-shadow:none;
}
.sidebar-nav a .nav-icon,.sidebar-nav a svg{opacity:.85}
.sidebar-nav a.active .nav-icon,.sidebar-nav a.active svg{opacity:1}
/* Section labels between nav groups, if the markup carries them. */
.sidebar-nav .nav-section,.sidebar-nav h4,.sidebar-nav .nav-label{color:rgba(255,255,255,.38)}
.sidebar-footer{border-top:1px solid rgba(255,255,255,.10)}

/* ── Top bar ─────────────────────────────────────────────────────────────── */
.topbar{
  background:#FFFFFF;
  border-bottom:1px solid var(--border-color);
  box-shadow:none;
}

/* ── Cards and panels ────────────────────────────────────────────────────── */
.card,.modal,.auth-card,.profile-dropdown,.empty-state{
  background:var(--card-bg);
  border:1px solid var(--border-color);
  border-radius:var(--radius-lg);
  box-shadow:var(--shadow);
}
.modal{box-shadow:var(--shadow-lg)}
.card:hover{box-shadow:var(--shadow)}

/* ── Buttons ─────────────────────────────────────────────────────────────── */
.btn{
  border-radius:14px;
  font-weight:700;
  box-shadow:none;
  transition:background .15s ease,color .15s ease,filter .15s ease;
}
.btn:hover{filter:brightness(.97);box-shadow:none}
.btn-primary{background:var(--primary-color);color:#fff;border:0}
.btn-primary:hover{background:var(--primary-hover);color:#fff}
.btn-secondary,.btn-ghost,.btn-outline{
  background:var(--primary-light);
  color:var(--primary-hover);
  border:0;
}
.btn-secondary:hover,.btn-ghost:hover,.btn-outline:hover{background:#DEEAFD;color:var(--primary-hover)}
.btn-danger{background:var(--error-light);color:var(--error-color);border:0}
.btn-danger:hover{background:#FBDCE4;color:var(--error-color)}
.btn-success{background:var(--success-light);color:var(--success-color);border:0}
.btn-success:hover{background:#D8F0E4;color:var(--success-color)}
.btn-warning{background:var(--warning-light);color:var(--warning-color);border:0}
.btn-sm{border-radius:11px}
.btn-icon{border-radius:12px;background:var(--bg2);border:0;color:var(--text-secondary)}
.btn-icon:hover{background:var(--primary-light);color:var(--primary-hover)}

/* ── Form controls ───────────────────────────────────────────────────────── */
.form-control,input,select,textarea{
  border-radius:14px;
  border:1px solid var(--border-color);
  background:var(--input-bg);
  color:var(--text-primary);
}
.form-control:focus,input:focus,select:focus,textarea:focus{
  outline:0;
  border-color:var(--primary-color);
  background:#fff;
  box-shadow:0 0 0 3px var(--primary-light);
}
.form-control::placeholder{color:var(--text-muted)}
.form-group label,label{color:var(--text-secondary)}
.helper-text{color:var(--text-muted)}

/* ── Tables ──────────────────────────────────────────────────────────────── */
.table,.data-table{background:transparent}
/* Deep blue headers. They band the top of every table, which breaks up the
   white far more than a grey header did, and makes it obvious where one table
   ends and the next begins on a page that has several. */
.table th,.data-table th,.compact-table th{
  background:var(--primary-hover);
  color:#fff;
  border-bottom:0;
  font-weight:700;
  letter-spacing:.4px;
}
/* No rounding on the header cells themselves. The card already has
   overflow:hidden and a 22px radius, so it clips the blue band for us — a 16px
   corner on the cell inside a 22px corner on the card leaves a white notch
   between the two, which is exactly what it looked like. */
/* Sort arrows and any icon inside a header follow the white text. */
.table th *,.data-table th *,.compact-table th *{color:inherit}
.table td,.data-table td,.compact-table td{
  border-bottom:1px solid var(--border-color-faint);
  color:var(--text-primary);
}
.table tbody tr:hover,.data-table tbody tr:hover,.compact-table tbody tr:hover{background:var(--bg2)}
/* The scroll box around a table carried the card's own 22px rounding. Since the
   card already rounds and clips its corners, that second curve only showed up
   where the table meets the card's straight side — which is where the blue
   header picked up a stray rounded corner. The card does the rounding. */
.table-responsive{border-radius:0;overflow:auto}

/* ── Badges, pills, status ───────────────────────────────────────────────── */
.badge,.role-badge,.pill,.tag{
  border-radius:var(--radius-pill);
  font-weight:700;
  border:0;
}
.badge-success{background:var(--success-light);color:var(--success-color)}
.badge-warning{background:var(--warning-light);color:var(--warning-color)}
.badge-danger {background:var(--error-light);color:var(--error-color)}
.badge-info   {background:var(--info-light);color:var(--info-color)}
.pill-on      {background:var(--success-light);color:var(--success-color)}
.pill-off     {background:var(--bg2);color:var(--text-muted)}
.pill-warn    {background:var(--warning-light);color:var(--warning-color)}

/* Role chips: each role keeps its own colour, now as a pale wash. */
.role-superadmin   {background:var(--info-light);color:var(--info-color)}
.role-admin        {background:var(--primary-light);color:var(--primary-hover)}
.role-unit_head,
.role-supervisor   {background:var(--secondary-light);color:var(--secondary-color)}
.role-surveyor     {background:var(--success-light);color:var(--success-color)}
.role-hospital_staff{background:var(--primary-light);color:var(--primary-hover)}
.role-hr,
.role-business_head{background:var(--warning-light);color:var(--warning-color)}
.role-auditor,
.role-viewer,
.role-user         {background:var(--bg2);color:var(--text-secondary)}

/* ── Alerts ──────────────────────────────────────────────────────────────── */
.alert{border-radius:var(--border-radius);border:0;font-weight:600}
.alert-success{background:var(--success-light);color:var(--success-color)}
.alert-error  {background:var(--error-light);color:var(--error-color)}

/* ── Modals ──────────────────────────────────────────────────────────────── */
.modal-backdrop{background:rgba(23,28,43,.38)}
.modal-header{border-bottom:1px solid var(--border-color)}
.modal-close{background:var(--bg2);color:var(--text-secondary);border-radius:50%;border:0}
.modal-close:hover{background:var(--primary-light);color:var(--primary-hover)}

/* ── Toggles ─────────────────────────────────────────────────────────────── */
.toggle-slider{background:#CBD5E1}
.toggle-switch input:checked + .toggle-slider{background:var(--primary-color)}

/* ── Sign-in page ────────────────────────────────────────────────────────── */
/* The soft wash the whole direction is named for, used at its fullest here
   because this is the one screen with nothing else on it. */
.auth-container{
  background:
    radial-gradient(820px 460px at 14% -8%, #E3EDFD 0%, rgba(227,237,253,0) 62%),
    radial-gradient(700px 480px at 100% 4%, #EDE9FD 0%, rgba(237,233,253,0) 58%),
    var(--bg-color);
}
.auth-card{border-radius:26px;box-shadow:var(--shadow-lg)}

/* ── Odds and ends ───────────────────────────────────────────────────────── */
.section-title,.headline{color:var(--text-primary)}
.eyebrow{color:var(--primary-color)}
.text-secondary{color:var(--text-secondary)}
.status-dot-active{background:var(--success-color)}
.status-dot-inactive{background:var(--text-muted)}
.link-btn{color:var(--primary-color)}
.sidebar-overlay{background:rgba(23,28,43,.38)}

/* Scrollbars, which on a light ground otherwise stay dark and look broken. */
*::-webkit-scrollbar{width:11px;height:11px}
*::-webkit-scrollbar-track{background:var(--bg2)}
*::-webkit-scrollbar-thumb{background:#D3DBE8;border-radius:999px;border:3px solid var(--bg2)}
*::-webkit-scrollbar-thumb:hover{background:#BCC7D9}

/* ============================================================================
   PAGE CORRECTIONS
   ----------------------------------------------------------------------------
   A handful of pages hardcoded white text because the old system was dark. Those
   values are not tokens, so the swap above cannot reach them — on a white page
   they become white-on-white. Each one is corrected here rather than in the page
   itself, so no page file is edited and removing this stylesheet restores
   everything exactly.
   ========================================================================== */

/* ── Dashboard: the welcome band ── */
.dash-welcome{color:var(--text-primary)}
.dash-welcome-left p{color:var(--text-secondary)}
.dash-date{color:var(--text-muted)}
.dash-time{color:var(--text-primary)}
.dash-quick-link{
  background:var(--primary-light);
  border:1px solid transparent;
  color:var(--primary-hover);
  border-radius:11px;
}
.dash-quick-link:hover{background:#DEEAFD;color:var(--primary-hover)}

/* ── The sidebar footer carries its colour as an inline style, which no
      stylesheet can beat without !important. It was white at 30% opacity for a
      dark sidebar, so on a white one it is invisible. This is the only
      !important in the file and this is why. ── */
.sidebar-footer div{color:rgba(255,255,255,.40) !important}

/* ============================================================================
   TOKENS THAT WERE REFERENCED BUT NEVER DEFINED
   ----------------------------------------------------------------------------
   Found by listing every var(--x) used anywhere and subtracting the ones the
   stylesheets actually declare. All of these were already broken before this
   redesign — an undefined custom property resolves to nothing, so the rule was
   silently dropped. On the dark theme that happened to be survivable; on a white
   page a white badge on no background is invisible, which is how it surfaced.
   ========================================================================== */

:root{
  /* Used by the count badges on Users, Devices, Survey Data and Activity Log.
     They were white text on a background that never existed. */
  /* Flat, not a gradient. Survey Data puts two of these side by side — "Total: 0"
     and "Today: 0" — and the second one sets its colour inline. A gradient on one
     and a flat fill on the other made two identical badges look like two
     different things. */
  --primary-gradient:var(--primary-color);

  /* The timeline map legend: the line, and the three kinds of pin. These keep
     their meaning from the app — blue for travel, green start, amber stop,
     rose end — rather than being invented here. */
  --map-route:#1668E3;
  --map-start:#0F8A4D;
  --map-stop:#B45309;
  --map-end:#BE3455;

  /* style.css asks for this once, at line 1147. */
  --font-sans:var(--font-family);
}

/* ── Sign-in: the left panel becomes the brand band ──────────────────────────
   It was a dark panel carrying a white logo and white feature text. Turning the
   page white made all of that vanish. Rather than recolour six separate pieces
   of text, the panel itself becomes the phone app's blue gradient — which is
   where the white logo and white text belong, and makes the sign-in screen the
   one place the brand is at full strength. */
.login-brand{
  background:linear-gradient(135deg,var(--primary-hover) 0%,var(--primary-color) 100%);
  color:#fff;
}
.login-brand h1,.login-brand h2,.login-brand h3,
.login-brand strong,.login-brand b,.login-brand li,.login-brand span,
.login-brand .brand-tagline{color:#fff}
/* The headline and the line under it are DIVs, not a heading and a paragraph, so
   a list of tag selectors misses them entirely — found by reading the computed
   colour of every text node in the panel rather than by assuming the markup. */
.login-brand p,.login-brand small,
.login-brand .brand-subtitle{color:rgba(255,255,255,.82)}
.login-brand .brand-footer{color:rgba(255,255,255,.62)}

/* ── Control Panel: the allowed-app tiles ───────────────────────────────────
   Each tile is a white square holding an inline SVG drawn in currentColor,
   which was white for the dark theme — so on a white tile the glyph vanished
   and eight of them rendered as empty boxes. The tiles that still showed an
   icon were the ones with their own brand colour baked in (Gmail, Yahoo),
   which is what made it look like a loading failure rather than a colour
   problem. */
.app-icon{color:var(--primary-hover)}
.app-icon svg{stroke:currentColor}
.app-chip{border-radius:14px}
.app-chip-remove{background:var(--error-light);color:var(--error-color);border:0}

/* ── Action buttons in tables ────────────────────────────────────────────────
   Four buttons will not fit on one line in an Actions column however tight they
   are, so they wrap — the problem was that they wrapped RAGGEDLY. A row with
   three buttons sat on one line and a row with four dropped its last one to a
   second line hard against the right edge, so nothing lined up down the column.
   Making the cell a centred flex container with a real gap means both lines
   centre under the header and every row reads the same way. */
.actions-cell,.table-actions{
  display:flex;flex-wrap:wrap;gap:6px;
  justify-content:center;align-items:center;
}
/* Bare buttons sitting straight in a cell, with no wrapper to flex — the Shifts
   page does this — at least get breathing room between them. */
.data-table td > .btn + .btn,
.table td > .btn + .btn,
.compact-table td > .btn + .btn{margin-left:6px}

/* Tighter, so four fit on two tidy lines rather than three plus a stray. */
.data-table .btn-sm,.table .btn-sm,.compact-table .btn-sm{
  padding:6px 11px;font-size:12.5px;
}

/* ============================================================================
   ONE QUIET STYLE FOR EVERY SECONDARY ACTION
   ----------------------------------------------------------------------------
   A filter toolbar had four buttons in four different colours: Apply blue,
   Export GREEN, Report blue-tint, Clear PINK. Export is not a success and Clear
   is not a deletion — they are just two more filter actions — so the colour was
   telling the eye something untrue, and four colours in one row is what made the
   page look thrown together.

   The rule now: ONE solid blue button per group, for the thing that actually
   runs; everything beside it quiet and identical; red reserved for actions that
   destroy something, green for nothing at all.
   ========================================================================== */

/* `btn-success` is used for Export, Export Excel, Download Excel and All Data far
   more often than for anything that succeeds, so it joins the quiet group. */
.btn-success{background:var(--primary-light);color:var(--primary-hover);border:0}
.btn-success:hover{background:#DEEAFD;color:var(--primary-hover)}

/* `btn-danger` stays red where it belongs — Delete, Deactivate, Switch off — but
   inside a filter toolbar it is only ever "Clear", which destroys nothing. */
.filters-grid .btn-danger,
.filter-actions .btn-danger{background:var(--primary-light);color:var(--primary-hover)}
.filters-grid .btn-danger:hover,
.filter-actions .btn-danger:hover{background:#DEEAFD;color:var(--primary-hover)}

/* ── The small caps eyebrows above card titles ──────────────────────────────
   "— FIELD DATA", "— AUDIT TRAIL", "— HISTORY". They were a dark-theme flourish
   in a mono face at 10px; on a light card they read as leftover noise above the
   real heading. Kept, because they do say which part of the system you are in,
   but quietened so the heading is clearly the heading. */
.eyebrow{
  font-size:10.5px;letter-spacing:1.4px;text-transform:uppercase;
  color:var(--text-muted);font-weight:700;
}

/* ── Card headings ────────────────────────────────────────────────────────── */
.section-title,.card-header h2,.card-header h3{letter-spacing:-.2px}

/* ── Tabs ─────────────────────────────────────────────────────────────────── */
/* Control Panel's tab row: the active tab was marked only by colour. A rule
   under it says which one you are on without relying on hue. */
.tab-btn.active,.tabs a.active,.tab.active{
  color:var(--primary-hover);font-weight:700;
  box-shadow:inset 0 -2px 0 var(--primary-color);
}

/* ============================================================================
   DASHBOARD
   ----------------------------------------------------------------------------
   Every block on this page — the stats, the user activity, field operations,
   the recent submissions table — was transparent with no border and no radius.
   On the dark theme they were separated by faint light-on-dark hairlines that
   simply disappeared when the page turned white, so the whole page became text
   floating on grey with nothing holding it. It was the worst page in the set
   and this is why.
   ========================================================================== */

.dash-stats,.dash-user-status,.dash-ops,.dash-table-card{
  background:var(--card-bg);
  border:1px solid var(--border-color);
  border-radius:var(--radius-lg);
  box-shadow:var(--shadow);
  padding:18px 20px;
  margin-bottom:18px;
}
/* The table block holds a full-width table, so it keeps its corners but loses
   the side padding that would inset the header band from the card edge. */
.dash-table-card{padding:18px 0 0}
.dash-table-card > *:not(table){padding-left:20px;padding-right:20px}

/* The section headings sit ABOVE their card, not inside it. */
.dash-section-title{
  margin:4px 0 10px;
  font-size:11px;letter-spacing:1.4px;text-transform:uppercase;
  color:var(--text-muted);font-weight:700;
}

/* The welcome band's decorative glow was a radial wash built for a dark ground.
   On white it is a grey smudge that stops abruptly behind the clock. */
.dash-welcome::before{display:none}
.dash-welcome{border-bottom:1px solid var(--border-color);padding-bottom:22px;margin-bottom:22px}

/* THE COLOUR CLASSES WERE CROSSED. `.dash-stat-value.blue` was rendering purple
   and `.purple` was rendering blue, because each pointed at a token whose
   meaning changed underneath it. Names now match what you see. */
.dash-stat-value.blue{color:var(--primary-color)}
.dash-stat-value.purple{color:var(--info-color)}
.dash-stat-value.green{color:var(--success-color)}

/* The Recent Submissions table sets its own header background at
   `.dash-table-card .table thead th`, which outranks the shared rule — so it was
   the one table in the whole dashboard without the blue band, which is exactly
   the sort of near-miss that makes a set of pages feel unrelated. */
.dash-table-card .table thead th{
  background:var(--primary-hover);
  color:#fff;
  border-bottom:0;
}

/* ── Users: the toolbar sat outside any card ────────────────────────────────
   "System Users 37", the search box, the three filters and the two buttons all
   floated straight on the grey while the table beneath them was on white — so
   the page had a white block with a grey header strip above it, and every other
   page in the set puts its filters on a card. */
.users-toolbar{
  background:var(--card-bg);
  border:1px solid var(--border-color);
  border-radius:var(--radius-lg);
  box-shadow:var(--shadow);
  padding:14px 18px;
  margin-bottom:18px;
}

/* ── Control Panel ──────────────────────────────────────────────────────────
   `.card` carries no padding anywhere in this dashboard — each page supplies its
   own inner spacing — and this page's sections never did. Every label, field and
   button was therefore pressed flat against the card's left edge, and the
   headings ran straight into the first row of fields with nothing between them.
   It is the worst alignment in the set and it is one missing declaration. */
.cp-section{padding:22px 24px}

/* The sub-headings inside a panel were separated by a hairline that only existed
   in the dark theme, so "Exit & idle" sat directly on top of the field below it. */
.cp-sub{
  border-top:1px solid var(--border-color);
  padding-top:14px;
  margin-top:12px;
  margin-bottom:10px;
  /* The same caption style as everywhere else, and in the readable grey rather
     than the muted one. At 0.64rem in --text-muted these were the palest text
     on the page, and they are the only thing saying where one group of settings
     ends and the next begins. */
  font-family:var(--font-mono);
  font-size:var(--label-size);
  font-weight:var(--label-weight);
  letter-spacing:var(--label-track);
  color:var(--text-secondary);
}
.cp-section > .cp-sub:first-of-type{border-top:0;padding-top:0;margin-top:0}
/* The fields below carry a floating label that sits above the box, so the gap
   has to clear that label as well as the heading. */
.cp-head{margin-bottom:26px}

/* The tab row reads as tabs rather than as plain text. */
.cp-tabs{border-bottom:1px solid var(--border-color)}
.cp-tab{
  border:0;background:none;color:var(--text-secondary);
  font-weight:600;border-bottom:2px solid transparent;
}
.cp-tab:hover{color:var(--text-primary)}
.cp-tab.active{color:var(--primary-hover);font-weight:700;border-bottom-color:var(--primary-color)}

/* ── Analytics: the figures strip ───────────────────────────────────────────
   The seven numbers across the top — total surveys, members, average family
   size and the rest — sat in a bare grid on the page ground while the charts
   below them were all on cards. Same fault as the dashboard: on the dark theme
   a hairline held the strip together, and it went when the page turned white. */
.analytics-kpi-grid{
  background:var(--card-bg);
  border:1px solid var(--border-color);
  border-radius:var(--radius-lg);
  box-shadow:var(--shadow);
  padding:18px 20px;
  margin-bottom:18px;
}

/* ── Colour has to mean something ───────────────────────────────────────────
   Analytics put its seven figures in seven different colours — total surveys
   violet, members green, average family size blue, male rose, female amber,
   adults teal, children blue. None of that is a status; they are just counts of
   different things, and colouring each one differently teaches the eye to read
   meaning into a choice that carries none. It is the loudest colour mismatch
   left in the dashboard.

   The rule, applied here and on the dashboard's stat cards: a plain count is
   ink. Colour is kept for figures that genuinely carry a state — online and
   deactivated, late and absent — where it is doing real work. */
.analytics-kpi .kpi-val,
.analytics-kpi:nth-child(1) .kpi-val,
.analytics-kpi:nth-child(2) .kpi-val,
.analytics-kpi:nth-child(3) .kpi-val,
.analytics-kpi:nth-child(4) .kpi-val,
.analytics-kpi:nth-child(5) .kpi-val,
.analytics-kpi:nth-child(6) .kpi-val,
.analytics-kpi:nth-child(7) .kpi-val{color:var(--text-primary)}

.dash-stat-value,
.dash-stat-value.blue,
.dash-stat-value.purple,
.dash-stat-value.green{color:var(--text-primary)}

/* The figures that DO carry a state keep their colour, because there it is the
   fastest way to read the line. */
.dash-user-val.online{color:var(--success-color)}
.dash-user-val.deactivated{color:var(--error-color)}
.dash-user-val.offline{color:var(--text-secondary)}

/* ══════════════════════════════════════════════════════════════════════════
   CONSISTENCY PASS
   Four faults reported from the screen, each fixed here in the theme layer so
   no page's own markup or JavaScript has to change.
   ══════════════════════════════════════════════════════════════════════════ */

/* ── 1. One font, everywhere ─────────────────────────────────────────────────
   Buttons and form controls do NOT inherit font-family from their parent —
   browsers give them the system UI face unless told otherwise. Most controls
   here say `font-family: inherit` by hand, but the modal close button (the ×)
   never did, so seven of them on every page with a modal were rendering in
   Arial while the rest of the page was in Roboto. Declaring it once for every
   control closes that gap and any future one. */
button,input,select,textarea,.btn,.modal-close{font-family:var(--font-family)}

/* ── 2. The filter bar sits INSIDE its card ──────────────────────────────────
   `.compact-card` — the padded card every filter bar on this dashboard is
   wrapped in — was only ever defined inside survey-data.html's own <style>
   block. The other seven pages that use the class got nothing, and since
   `.card` carries no padding of its own, their filter fields ran hard against
   the card's border: the last control on Hospital Attendance looked cut off by
   the card edge, because it was touching it. Defined here once, for all of
   them. */
.card.compact-card{padding:20px}
.card.compact-card .section-title{font-size:.95rem;margin-bottom:14px}
.card.compact-card .section-icon{width:30px;height:30px;font-size:.9rem}

/* ── 3. Every filter box is the Users page's filter box ──────────────────────
   Two different filter languages had grown up side by side. The Users page
   uses a plain inline toolbar: a compact field with a 2px border and an 8px
   radius, nothing else. Every other page uses `.filters-grid`, whose fields
   were taller, rounder, thinner-bordered, and carried a label notched into the
   top border — so moving between two pages that do the same job looked like
   moving between two products.

   The Users control wins, because it is the quieter of the two. The label
   stays, but as a caption above the field rather than a notch cut into it:
   Date From and Date To are the only things on screen that say what they are,
   so dropping labels to match Users exactly would cost real meaning. */
.filters-grid .form-group > label,
.log-filters .form-group > label,
.analytics-filters .form-group > label{
  position:static;
  background:none;
  padding:0 0 6px 2px;
  font-size:.68rem;
  color:var(--text-secondary);
  letter-spacing:.07em;
}
.filters-grid .form-group .form-control,
.log-filters .form-group .form-control,
.analytics-filters .form-group .form-control{
  padding:8px 14px;
  border:2px solid var(--border-color);
  border-radius:var(--radius-xs);
  font-size:.82rem;
  background:var(--input-bg);
}
.filters-grid .form-group .form-control:focus,
.log-filters .form-group .form-control:focus,
.analytics-filters .form-group .form-control:focus{
  border-color:var(--primary-color);
  box-shadow:0 0 0 3px var(--primary-light);
  outline:none;
}
/* The buttons at the end of the row line up with the bottom of the fields,
   not with the top of their labels. */
.filters-grid .filter-actions,
.log-filters .filter-actions,
.analytics-filters .filter-actions{justify-content:flex-end}

/* ── 4. The sidebar icons are the same colour as their labels ────────────────
   `.nav-icon` takes `--text-secondary`, which was a pale grey on the old dark
   theme and is a dark slate now. On the navy sidebar that put a nearly black
   icon next to a white label in the same row — the single most visible colour
   mismatch left. The icon now simply inherits whatever colour its link is, so
   the two can never drift apart again.

   The active row needs its own answer too: style.css gives the active chip
   `--primary-color`, which used to stand out against a dark sidebar but now
   sits on an active row that is ITSELF primary-color, making the chip vanish.
   A white wash reads correctly on both. */
.sidebar-nav .nav-icon{
  color:inherit;
  background:rgba(255,255,255,.08);
}
.sidebar-nav a:hover .nav-icon{background:rgba(255,255,255,.15)}
.sidebar-nav a.active .nav-icon{
  background:rgba(255,255,255,.20);
  color:#fff;
}

/* ── 5. Buttons stop breaking their own labels ───────────────────────────────
   A button in a tight column wraps its text mid-label and grows to two lines —
   "+ Add group" on the Shifts page was doing exactly that, and the Edit /
   Switch off pair in each table row stacked at two different widths, which is
   what reads as buttons sitting crooked or spilling out. A label never wraps;
   if a row of buttons will not fit it breaks BETWEEN buttons instead. Stacked
   row actions share one width so the pair reads as a pair. */
.btn,button{white-space:nowrap}
td .btn-sm{min-width:86px}

/* A row's action buttons are laid out, not left to the whitespace between the
   tags. `.table-actions` was nowrap, which in a narrow column pushed the second
   button out of the cell; wrapping lets the pair stack, and because it is a
   flex container they stack perfectly aligned instead of each being centred on
   its own line a few pixels apart. */
.table-actions{flex-wrap:wrap;justify-content:center;row-gap:6px}

/* The map on Timeline and Field Activity is Leaflet's, and Leaflet ships its
   own font stack — its zoom buttons, attribution line and popups were set in
   Helvetica while the page around them was in Roboto. The map is part of the
   page, so it uses the page's typeface. */
.leaflet-container,.leaflet-control,.leaflet-popup-content,.leaflet-control-attribution{
  font-family:var(--font-family);
}

/* A date box is the one filter that cannot announce itself: it is pre-filled,
   so its placeholder never shows, and two boxes both reading 22-10-2026 give no
   clue which end of the range is which. The word goes BESIDE the box rather
   than above it, so the row keeps one height and nothing is said twice. */
.field-inline{display:flex;align-items:center;gap:8px;min-width:0}
.field-inline .fi-label{
  flex:0 0 auto;
  font-family:var(--font-mono);
  font-size:.68rem;
  text-transform:uppercase;
  letter-spacing:.07em;
  color:var(--text-secondary);
}
.field-inline .form-control{min-width:0}
.form-group:focus-within .field-inline .fi-label{color:var(--primary-color)}

/* A browser gives a select, a date box and a text box three slightly different
   intrinsic heights, so a filter row built from all three came out 2px ragged.
   One height for every filter control, on every page including Users, so the
   row reads as a single object. */
.filters-grid .form-group .form-control,
.log-filters .form-group .form-control,
.analytics-filters .form-group .form-control,
.users-search input,
.users-search select{height:36px}

/* ── The filter row flows, it is not a fixed grid ────────────────────────────
   Each page laid its filters out as equal grid columns, so every field got the
   same width whether it held "All" or a date. Once the captions came off and
   the From / To moved beside the date boxes, that equal width was too narrow
   for a date and it clipped — 23-09-2026 showed as 23-09-202.

   The Users page never had this problem because its filter row is a plain row
   of controls that each take the width they need and wrap when they run out.
   Every filter row now works the same way. */
.card .filters-grid,
.card .log-filters,
.card .analytics-filters{
  display:flex;
  flex-wrap:wrap;
  align-items:flex-end;
  gap:12px;
}
/* flex-basis 0, not 165px: the fields divide whatever is left AFTER the button
   group has taken the width it needs, instead of each growing to its own 280px
   cap and pushing the buttons onto a second row. That is what put Apply / Export
   / Report / Clear on a line of their own even on a 1500px screen — five fields
   at that cap come to 1400px before the buttons are measured at all.
   min-width is now the only thing that can force a wrap, and 160px is what it
   takes for the longest label any of these fields shows -- "Any medical issue"
   needs 160px and was being clipped to "Any medical is" at 132. Every filter
   row with five or six fields therefore sits on one line, and the two that are
   genuinely crowded -- Analytics with nine and Survey Data with sixteen -- take
   two tidy rows rather than clipping their own labels. */
/* A hidden filter field must actually disappear. Every page sets
   `.filters-grid .form-group{display:flex}`, and an explicit display beats the
   browser's own [hidden] rule, so marking one hidden left it sitting there at full
   width. This is the same two-class weight, declared later, so it wins.
   Attendance uses it for the From / To boxes, which appear only for a custom range. */
.form-group[hidden]{display:none}

.card .filters-grid > .form-group,
.card .log-filters > .form-group,
.card .analytics-filters > .form-group{
  flex:1 1 0;
  min-width:160px;
  max-width:300px;
}
/* The buttons take exactly the room they need and no more. */
.card .filters-grid > .filter-actions,
.card .log-filters > .filter-actions,
.card .analytics-filters > .filter-actions{
  flex:0 0 auto;
  max-width:none;
}

/* ══════════════════════════════════════════════════════════════════════════
   DATE FIELDS — the calendar glyph is white on a white field
   ══════════════════════════════════════════════════════════════════════════
   style.css sets `color-scheme: dark` on every date and time input, and says why:
   on the old dark theme Chromium drew a near-black glyph that disappeared into
   the input, and `color-scheme: dark` turned it white. The inputs are white now,
   so that same white glyph is invisible again — the fix outlived the theme it was
   fixing, which is why it reads as though the icon were simply missing.
   `light` gives a dark glyph on the white field and a light picker panel to match.
   Still no `filter: invert()`: that is the double-correction style.css warns
   about, and it would land us straight back on white. */
input[type="date"],
input[type="time"],
input[type="datetime-local"],
input[type="month"],
input[type="week"]{
  color-scheme:light;
}
/* Full strength — the glyph is small and sits on white, so 0.85 read as grey. */
input[type="date"]::-webkit-calendar-picker-indicator,
input[type="time"]::-webkit-calendar-picker-indicator,
input[type="datetime-local"]::-webkit-calendar-picker-indicator,
input[type="month"]::-webkit-calendar-picker-indicator,
input[type="week"]::-webkit-calendar-picker-indicator{
  opacity:1;
}

/* ══════════════════════════════════════════════════════════════════════════
   ONE BUTTON COLOUR
   ══════════════════════════════════════════════════════════════════════════
   A row of actions was reading as four different things: Edit in blue next to
   Switch off in pink, Export in one colour and Clear in another. None of that
   difference was telling you anything — Switch off is as reversible as Edit —
   so the colour was decoration pretending to be meaning.

   Every button on a page is now the same blue. There is one exception, and it
   is deliberate: the final button inside a confirmation box, the click that
   permanently destroys something, stays red. That is the one place where the
   colour is the warning, and the only place a red button can still be met. */
.btn-danger,
.btn-warning,
.btn-success{
  background:var(--primary-light);
  color:var(--primary-hover);
  border:0;
}
.btn-danger:hover,
.btn-warning:hover,
.btn-success:hover{background:#DEEAFD;color:var(--primary-hover)}

/* The point of no return keeps its red. */
.modal .btn-danger,
.modal-backdrop .btn-danger{
  background:var(--error-color);
  color:#fff;
}
.modal .btn-danger:hover,
.modal-backdrop .btn-danger:hover{background:#A82C49;color:#fff}

/* A .btn with no variant at all used to fall back to a 6%-white wash — correct
   on the old dark theme, invisible on a white card. It now reads as the same
   quiet button as everything else. */
.btn:not([class*="btn-primary"]):not([class*="btn-danger"]):not([class*="btn-success"]):not([class*="btn-warning"]):not([class*="btn-secondary"]):not([class*="btn-ghost"]):not([class*="btn-outline"]):not([class*="btn-icon"]){
  background:var(--primary-light);
  color:var(--primary-hover);
  border:0;
}

/* ── The table header is square ──────────────────────────────────────────────
   `.table th:first-child` and `:last-child` carried a 16px top corner. The card
   around them is rounded to 22px and already clips its own corners, so the
   header's smaller curve sat *inside* the card's larger one and left a pale
   wedge in each corner. Worse, on most pages the table is not at the top of the
   card at all — a heading row sits above it — so the rounded top made the deep
   blue header look like a tab that had come loose. Square corners; the card
   does the rounding. */
.table th:first-child,
.table th:last-child,
.data-table th:first-child,
.data-table th:last-child,
.compact-table th:first-child,
.compact-table th:last-child{border-radius:0}

/* ── Two cards in a column are two cards ─────────────────────────────────────
   `.card + .card` added a hairline across the top of every card that followed
   another one, plus 28px of padding inside it. That made sense when a "card"
   was a flat section divided from the next by a rule. These are real cards now
   with their own border and their own gap, so the rule drew a second line just
   inside the first and the padding pushed the heading down — which is why the
   Register strip stood taller than Daily Activity's, where an inline padding:0
   happened to cancel it. Every table's heading strip is now the same height. */
/* The card keeps its OWN border on every side - it is a card. What goes is the
   28px of padding it inherited purely for following another card.

   The :not() list is load-bearing. A card that sets its own padding must keep
   it: without the exclusions this rule stripped the top padding from the FIRST
   chart card in each row on Analytics, so its title sat 20px lower than the two
   beside it. Any future padded card variant belongs in this list. */
.card + .card{border-top:1px solid var(--border-color)}
.card + .card:not(.compact-card):not(.cp-section):not(.chart-card):not(.dash-table-card){padding-top:0}

/* …but a card that sets its OWN padding keeps it. The reset above exists only to
   cancel the 28px a card inherited for the crime of following another one; it must
   not strip the padding a padded card asked for. Re-asserted here, after it. */
.card.cp-section{padding:22px 24px}
.card.compact-card{padding:20px}
.card.chart-card{padding:20px}

/* ══════════════════════════════════════════════════════════════════════════
   POP-UPS
   ══════════════════════════════════════════════════════════════════════════
   Two faults, and the second one is the serious one.

   The header was plain white text on a white box, so a pop-up opened with no
   edge of its own and read as part of the page behind it. It now carries the
   same deep blue as the table headings, which is the one place in this
   dashboard that colour already means "this is a heading".

   And a tall pop-up — Add shift is the worst — was silently CUT OFF. The box
   scrolls, but `.modal::-webkit-scrollbar { display: none }` hid every sign of
   it, so the Save button simply was not on screen and nothing suggested there
   was more below. The header now sticks to the top while the body scrolls
   under it, the footer sticks to the bottom so Save is always reachable, and
   the scrollbar is visible again. */
.modal{padding:0;max-height:88vh;scrollbar-width:thin}
.modal::-webkit-scrollbar{display:block;width:10px}
.modal::-webkit-scrollbar-thumb{background:var(--border-color);border-radius:8px}
.modal::-webkit-scrollbar-track{background:transparent}

.modal > .modal-header{
  position:sticky;top:0;z-index:6;
  background:var(--primary-hover);
  border-bottom:0;
  padding:15px 24px;
  gap:16px;
  align-items:flex-start;
}
.modal > .modal-header h3{
  color:#fff;font-weight:700;font-size:1.02rem;font-family:var(--font-family);
}
/* A subtitle under the title — who and when — sits on the blue too. */
.modal > .modal-header p{color:rgba(255,255,255,.74);margin:3px 0 0}
.modal .modal-close{
  background:rgba(255,255,255,.18);color:#fff;flex:0 0 auto;
}
.modal .modal-close:hover{background:rgba(255,255,255,.32);color:#fff}

/* The scrolling middle. The element in the selector is load-bearing: style.css
   sets `.modal > div:not(.modal-header){padding:0 28px}`, which carries an element
   of its own and so outranks a plain two-class rule - the body came out with no
   top padding and its first heading sat against the blue. */
.modal > div.modal-body{padding:22px 24px}

/* The footer rides the bottom edge, so Save never scrolls out of reach. */
.modal > .modal-foot{
  position:sticky;bottom:0;z-index:5;
  display:flex;align-items:center;justify-content:flex-end;gap:10px;
  padding:14px 24px;
  background:var(--primary-light);
  border-top:1px solid var(--border-color);
}
.modal > .modal-foot .btn{min-width:116px}
.modal > .modal-foot .btn-secondary{background:#FFFFFF;color:var(--primary-hover)}
.modal > .modal-foot .btn-secondary:hover{background:#F2F7FF}

/* Where a pop-up marks its own body, the scrolling belongs to the body alone.
   With the whole box scrolling, the scrollbar ran the full height and showed as
   a strip beside the blue header; now it starts below the header and stops above
   the footer, which is where the scrolling content actually is. The header and
   footer are held by the flex column instead of by `position: sticky`. */
.modal:has(> .modal-body){
  display:flex;
  flex-direction:column;
  overflow:hidden;
}
.modal:has(> .modal-body) > .modal-header,
.modal:has(> .modal-body) > .modal-foot{
  position:static;
  flex:0 0 auto;
}
.modal:has(> .modal-body) > .modal-body{
  flex:1 1 auto;
  min-height:0;
  overflow-y:auto;
  scrollbar-width:thin;
}
.modal-body::-webkit-scrollbar{width:10px}
.modal-body::-webkit-scrollbar-thumb{background:var(--border-color);border-radius:8px}
.modal-body::-webkit-scrollbar-track{background:transparent}

/* ══════════════════════════════════════════════════════════════════════════
   ONE CONTROL SYSTEM
   ══════════════════════════════════════════════════════════════════════════
   Measured across the dashboard, a control could be any of SIX heights — 31,
   32, 36, 41, 43 or 45px — and any of THREE corner radii — 8, 11, 12 or 14px.
   A button sat 5px shorter than the field beside it in every filter bar, and a
   41px button inside a 42px footer bar touched the modal's rounded corner,
   which is what made the Save buttons look wedged into the corner.

   Two heights from here on, which is what a control system needs: 40px for a
   field or a button in a form, 32px for a table action or an icon button, and
   36px in the compact filter rows where fields and buttons now match exactly.
   One radius for everything you can type in or click. */
:root{
  --control-h:40px;
  --control-h-sm:32px;
  --control-h-filter:36px;
  --control-radius:10px;
}

/* ── Buttons ── */
.btn{
  height:var(--control-h);
  min-height:var(--control-h);
  padding:0 20px;
  border-radius:var(--control-radius);
  font-size:0.875rem;
  font-weight:600;
  line-height:1;
  letter-spacing:0;
}
.btn-sm{
  height:var(--control-h-sm);
  min-height:var(--control-h-sm);
  padding:0 14px;
  border-radius:var(--control-radius);
  font-size:0.8rem;
}
.btn-icon{
  width:var(--control-h-sm);
  height:var(--control-h-sm);
  min-height:var(--control-h-sm);
  padding:0;
  border-radius:var(--control-radius);
}
.btn-block{width:100%}

/* ── Fields ── */
.form-control{
  height:var(--control-h);
  border-radius:var(--control-radius);
  padding:0 14px;
  font-size:0.875rem;
}
textarea.form-control{height:auto;min-height:90px;padding:11px 14px;line-height:1.55}
select.form-control{padding-right:34px}

/* ── Filter rows: field and button are the same height, always ── */
.filters-grid .form-group .form-control,
.log-filters .form-group .form-control,
.analytics-filters .form-group .form-control,
.users-search input,
.users-search select{height:var(--control-h-filter);border-radius:var(--control-radius)}
.filters-grid .btn,
.log-filters .btn,
.analytics-filters .btn,
.users-toolbar .btn{
  height:var(--control-h-filter);
  min-height:var(--control-h-filter);
  font-size:0.8rem;
  padding:0 16px;
}

/* ── The pop-up footer gets room to breathe ───────────────────────────────
   `.modal > div:not(.modal-header){padding:0 28px}` in style.css carries an
   element selector, so it outranked a two-class rule and the footer came out
   42px tall with 41px buttons inside it — the buttons filled it edge to edge
   and the last one ran into the modal's 22px corner. The element is in this
   selector for exactly that reason. */
.modal > div.modal-foot{padding:16px 24px}
.modal > div.modal-foot .btn{min-width:120px}

/* Some pages put the filter buttons straight into the actions group and others
   wrap them in a div of their own. Where they are direct children they inherit
   the group's stretch and come out 38px while the fields beside them are 36 —
   the kind of 2px difference that makes a row look hand-placed. They are
   centred and pinned to the filter height either way. */
.filters-grid .filter-actions,
.log-filters .filter-actions,
.analytics-filters .filter-actions{
  flex-direction:row;
  align-items:center;
  gap:8px;
}
.filters-grid .filter-actions .btn,
.log-filters .filter-actions .btn,
.analytics-filters .filter-actions .btn,
.filters-grid .filter-actions > div > .btn,
.log-filters .filter-actions > div > .btn{
  height:var(--control-h-filter);
  min-height:var(--control-h-filter);
  align-self:center;
}

/* A pair of icon buttons is 70px wide and always fits, so it must not wrap the
   way a pair of word buttons does — wrapping stacked the pencil above the power
   symbol and made those rows 29px taller than the rest. */
.table-actions:has(.btn-icon){flex-wrap:nowrap}
.table-actions .btn-icon{flex:0 0 auto}

/* A table fills the card it sits in. Once columns were dropped and nothing
   wrapped, several tables were narrower than their card and left a wide white
   margin down the right-hand side of every row. */
.data-table,.compact-table,.table{width:100%}

/* ── Icon buttons you can actually see ───────────────────────────────────────
   Three things were wrong at once. `.table-actions .btn-icon` set width 28px
   while the control system set height 32px, so every one of them was a squashed
   rectangle rather than a square. The glyph inside was 14px in a 32px button —
   less than half of it — drawn in a mid grey on a fill one shade off white, so a
   row of five read as five faint smudges rather than five buttons.

   Square, a bigger glyph, a fill that separates from the row, and a hover that
   clearly belongs to the thing under the pointer. */
.table-actions .btn-icon,
td .btn-icon{
  width:var(--control-h-sm);
  height:var(--control-h-sm);
  flex:0 0 auto;
  background:var(--primary-light);
  color:var(--primary-hover);
  border:1px solid transparent;
  transition:background .14s ease,color .14s ease,border-color .14s ease;
}
.table-actions .btn-icon svg,
td .btn-icon svg{width:17px;height:17px}
.table-actions .btn-icon:hover,
td .btn-icon:hover{
  background:var(--primary-color);
  color:#fff;
  border-color:var(--primary-color);
}
.table-actions .btn-icon:focus-visible,
td .btn-icon:focus-visible{
  outline:none;
  border-color:var(--primary-color);
  box-shadow:0 0 0 3px var(--primary-light);
}
/* Enough room between them that each is its own target. */
.table-actions{gap:6px}

/* ══════════════════════════════════════════════════════════════════════════
   ONE CAPTION STYLE
   ══════════════════════════════════════════════════════════════════════════
   The small mono uppercase caption — the thing that sits above a field, beside
   a date box, or down the left of a details panel — had been hand-written at a
   different size nearly every time it was needed: 0.62, 0.63, 0.66 and 0.68rem,
   at weight 400, 500 and 700, with three different letter-spacings. They are
   all the same thing and now they all look the same.

   The values are the form label's, because that is the one most of the
   dashboard already used. */
:root{
  --label-size:.66rem;
  --label-weight:500;
  --label-track:.08em;
}
.field-inline .fi-label,
.fx-now .lbl,
.sv-list dt,
.dd-label{
  font-family:var(--font-mono);
  font-size:var(--label-size);
  font-weight:var(--label-weight);
  letter-spacing:var(--label-track);
  text-transform:uppercase;
  color:var(--text-secondary);
}

/* ══════════════════════════════════════════════════════════════════════════
   THE DASHBOARD GETS ITS BLUE BACK
   ══════════════════════════════════════════════════════════════════════════
   The whole page was white on pale grey with one blue band at the top of the
   survey table, so the first thing you see every morning had nothing to look at.

   The greeting band is the natural place to put the colour, and it was built to
   carry it: its heading, its clock, its paragraph and its quick links were all
   written in white, for a dark panel, and somewhere along the way the panel's
   background was set to transparent and every one of those colours had to be
   turned dark to stay readable. This puts the panel back. */
.dash-welcome{
  background:linear-gradient(135deg,var(--sidebar-bg) 0%,var(--primary-hover) 58%,var(--primary-color) 100%);
  border:0;
  border-bottom:0;
  border-radius:var(--radius-lg);
  padding:24px 28px;
  margin-bottom:22px;
  color:#fff;
  box-shadow:0 10px 28px rgba(11,36,71,.18);
  overflow:hidden;
}
/* A soft highlight in the top-right corner, so the panel has some depth rather
   than reading as a flat rectangle of colour. */
.dash-welcome::before{
  display:block;
  content:'';
  position:absolute;
  inset:0;
  background-image:radial-gradient(ellipse 520px 320px at 88% -20%,rgba(255,255,255,.16),transparent 62%);
  pointer-events:none;
}
.dash-welcome h2,
.dash-welcome .dash-time{color:#fff}
.dash-welcome h2 em{color:#9CC6FF;font-style:italic}
.dash-welcome-left p{color:rgba(255,255,255,.76)}
.dash-date{color:rgba(255,255,255,.58)}

/* The quick links are chips on the panel again, not pale buttons on white. */
.dash-quick-link{
  background:rgba(255,255,255,.13);
  border:1px solid rgba(255,255,255,.18);
  color:#fff;
  border-radius:10px;
}
.dash-quick-link:hover{background:rgba(255,255,255,.26);color:#fff}
.dash-quick-link svg{color:inherit}

/* The small headings between the strips — USER ACTIVITY, FIELD OPERATIONS —
   carry the brand blue, so the colour runs down the page instead of stopping
   at the panel. */
.dash-section-title{color:var(--primary-hover)}

/* One blue for the three figures at the top. They were violet, green and blue,
   which taught the eye to read a meaning that was not there — they are all just
   counts. */
.dash-stat-icon.blue,
.dash-stat-icon.green,
.dash-stat-icon.purple{
  background:var(--primary-light);
  color:var(--primary-color);
}

/* The small label above the greeting takes its colour from the light-page rule,
   which is the brand blue — near-invisible now that it sits on a blue panel. */
.dash-welcome .eyebrow{color:rgba(255,255,255,.62)}
.dash-welcome .eyebrow::before{background:rgba(255,255,255,.38)}

/* ══════════════════════════════════════════════════════════════════════════
   THE THREE FIGURE STRIPS
   ══════════════════════════════════════════════════════════════════════════
   Below the greeting the page was three near-identical white slabs, and the
   type inside them did not agree with itself. The same caption — the small
   uppercase word under each figure — was written four different ways:

     Surveys Today     sans   0.72rem  weight 700   0.8px
     Enrolled Devices  MONO   0.62rem  weight 400   0.08em
     Online (24h)      sans   0.68rem  weight 600   0.5px
     User Activity     sans   0.78rem  weight 700   1.2px

   They are all the same kind of label, so they all use the one caption style
   now. The section headings above the cards stay a step larger and blue,
   because they are a level up, not the same thing. */
.dash-stat-label,
.dash-ops-lbl,
.dash-user-lbl{
  font-family:var(--font-mono);
  font-size:var(--label-size);
  font-weight:var(--label-weight);
  letter-spacing:var(--label-track);
  text-transform:uppercase;
  color:var(--text-secondary);
  margin-top:6px;
  line-height:1.3;
}
.dash-stat-label{margin-top:0;margin-bottom:5px}

/* Figures line up digit under digit, and there are two sizes, not four: the
   headline row, and the two detail rows beneath it. */
.dash-stat-value,.dash-ops-val,.dash-user-val{font-variant-numeric:tabular-nums}
.dash-stat-value{font-size:1.9rem}
.dash-ops-val,.dash-user-val{font-size:1.45rem}

/* The note under a figure stays on ONE line. "12 apps allowed · tracking
   follows check-in/out" wrapped to two and dragged the whole Field Operations
   row out of line with itself; the full text is on hover. */
.dash-ops-sub{
  font-size:.7rem;
  color:var(--text-muted);
  margin-top:4px;
  white-space:nowrap;
  overflow:hidden;
  text-overflow:ellipsis;
  line-height:1.4;
}
/* Every cell in a strip starts at the same height, so a cell with a note and a
   cell without still line up. */
.dash-ops-card,.dash-stat-card,.dash-user-card{min-width:0}

/* A blue edge along the top of each strip. It gives the three slabs a defined
   top instead of floating as white rectangles, and carries the greeting
   panel's colour down the page. */
.dash-stats,.dash-user-status,.dash-ops{
  position:relative;
  overflow:hidden;
}
.dash-stats::after,.dash-user-status::after,.dash-ops::after{
  content:'';
  position:absolute;
  top:0;left:0;right:0;
  height:3px;
  background:linear-gradient(90deg,var(--primary-hover),var(--primary-color));
}

/* The dividers between figures pick up a trace of the same blue rather than
   being a flat grey. */
.dash-stat-card:not(:first-child),
.dash-ops-card:not(:first-child),
.dash-user-card:not(:first-child){border-left-color:rgba(22,104,227,.14)}

/* Every note in the Field Operations row sits on the same bottom line, whether
   or not the label above it wrapped. "Executives Tracking" takes two lines
   where the others take one, which was pushing its note down and leaving the
   row looking hand-placed. */
.dash-ops-card{display:flex;flex-direction:column;height:100%}
.dash-ops-sub{margin-top:auto;padding-top:4px}

/* The shift end time is a clock reading, not a count, so it is set a step
   smaller — but through a class, at a size that belongs to the scale, rather
   than an inline value of its own. */
.dash-ops-val-time{font-size:1.1rem;letter-spacing:-.2px}

/* ══════════════════════════════════════════════════════════════════════════
   THE DAY'S FIGURES, AS TILES
   ══════════════════════════════════════════════════════════════════════════
   Twelve tiles, six across. Each figure gets its own small card instead of
   being one entry in a long strip, which is both easier to scan and far less
   tall than the three stacked cards with headings between them that this
   replaced.

   The coloured edge is NOT decoration: it appears only on the two figures that
   carry a state — somebody being online, somebody being deactivated. The rest
   are plain counts and take the brand blue. */
.tiles{
  display:grid;
  grid-template-columns:repeat(6,minmax(0,1fr));
  gap:12px;
  margin-bottom:18px;
}
.tile{
  position:relative;
  min-width:0;
  background:var(--card-bg);
  border:1px solid var(--border-color);
  border-radius:14px;
  box-shadow:var(--shadow);
  padding:14px 16px;
  overflow:hidden;
}
.tile::before{
  content:'';
  position:absolute;left:0;top:0;bottom:0;width:3px;
  background:var(--primary-color);
}
.tile.is-good::before{background:var(--success-color)}
.tile.is-bad::before{background:var(--error-color)}

.tile-n{
  font-size:1.5rem;
  font-weight:800;
  line-height:1.1;
  letter-spacing:-.5px;
  color:var(--text-primary);
  font-variant-numeric:tabular-nums;
}
/* A clock reading, not a count, so it sits a step smaller. */
.tile-n-time{font-size:1.1rem;letter-spacing:-.2px}

.tile-l{
  margin-top:4px;
  font-family:var(--font-mono);
  font-size:var(--label-size);
  font-weight:var(--label-weight);
  letter-spacing:var(--label-track);
  text-transform:uppercase;
  color:var(--text-secondary);
  white-space:nowrap;
  overflow:hidden;
  text-overflow:ellipsis;
}
/* The note under a figure stays on one line; the full text is on hover. */
.tile-s{
  margin-top:3px;
  font-size:.68rem;
  color:var(--text-muted);
  white-space:nowrap;
  overflow:hidden;
  text-overflow:ellipsis;
  line-height:1.35;
}
.tile-s:empty{display:none}

@media (max-width:1240px){.tiles{grid-template-columns:repeat(4,minmax(0,1fr))}}
@media (max-width:900px){.tiles{grid-template-columns:repeat(3,minmax(0,1fr))}}
@media (max-width:620px){.tiles{grid-template-columns:repeat(2,minmax(0,1fr))}}

/* ══════════════════════════════════════════════════════════════════════════
   ANALYTICS
   ══════════════════════════════════════════════════════════════════════════
   The seven figures across the top were one flat white band, the same problem
   the Dashboard had. They are tiles now, exactly like the Dashboard's, so the
   two pages read as the same product. None of these seven carries a state —
   they are all plain counts — so they all take the brand blue. */
.analytics-kpi-grid{
  background:none;
  border:0;
  box-shadow:none;
  padding:0;
  display:grid;
  grid-template-columns:repeat(7,minmax(0,1fr));
  gap:12px;
  margin-bottom:18px;
}
.analytics-kpi{
  position:relative;
  min-width:0;
  background:var(--card-bg);
  border:1px solid var(--border-color);
  border-left:0;
  border-radius:14px;
  box-shadow:var(--shadow);
  padding:14px 16px;
  overflow:hidden;
  text-align:left;
}
.analytics-kpi::before{
  content:'';
  position:absolute;left:0;top:0;bottom:0;width:3px;
  background:var(--primary-color);
}
.analytics-kpi .kpi-val{
  font-size:1.5rem;
  font-weight:800;
  line-height:1.1;
  letter-spacing:-.5px;
  font-variant-numeric:tabular-nums;
}
.analytics-kpi .kpi-lbl{
  margin-top:4px;
  font-family:var(--font-mono);
  font-size:var(--label-size);
  font-weight:var(--label-weight);
  letter-spacing:var(--label-track);
  text-transform:uppercase;
  color:var(--text-secondary);
  white-space:nowrap;
  overflow:hidden;
  text-overflow:ellipsis;
}

/* The single letter in a coloured square beside each chart title — G, A, S, D,
   T — is the first letter of the words next to it, in five different colours.
   It tells you nothing the title does not, and it was the last place on the
   dashboard where colour was handed out at random. */
.chart-card .chart-title .chart-icon{display:none}
.chart-card .chart-title{
  font-size:.92rem;
  font-weight:700;
  gap:0;
  padding-bottom:12px;
  margin-bottom:14px;
  border-bottom:1px solid var(--border-color-faint);
}

@media (max-width:1400px){.analytics-kpi-grid{grid-template-columns:repeat(4,minmax(0,1fr))}}
@media (max-width:900px){.analytics-kpi-grid{grid-template-columns:repeat(3,minmax(0,1fr))}}
@media (max-width:620px){.analytics-kpi-grid{grid-template-columns:repeat(2,minmax(0,1fr))}}

/* ── The sidebar's group headings ───────────────────────────────────────────
   Fourteen links in one column meant reading the whole list to find anything.
   They are grouped now — Overview, Field team, Hospital, Administration — and
   the heading is deliberately quiet: it separates, it is not another thing to
   read. The first one needs no space above it; the rest do. */
.sidebar-nav .nav-group{
  margin:14px 8px 5px;
  padding:0 13px;
  font-family:var(--font-mono);
  font-size:var(--label-size);
  font-weight:var(--label-weight);
  letter-spacing:var(--label-track);
  text-transform:uppercase;
  color:rgba(255,255,255,.40);
  line-height:1.3;
}
.sidebar-nav .nav-group:first-child{margin-top:4px}

/* Grouping added four heading lines, and the nav was already a little taller
   than the screen — Control Panel ended 237px below the fold. The rows were
   56px tall, which is roomy for a sidebar; at 46px the four headings pay for
   themselves and the list is no harder to reach than before it was grouped. */
.sidebar-nav a,.sidebar-nav .nav-item,.sidebar-nav button{padding:8px 13px}

/* ══════════════════════════════════════════════════════════════════════════
   ONE TABLE HEADER, EVERYWHERE
   ══════════════════════════════════════════════════════════════════════════
   The blue band was a different height depending on which table you were
   looking at: 14px of padding on a plain table, 10px on a compact one, 10px
   again but a different font size on the permissions grid. Each page had set
   its own. One padding and one minimum height now, so the band is the same
   depth on every tab and every page; a heading that needs two lines still
   grows, because the height is a floor and not a cap. */
/* thead only. The permissions grid uses a <th> at the start of each BODY row
   as that permission's name — a row label, not a column header — and it keeps
   its own larger, left-aligned type. */
.table thead th,.data-table thead th,.compact-table thead th,
.perm-table thead th,.unit-table thead th{
  padding:11px 16px;
  height:44px;
  font-size:.7rem;
  vertical-align:middle;
}

/* ── Control Panel ─────────────────────────────────────────────────────────
   With the category label gone, the heading is the whole head, so the block
   no longer needs the room the two-line version wanted. */
.cp-head{margin-bottom:18px}
.cp-head h3{font-size:1rem;font-weight:700}

/* The Units table: alignment belongs in the stylesheet, not on each cell. */
.unit-table th.u-center,.unit-table td.u-center{text-align:center}
.unit-table th:last-child,.unit-table td:last-child{text-align:center}

/* ══════════════════════════════════════════════════════════════════════════
   DEVICES — a white card inside a white card is not a card
   ══════════════════════════════════════════════════════════════════════════
   Each handset's card is --elevated-bg, which the light theme resolves to
   #FFFFFF, and it sits inside a panel that is also #FFFFFF. Nothing separated
   one handset from the next but a hairline, so the whole page read as one flat
   sheet. The grid now sits on the pale page ground -- that is what makes a
   white card read as a card -- and each one takes the same blue edge as the
   figures on the Dashboard. */
.device-grid{
  background:var(--bg-color);
  padding:16px;
  border-top:1px solid var(--border-color);
  gap:14px;
}
.device-card{
  position:relative;
  background:var(--card-bg);
  border:1px solid var(--border-color);
  border-radius:14px;
  box-shadow:var(--shadow);
  overflow:hidden;
  padding:0;              /* the header runs edge to edge; the body carries the padding */
  gap:0;
}
/* Who the handset belongs to, as the title of the card rather than its first line.
   Across a grid of twelve this is what separates one card from the next at a glance,
   which is the job the 3px left edge used to do less well. */
.device-card-head{
  display:flex;align-items:center;justify-content:space-between;gap:10px;
  background:var(--primary-hover);
  padding:10px 14px;
  min-width:0;
}
.device-card-head .device-card-name{
  color:#fff;font-weight:700;font-size:.88rem;
  overflow:hidden;text-overflow:ellipsis;white-space:nowrap;min-width:0;
}
.device-card-head .device-card-name svg{fill:currentColor}
/* The badge sits on blue now, so it borrows the band rather than cutting a pale
   rectangle out of it. */
.device-card-head .status-pill,
.device-card-head .unlock-pill{
  background:rgba(255,255,255,.18);
  color:#fff;
  border-color:rgba(255,255,255,.28);
}
/* The badges sit side by side in the band, not stacked. Stacked, a handset with an
   unlock pending had a header two lines tall while the cards either side of it had
   one, so a row of cards no longer lined up. The min-height holds every header to
   the same depth whether it carries one badge or two. */
.device-card-head .status-cell-stack{
  flex-direction:row;align-items:center;gap:6px;flex:0 0 auto;
}
.device-card-head{min-height:42px}
.device-card-body{padding:12px 14px 14px}
.device-card:hover{
  border-color:var(--primary-color);
  box-shadow:var(--shadow-lg);
}
/* The empty state and any error sit on the same ground, not on a white strip. */
.device-grid > .text-center{background:none}

/* ══════════════════════════════════════════════════════════════════════════
   KIOSK ATTENDANCE — the same register treatment as Hospital Attendance
   ══════════════════════════════════════════════════════════════════════════
   This page never got the pass the hospital register did. Its remarks stacked
   one above another, so every row was a different height, and the four filter
   buttons wrapped onto a line of their own. */
.compact-table.history-table td{white-space:nowrap;height:44px;padding:0 14px}
.compact-table.history-table th{white-space:nowrap}

/* One register vocabulary, defined once.
   Hospital Attendance invented these in its own <style> block, in hex that pre-dates
   the light theme -- #22c55e where the theme's green is #0F8A4D. Both registers now
   read from the same tokens, which also means a theme change moves both. */
.st-present{color:var(--success-color);font-weight:700}
.st-late{color:var(--warning-color);font-weight:700}
.st-absent{color:var(--error-color);font-weight:700}
.st-leave{color:var(--info-color);font-weight:700}
.st-off,.st-muted{color:var(--text-secondary);font-weight:700}

/* A remark chip is as tall as the text beside it, so a row carrying three of them
   is exactly as tall as a row carrying none. Same shape as the hospital register's
   .tag, because they say the same kinds of thing. */
.remark-chip{
  display:inline-block;padding:2px 9px;border-radius:10px;
  background:var(--warning-light);color:var(--warning-color);
  font-size:.68rem;font-weight:700;line-height:1.5;
  white-space:nowrap;vertical-align:middle;margin:0 2px 0 0;
}

/* The filter buttons were a size found nowhere else: 0.74rem text in 8px/10px
   padding, set per page, which made them visibly smaller than every other button
   in the dashboard. They are the same height as the field beside them now --
   --control-h-filter, not --control-h-sm, because this row's unit of height is
   the filter field and a 32px button next to a 36px select reads as a mistake.
   Specificity (0,3,0) is what lets this beat the two page-local
   `.filter-actions .btn` rules attendance.html and hospital-attendance.html
   each declare. */
.card .filter-actions .btn{
  height:var(--control-h-filter);min-height:var(--control-h-filter);
  padding:0 16px;font-size:.8rem;
  flex:0 0 auto;
  overflow:visible;text-overflow:clip;
}
/* The group is a .form-group too, so it inherited the 132px field minimum and
   Activity Log's two buttons were padded out to fill it. */
.card .filters-grid > .filter-actions,
.card .log-filters > .filter-actions,
.card .analytics-filters > .filter-actions{
  min-width:auto;
}

/* ══════════════════════════════════════════════════════════════════════════
   LABELS ARE WORDS, SO THEY USE THE WORD TYPEFACE
   ══════════════════════════════════════════════════════════════════════════
   Reported as the font not matching the rest of the dashboard, on a dialog with
   seven labels stacked down it. Roboto Mono loads and renders fine — the problem
   is what it was being used FOR. Every small label was set in it: field labels,
   filter captions, tile and KPI labels, sidebar groups, section subtitles. All
   words, all sitting beside body text in Roboto.

   The typewriter face earns its place on figures that line up in a column — match
   scores, durations, coordinates, version strings — and it keeps every one of
   those. It does not earn its place on the word "Email".

   The treatment is unchanged: still small, still uppercase, still letter-spaced,
   so a label still reads as a label. Only the family moves. Tracking comes down
   from .08em to .05em because a proportional face needs less of it than a
   monospaced one to read as small caps, and the size goes up a touch since Roboto
   at 10.5px is smaller on the page than Roboto Mono at the same number. */
:root{
  --label-size:.7rem;
  --label-track:.05em;
}
.form-group label,
.field-inline .fi-label,
.fx-now .lbl,
.sv-list dt,
.dd-label,
.cp-sub,
.dash-stat-label,
.dash-ops-lbl,
.dash-user-lbl,
.tile-l,
.analytics-kpi .kpi-lbl,
.sidebar-nav .nav-group,
.ms-head,
.profile-info .profile-role{
  font-family:var(--font-family);
  letter-spacing:var(--label-track);
}
/* The floating field label sits on the input's top edge, so at the larger size it
   needs to sit a shade higher to stay centred on the border. */
.form-group label{
  font-size:var(--label-size);
  font-weight:600;
  top:-10px;
}

