/* AVL Association of Nepal, admin panel
   Shares the site's flag palette: crimson for identity and primary actions,
   flag blue for links, focus and secondary actions.

   The greys and the two typefaces are deliberately the same values as the
   public site's light theme in css/style.css, so the two never drift a shade
   apart. If you change --text or --border here, change them there too.

   One deliberate difference: --bg stays a light grey rather than the site's
   plain white. The admin is dense tables and cards that need to separate from
   the ground they sit on; the marketing site has no such need, which is why
   flat white suits it and would flatten this. */

:root {
  --primary: #ce1126;
  --primary-dark: #a80e1f;
  --primary-deep: #6b0914;
  --accent: #003893;
  --accent-dark: #002a6e;

  /* Matched to --text / --text-2 / --text-3 in css/style.css. */
  --text: #14171a;
  --text-light: #4a525c;
  --text-faint: #78818c;
  --bg: #f7f8fa;
  --white: #ffffff;
  --border: #dde2e8;
  --border-soft: #e9edf2;

  --ok-bg: #eef7f1;   --ok-fg: #0f6b38;   --ok-br: #cde5d8;
  --err-bg: #fdf1f3;  --err-fg: #a80e1f;  --err-br: #f3d3d9;
  --warn-bg: #fff8e8; --warn-fg: #8a6100; --warn-br: #f0e0bb;

  --radius: 10px;
  --radius-sm: 7px;
  --sidebar: 236px;
  --shadow: 0 1px 2px rgba(17,24,39,.04), 0 8px 20px -12px rgba(17,24,39,.12);
  /* Same stacks as css/style.css. Segoe UI stays in the fallback chain, which is
     what this used to be set to outright. */
  --font:         'Inter', system-ui, -apple-system, 'Segoe UI', sans-serif;
  --font-display: 'Space Grotesk', 'Inter', system-ui, sans-serif;
}

*, *::before, *::after { box-sizing: border-box; margin: 0; padding: 0; }

body {
  font-family: var(--font);
  background: var(--bg);
  color: var(--text);
  font-size: 14px;
  line-height: 1.6;
  -webkit-font-smoothing: antialiased;
}

img { max-width: 100%; display: block; }
a { color: var(--accent-dark); text-decoration: none; }
a:hover { text-decoration: underline; }
ul { list-style: none; }

:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; border-radius: 3px; }

/* ── Login ───────────────────────────────────────────────────────────────── */
.login-page {
  min-height: 100vh;
  display: flex;
  align-items: center;
  justify-content: center;
  padding: 24px;
  background: linear-gradient(160deg, #fff 0%, var(--bg) 60%, #eef1f6 100%);
}
.login-card {
  width: 100%;
  max-width: 400px;
  background: var(--white);
  border: 1px solid var(--border);
  border-radius: 14px;
  box-shadow: var(--shadow);
  overflow: hidden;
}
/* Logo centred on white, with the crimson band gone. The band was there to
   carry a text wordmark; the logo is full colour and needs a light ground —
   on crimson its chevron is the same red and vanishes. */
.login-header {
  background: var(--white);
  border-bottom: 1px solid var(--border-soft);
  padding: 28px 26px 20px;
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 12px;
  text-align: center;
}
/* The heading is the logo. Zero line-height stops the inline box adding a few
   stray pixels under the image. */
.login-header h1 { line-height: 0; }
.login-header h1 img { width: 112px; height: auto; }
.login-header p {
  color: var(--text-faint);
  font-size: 11px;
  font-weight: 600;
  text-transform: uppercase;
  letter-spacing: .12em;
}
.login-body { padding: 26px; }
.login-foot { margin-top: 18px; font-size: 13px; text-align: center; }

/* ── Layout ──────────────────────────────────────────────────────────────── */
.admin-body { display: flex; min-height: 100vh; }

.sidebar {
  width: var(--sidebar);
  flex: none;
  background: #14181d;
  position: fixed;
  top: 0; left: 0; bottom: 0;
  display: flex;
  flex-direction: column;
  overflow-y: auto;
  z-index: 200;
  transition: transform .22s ease;
}
/* Logo only, centred. The name and the "Administration" label that used to sit
   beside a text mark are both gone. */
.sidebar-brand {
  display: flex;
  align-items: center;
  justify-content: center;
  padding: 20px 20px 18px;
  border-bottom: 1px solid rgba(255,255,255,.09);
}
.sidebar-logo { display: block; line-height: 0; }
.sidebar-logo img {
  width: 104px;
  height: auto;
  transition: opacity .15s ease;
}
.sidebar-logo:hover img { opacity: .85; }

.sidebar nav { padding: 12px 10px; flex: 1; }
.nav-group {
  color: #667180;
  font-size: 10px;
  font-weight: 700;
  text-transform: uppercase;
  letter-spacing: .1em;
  padding: 14px 10px 6px;
}
.sidebar nav a {
  display: flex; align-items: center; gap: 10px;
  padding: 9px 11px;
  border-radius: var(--radius-sm);
  color: #b9c2cc;
  font-size: 13.5px;
  font-weight: 500;
  text-decoration: none;
  transition: background .15s, color .15s;
}
.sidebar nav a .ico { width: 17px; text-align: center; flex: none; font-size: 14px; }
.sidebar nav a:hover { background: rgba(255,255,255,.07); color: #fff; text-decoration: none; }
.sidebar nav a.active { background: var(--primary); color: #fff; font-weight: 600; }
.sidebar nav a .count {
  margin-left: auto;
  background: var(--primary);
  color: #fff;
  font-size: 10.5px;
  font-weight: 700;
  padding: 1px 7px;
  border-radius: 999px;
}
.sidebar nav a.active .count { background: rgba(255,255,255,.25); }

.sidebar-foot {
  border-top: 1px solid rgba(255,255,255,.09);
  padding: 14px 20px;
}
.sidebar-foot .who { color: #fff; font-size: 12.5px; font-weight: 600; }
.sidebar-foot .role { color: #7d8894; font-size: 11px; text-transform: capitalize; }
.sidebar-foot a { color: #b9c2cc; font-size: 12px; display: inline-block; margin-top: 8px; }

.main {
  flex: 1;
  margin-left: var(--sidebar);
  min-width: 0;
  display: flex;
  flex-direction: column;
}
/* [hidden] has to beat the display rules further down this file, or it does
   nothing at all on the elements that most want it. .btn-secondary and friends
   set `display: inline-flex`, which outranks the browser's own
   `[hidden] { display: none }` on specificity — so `button.hidden = true` from
   the footer script left the button sitting there, which is exactly what
   happened to the filter bar's Apply. One rule here rather than a per-component
   [hidden] selector each time this comes up. */
[hidden] { display: none !important; }

/* The page header band: title and actions on one row, and — where a page has
   them — its filters on a second row inside the SAME band.

   Two containers stacked (a topbar, then a filter card under it) is what this
   was, and it read as two unrelated bars with a strip of page background
   between them. The filters are a control on this page, not a thing of their
   own, so they belong inside its header. It is sticky, which means they also
   stay put while the table scrolls under them. */
.topbar {
  background: var(--white);
  border-bottom: 1px solid var(--border);
  padding: 15px 26px;
  display: flex;
  flex-direction: column;
  position: sticky;
  top: 0;
  z-index: 100;
}
.topbar-main { display: flex; align-items: center; gap: 14px; }

/* ── The phone masthead ───────────────────────────────────────────────────────
   Logo and wordmark left, menu button hard right — and deliberately IDENTICAL to
   the public site's header, not merely similar to it. Every number below is
   lifted from the matching rule in css/style.css so that a member crossing from
   avlnepal.org into their account sees the same bar, not a near-miss of it:

     .brand           gap 16px                       -> here
     .brand-logo      46px tall on a phone (~line 4769 of style.css)
     .brand-wordmark  1.08rem / 600 / #003893 / Livvic, nowrap
     .nav-toggle      44x44, 1px border, 6px radius, three 18x1.5px bars

   Two of those need saying out loud, because this file's own tokens would
   otherwise quietly override them:

   THE FACE IS LIVVIC, not var(--font-display). --font-display is Space Grotesk
   in the admin and Livvic on the public site, so using the token here would have
   set the association's name in a different typeface on either side of the
   login. Both shells already load Livvic 400/500/600 — see the <link> in each
   header partial — so this costs no extra request.

   THE RADIUS IS 6px, not var(--radius-sm). That token is 7px here and 6px
   there; one pixel, and invisible on its own, but this whole block exists to
   stop exactly that kind of drift.

   Hidden above 900px: up there the sidebar is on screen carrying the same mark,
   and a second one in the bar would be the association's name twice on one
   view. It appears at the width the sidebar leaves — which is the width at
   which the screen otherwise stopped saying whose panel it was. */
.topbar-brand { display: none; align-items: center; gap: 16px; min-width: 0; }
.topbar-brand img { height: 46px; width: auto; display: block; flex: none; }
.topbar-brand span {
  font-family: "Livvic", "Inter", system-ui, sans-serif;
  font-size: 1.08rem;
  font-weight: 600;
  line-height: 1.25;
  letter-spacing: 0;
  color: #003893;
  /* One line or an ellipsis, never two — .brand-wordmark is nowrap here too. */
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}
.topbar-brand:hover { text-decoration: none; }
.topbar-title { min-width: 0; }

/* The menu button, rebuilt as the public site's .nav-toggle: a bordered square
   holding three bars, rather than the ☰ glyph this was. The glyph was a
   character in whatever the body face is — a different weight and a different
   size in the two areas — where the bars are drawn to the pixel. */
.menu-btn {
  display: none;
  flex-direction: column;
  justify-content: center;
  align-items: center;
  gap: 5px;
  width: 44px;
  height: 44px;
  padding: 0;
  background: rgba(26, 29, 33, 0.022);
  border: 1px solid rgba(26, 29, 33, 0.13);
  border-radius: 6px;
  cursor: pointer;
}
.menu-btn span {
  display: block;
  width: 18px;
  height: 1.5px;
  background: var(--text);
  transition: transform .18s ease, opacity .18s ease;
}
/* Open, the three bars fold into an X. The footer script keeps aria-expanded in
   step with the drawer, which is what drives this. */
.menu-btn[aria-expanded="true"] span:nth-child(1) { transform: translateY(6.5px) rotate(45deg); }
.menu-btn[aria-expanded="true"] span:nth-child(2) { opacity: 0; }
.menu-btn[aria-expanded="true"] span:nth-child(3) { transform: translateY(-6.5px) rotate(-45deg); }
.topbar-filters {
  margin-top: 13px;
  padding-top: 12px;
  border-top: 1px solid var(--border-soft);
}
/* Embedded in the header band, the bar drops its own chrome — the band already
   provides the surface, the border and the shadow. */
.topbar .filter-bar {
  background: none;
  border: 0;
  box-shadow: none;
  border-radius: 0;
  padding: 0;
  margin: 0;
}
/* Display face on the page title only. .card-head h2 below is an uppercase
   letterspaced label, not a heading, so it stays in the body face — same
   division the public site makes. */
.topbar h1 { font-family: var(--font-display); font-size: 17px; font-weight: 600; color: var(--text); }
.topbar .sub { color: var(--text-faint); font-size: 12.5px; }
.topbar-main .spacer { margin-left: auto; }
.content { padding: 24px 26px 60px; }

/* .menu-btn is defined up with the phone masthead now, to the public site's
   .nav-toggle measurements. It was a 38px square with a 17px glyph here. */
.sidebar-scrim {
  display: none;
  position: fixed; inset: 0;
  background: rgba(15,20,26,.45);
  z-index: 150;
}

/* ── Cards, tables, forms ────────────────────────────────────────────────── */
.card {
  background: var(--white);
  border: 1px solid var(--border);
  border-radius: var(--radius);
  box-shadow: var(--shadow);
  margin-bottom: 20px;
  overflow: hidden;
}
.card-head {
  padding: 14px 18px;
  border-bottom: 1px solid var(--border);
  background: #fbfcfd;
  display: flex;
  align-items: center;
  gap: 12px;
  flex-wrap: wrap;
}
.card-head h2 { font-size: 13px; font-weight: 700; text-transform: uppercase; letter-spacing: .05em; color: var(--text-light); }
.card-head .spacer { margin-left: auto; }
.card-body { padding: 18px; }

.stat-grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(180px, 1fr)); gap: 16px; margin-bottom: 22px; }
.stat {
  background: var(--white);
  border: 1px solid var(--border);
  border-left: 3px solid var(--accent);
  border-radius: var(--radius);
  padding: 16px 18px;
  box-shadow: var(--shadow);
}
.stat.alert { border-left-color: var(--primary); }
.stat b { display: block; font-size: 26px; font-weight: 700; color: var(--text); line-height: 1.1; }
.stat span { display: block; font-size: 12px; color: var(--text-faint); text-transform: uppercase; letter-spacing: .05em; font-weight: 600; }
.stat a { display: inline-block; font-size: 12px; margin-top: 6px; }

/* List footer: the row count on the left, page links on the right. Sits below a
   .table-wrap, so it needs its own top border, the table's last row has none. */
.pager {
  display: flex;
  align-items: center;
  gap: 12px;
  flex-wrap: wrap;
  border-top: 1px solid var(--border-soft);
}
.pager-links { display: flex; align-items: center; gap: 10px; }

.table-wrap { overflow-x: auto; }
table { width: 100%; border-collapse: collapse; }
th, td { padding: 11px 14px; text-align: left; border-bottom: 1px solid var(--border-soft); vertical-align: middle; }
th {
  font-size: 11px; font-weight: 700; text-transform: uppercase; letter-spacing: .06em;
  color: var(--text-faint); background: #fbfcfd; white-space: nowrap;
}
tbody tr:last-child td { border-bottom: none; }
tbody tr:hover { background: #fbfcfd; }
td.shrink { width: 1%; white-space: nowrap; }

/* A table whose cells stack two or three lines each (admin/member-records.php).
   Two things make that kind of row read as a wall rather than a record:

   vertical-align: middle, which is the default above and right for a table of
   single-line cells — but when one cell is three lines and its neighbour is
   one, centring each independently leaves nothing on the row sharing a
   baseline, and the eye has no line to follow across. Top-aligned, every cell
   starts level and the row reads left to right again.

   And the secondary lines running at the body line-height, which packs a
   three-line cell into the same space a one-line cell occupies. Loosening them
   costs a few pixels of row height and buys the separation back.

   Scoped to a class rather than changed on `td`, because the single-line tables
   on the other admin screens are correct as they are. */
.table-stacked td { vertical-align: top; padding-top: 13px; padding-bottom: 13px; }
.table-stacked td .small,
.table-stacked td .muted { line-height: 1.55; }
/* The first line of a cell sits level with the pills beside it: a .pill carries
   its own padding, so an unstyled sibling line starts a couple of pixels high. */
.table-stacked td > .strong { display: block; padding-top: 1px; }
.row-thumb {
  width: 52px; height: 40px; object-fit: cover;
  border-radius: 5px; border: 1px solid var(--border); background: var(--bg);
}
.muted { color: var(--text-faint); }
.small { font-size: 12px; }
.strong { font-weight: 600; }
.nowrap { white-space: nowrap; }

.field { margin-bottom: 15px; }
.field > label {
  display: block; font-size: 12px; font-weight: 600;
  color: var(--text-light); margin-bottom: 5px;
}
.field .hint { font-size: 11.5px; color: var(--text-faint); margin-top: 4px; }
input[type=text], input[type=password], input[type=email], input[type=tel],
input[type=url], input[type=number], input[type=date], input[type=datetime-local],
input[type=search], select, textarea {
  width: 100%;
  padding: 9px 11px;
  border: 1.5px solid var(--border);
  border-radius: var(--radius-sm);
  font-family: inherit;
  font-size: 13.5px;
  color: var(--text);
  background: var(--white);
  transition: border-color .15s, box-shadow .15s;
}
input:focus, select:focus, textarea:focus {
  outline: none;
  border-color: var(--accent);
  box-shadow: 0 0 0 3px rgba(0,56,147,.13);
}
textarea { resize: vertical; min-height: 90px; line-height: 1.6; }
input[type=file] { font-size: 13px; }
.field-row { display: grid; grid-template-columns: 1fr 1fr; gap: 14px; }
.field-row-3 { display: grid; grid-template-columns: repeat(3, 1fr); gap: 14px; }
.check { display: flex; align-items: center; gap: 8px; font-size: 13.5px; }
.check input { width: auto; }

/* The member list in the audience picker. Capped and scrolled so a long
   membership roll cannot push the Save button off the screen. */
.member-picker {
  max-height: 260px;
  overflow-y: auto;
  border: 1.5px solid var(--border);
  border-radius: var(--radius-sm);
  background: var(--white);
  padding: 4px 0;
}
.member-picker-row { padding: 6px 11px; align-items: flex-start; }
.member-picker-row:hover { background: var(--bg); }
.member-picker-row .hint { display: inline; }

.btn-primary, .btn-secondary, .btn-ghost, .btn-danger {
  display: inline-flex; align-items: center; justify-content: center; gap: 6px;
  padding: 9px 17px;
  border: none;
  border-radius: var(--radius-sm);
  font-family: inherit;
  font-size: 13.5px;
  font-weight: 600;
  cursor: pointer;
  text-decoration: none;
  transition: background .15s, box-shadow .15s, opacity .15s;
  white-space: nowrap;
}
.btn-primary { background: var(--primary); color: #fff; }
.btn-primary:hover { background: var(--primary-dark); text-decoration: none; }
.btn-secondary { background: var(--accent); color: #fff; }
.btn-secondary:hover { background: var(--accent-dark); text-decoration: none; }
.btn-ghost { background: var(--white); color: var(--text-light); box-shadow: inset 0 0 0 1.5px var(--border); }
.btn-ghost:hover { background: #f2f5f8; color: var(--text); text-decoration: none; }
.btn-danger { background: var(--white); color: var(--primary-dark); box-shadow: inset 0 0 0 1.5px var(--err-br); }
.btn-danger:hover { background: var(--err-bg); text-decoration: none; }
.btn-block { width: 100%; }
.btn-sm { padding: 6px 11px; font-size: 12.5px; }
.btn-row { display: flex; gap: 10px; flex-wrap: wrap; align-items: center; margin-top: 18px; }
.btn-row .spacer { margin-left: auto; }

/* ── Filter bar ───────────────────────────────────────────────────────────────
   A list's filters, on one line: label + dropdown, three times, then the count.

   The first version on member-records.php was a full .card — a heading band
   saying FILTERS, then three stacked .btn-rows each with an 18px top margin.
   Three facts about the query cost most of a laptop screen and the table they
   filter started below the fold, which is backwards: the table is the page and
   the filters are a control on it. A single row of chips was the next try, and
   still put twelve words in front of the reader. Dropdowns say it in three.

   The bar is a <form>, so it works with JavaScript off; the footer script turns
   the selects into auto-submit and hides the Apply button. */
.filter-bar {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 10px 20px;
  padding: 11px 16px;
  margin-bottom: 22px;
  background: var(--white);
  border: 1px solid var(--border);
  border-radius: var(--radius);
  box-shadow: var(--shadow);
}
.filter-bar .filter-field { display: flex; align-items: center; gap: 8px; }
.filter-bar .filter-label {
  font-size: 10.5px;
  font-weight: 700;
  text-transform: uppercase;
  letter-spacing: .06em;
  color: var(--text-faint);
  white-space: nowrap;
}

/* The chevron is a background image rather than a pseudo-element, because a
   <select> cannot carry children. Inlined as a data URI so it costs no request
   and cannot 404 on a host that mangles asset paths. */
.filter-bar .filter-select {
  appearance: none;
  -webkit-appearance: none;
  font: inherit;
  font-size: 13px;
  font-weight: 500;
  color: var(--text);
  background-color: var(--white);
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='10' height='6' viewBox='0 0 10 6'%3E%3Cpath d='M1 1l4 4 4-4' fill='none' stroke='%2378818c' stroke-width='1.6' stroke-linecap='round' stroke-linejoin='round'/%3E%3C/svg%3E");
  background-repeat: no-repeat;
  background-position: right 11px center;
  border: 0;
  box-shadow: inset 0 0 0 1.5px var(--border);
  border-radius: var(--radius-sm);
  padding: 7px 30px 7px 11px;
  cursor: pointer;
  transition: box-shadow .12s ease, background-color .12s ease;
}
.filter-bar .filter-select:hover { background-color: #f2f5f8; }
.filter-bar .filter-select:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }

/* A dropdown that is actually filtering says so on its own, without a summary
   line: accent ring, accent text. This is the only state cue the bar has now
   that the chips are gone, so it has to be legible at a glance. */
.filter-bar .filter-select.is-set {
  box-shadow: inset 0 0 0 1.5px var(--accent);
  color: var(--accent-dark);
  font-weight: 600;
}

.filter-bar .filter-count {
  margin-left: auto;
  font-size: 12px;
  color: var(--text-faint);
  white-space: nowrap;
}
/* Clear is a link, not a button: it undoes a choice rather than making one. */
.filter-bar .filter-count a { margin-left: 8px; font-weight: 600; }

@media (max-width: 700px) {
  /* Stacked, and each dropdown takes the width so the labels line up and the
     tap target is the whole row. */
  .filter-bar { gap: 10px; }
  .filter-bar .filter-field { width: 100%; }
  .filter-bar .filter-label { min-width: 88px; }
  .filter-bar .filter-select { flex: 1; }
  .filter-bar .filter-count { margin-left: 0; width: 100%; }
}

.alert {
  padding: 11px 15px;
  border-radius: var(--radius-sm);
  font-size: 13.5px;
  font-weight: 500;
  margin-bottom: 16px;
  border: 1px solid transparent;
}
.alert-success { background: var(--ok-bg);  color: var(--ok-fg);  border-color: var(--ok-br); }
.alert-error   { background: var(--err-bg); color: var(--err-fg); border-color: var(--err-br); }
.alert-warn    { background: var(--warn-bg);color: var(--warn-fg);border-color: var(--warn-br); }

.pill {
  display: inline-block;
  padding: 2px 9px;
  border-radius: 999px;
  font-size: 11px;
  font-weight: 700;
  letter-spacing: .03em;
  text-transform: uppercase;
}
.pill-on   { background: var(--ok-bg);  color: var(--ok-fg); }
.pill-off  { background: #eef1f5;       color: var(--text-faint); }
.pill-new  { background: var(--err-bg); color: var(--err-fg); }
.pill-info { background: #e9effa;       color: var(--accent-dark); }
.pill-warn { background: var(--warn-bg);color: var(--warn-fg); }

.empty { padding: 40px 20px; text-align: center; color: var(--text-faint); }
.empty p { margin-bottom: 14px; }

/* ── Media picker & library ──────────────────────────────────────────────── */
.picker { border: 1.5px dashed var(--border); border-radius: var(--radius); padding: 13px; background: #fbfcfd; }
.picker-current { display: flex; align-items: center; gap: 12px; margin-bottom: 11px; }
.picker-current img {
  width: 78px; height: 58px; object-fit: cover;
  border-radius: 5px; border: 1px solid var(--border); background: var(--white);
}
.picker-current .none {
  width: 78px; height: 58px; flex: none;
  border: 1px dashed var(--border); border-radius: 5px;
  display: flex; align-items: center; justify-content: center;
  color: var(--text-faint); font-size: 11px; background: var(--white);
}

.media-grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(160px, 1fr)); gap: 14px; }
.media-tile {
  border: 1px solid var(--border);
  border-radius: var(--radius);
  overflow: hidden;
  background: var(--white);
  transition: box-shadow .15s, transform .15s;
}
.media-tile:hover { box-shadow: var(--shadow); transform: translateY(-2px); }
.media-tile img { width: 100%; aspect-ratio: 4/3; object-fit: cover; background: var(--bg); }
.media-tile .meta { padding: 9px 11px; font-size: 11.5px; color: var(--text-faint); }
.media-tile .meta b { display: block; color: var(--text); font-size: 12px; font-weight: 600; word-break: break-all; }
.media-tile .acts { padding: 0 11px 11px; display: flex; gap: 7px; }

/* Sortable rows */
.sortable tr { cursor: grab; }
/* Any direct child carrying data-id is a draggable row, table or not — the page
   editor sorts cards. The grab cursor stays on tables only: a block in the page
   editor can be open, and a whole edit form under a grab cursor invites you to
   drag the field you meant to type in. There the ⠿ handle carries it instead. */
.sortable > [data-id].dragging { opacity: .45; }
.drag-handle { color: var(--text-faint); cursor: grab; user-select: none; font-size: 15px; }

/* ── Confirm dialog ──────────────────────────────────────────────────────── */

/* Replaces window.confirm() for anything marked data-confirm. The browser's own
   dialog is drawn by the OS at the top of the window, carries the hostname, and
   cannot be styled — so a destructive action in this panel was being confirmed
   somewhere that looks nothing like the panel.

   Built on <dialog> rather than a div: showModal() gives the focus trap, the
   Esc-to-close and the inert background for free, and getting those right by
   hand is most of the work in a modal. The markup is created once in
   partials/footer.php, so no page needs to include anything. */
.confirm-dialog {
  width: min(430px, calc(100vw - 32px));
  /* A modal <dialog> is centred by the UA's `margin: auto`, which the global
     `* { margin: 0 }` reset at the top of this file overrides — without this it
     renders pinned to the top-left corner. */
  margin: auto;
  padding: 0;
  border: 0;
  border-radius: var(--radius);
  background: var(--white);
  color: var(--text);
  box-shadow: 0 10px 15px -3px rgba(17, 24, 39, .1), 0 30px 60px -12px rgba(17, 24, 39, .28);
}
.confirm-dialog::backdrop {
  background: rgba(17, 24, 39, .5);
  /* Progressive: browsers without backdrop-filter still get the dim. */
  backdrop-filter: blur(2px);
}

.confirm-dialog .dlg-body {
  display: flex;
  gap: 14px;
  padding: 22px 22px 18px;
}
/* Round warning mark. Uses the error tokens rather than a fresh red so it reads
   as the same "something destructive" signal the alerts already use. */
.confirm-dialog .dlg-icon {
  flex: none;
  width: 38px;
  height: 38px;
  border-radius: 50%;
  display: grid;
  place-items: center;
  background: var(--err-bg);
  color: var(--err-fg);
  font-size: 19px;
  font-weight: 700;
  line-height: 1;
}
.confirm-dialog h2 {
  margin: 2px 0 5px;
  font-family: var(--font-display);
  font-size: 16px;
  font-weight: 600;
  color: var(--text);
}
.confirm-dialog p {
  margin: 0;
  font-size: 13.5px;
  line-height: 1.55;
  color: var(--text-light);
  /* Messages carry titles an editor typed, which can be long and unbroken. */
  overflow-wrap: anywhere;
}

.confirm-dialog .dlg-actions {
  display: flex;
  justify-content: flex-end;
  gap: 9px;
  padding: 14px 22px;
  border-top: 1px solid var(--border-soft);
  background: var(--bg);
  border-radius: 0 0 var(--radius) var(--radius);
}
/* Solid, unlike the outlined .btn-danger in the tables. In a table the outline
   keeps a row of Delete buttons quiet; here it is the one committing action and
   should look like it. */
.confirm-dialog .btn-confirm {
  background: var(--primary);
  color: var(--white);
  box-shadow: none;
}
.confirm-dialog .btn-confirm:hover { background: var(--primary-dark); }

@media (max-width: 480px) {
  .confirm-dialog .dlg-actions { flex-direction: column-reverse; }
  .confirm-dialog .dlg-actions button { width: 100%; }
}

/* ── Responsive ──────────────────────────────────────────────────────────── */
@media (max-width: 980px) {
  .sidebar { transform: translateX(-100%); }
  .sidebar.open { transform: translateX(0); }
  .sidebar.open ~ .sidebar-scrim { display: block; }
  .main { margin-left: 0; }
  .menu-btn { display: flex; }

  /* Two rows in one bar: the masthead, then the page title under it.
     Ordered rather than reordered in the markup, because the source order —
     brand, button, title, actions — is the reading order a screen reader should
     get, and it is only the phone that wants the title on its own line. */
  .topbar-main { flex-wrap: wrap; gap: 10px 12px; }
  .topbar-brand { display: flex; order: 1; }
  .menu-btn { order: 2; margin-left: auto; }
  /* The page title, centred on its own row and ruled off from the masthead
     above it. Two things share this bar and they are not the same thing: the
     top line says whose panel this is and never changes, the line under it says
     which page you are on and changes every time you tap. Left-aligned and
     unruled they read as one four-line block; a hairline between them and a
     centre line under it makes the second a caption for the screen rather than
     more of the header.

     Centred to match the public site's own page titles, which sit on the page's
     centre line on every inner page. */
  .topbar-title {
    order: 3;
    width: 100%;
    text-align: center;
    padding-top: 11px;
    border-top: 1px solid var(--border-soft);
  }
  .topbar-main .spacer { display: none; }

  /* ── The stat tiles, on a phone ────────────────────────────────────────────
     Four tiles, and every one of them spent a third of its height on the words
     "Open →" — a link that goes exactly where the tile it sits in goes. Four
     tiles, four identical links, in a 2x2 grid: about 100px each for one number
     and one word.

     So the whole tile becomes the target instead. The link is stretched over
     the card with inset:0 and its text pulled off-screen — text-indent rather
     than display:none, because a screen reader must still be able to say where
     the thing leads, and a bare number is not a destination. What is left is
     the number and its label, at roughly 62px a tile.

     Nothing here is new furniture: it is the same .stat the admin dashboard
     draws, minus the part a thumb makes unnecessary. */
  .stat-grid { grid-template-columns: 1fr 1fr; gap: 10px; margin-bottom: 16px; }
  .stat { position: relative; padding: 12px 14px; }
  .stat b { font-size: 22px; }
  .stat span { font-size: 10.5px; letter-spacing: .04em; }
  .stat a {
    position: absolute;
    inset: 0;
    margin: 0;
    /* Off-screen, not hidden: still announced, still focusable, and the whole
       card is now the tap target rather than eleven pixels of text. */
    text-indent: -999em;
    overflow: hidden;
  }
  .topbar-main > :not(.topbar-brand):not(.menu-btn):not(.topbar-title) { order: 4; }
}
@media (max-width: 640px) {
  .content { padding: 18px 15px 50px; }
  .topbar { padding: 12px 15px; }
  .field-row, .field-row-3 { grid-template-columns: 1fr; }
  .btn-row { flex-direction: column; align-items: stretch; }
  .btn-row .spacer { margin-left: 0; }
}
