/* IAD Wiki — admin backend. Same monochrome Swiss direction as the public site:
 * rules not boxes, radius 0, no shadows, no colour accent.
 *
 * Class names are load-bearing — admin.js and editor.js query .page-row,
 * .section-item, .drag-handle, .drop-*, .save-flash, .filters and friends. Restyle
 * them freely; do not rename them.
 */

/* --- Tokens --------------------------------------------------------------- */
:root {
  --bg: #ffffff;
  --ink: #000000;
  --soft: #3d3d3d;
  --faint: #6f6f6f;
  --rule: #000000;
  --hair: rgba(0, 0, 0, .25);
  --inset: #f2f2f2;
  --logo-f: none;
  --font: 'Archivo', Helvetica, Arial, sans-serif;
  --mono: ui-monospace, Menlo, Consolas, monospace;
  --control-h: 46px; /* shared height for inputs/selects/buttons in a form row */

  /* Compatibility aliases for editor.css, which is styled against the previous
   * token names. They are defined here rather than in editor.css so there is one
   * palette: because var() resolves at use time, each alias follows whichever
   * theme is active without needing a dark counterpart.
   *
   * Note where the mapping is deliberately lossy. The redesign has no colour
   * accent and no semantic red/amber, so --accent-2, --danger and --published all
   * collapse onto the ink or the faint grey. Anything that used to be
   * "highlighted by being blue" is now highlighted by weight or by a rule; if you
   * restyle a piece of the editor, drop the alias and use the real token.
   */
  --panel: var(--bg);
  --text: var(--ink);
  --text-soft: var(--soft);
  --muted: var(--faint);
  --border: var(--hair);
  --border-strong: var(--rule);
  --accent-2: var(--ink);
  --published: var(--ink);
  --danger: var(--faint);
  --warn: var(--faint);
}

@media (prefers-color-scheme: dark) {
  :root:not([data-theme="light"]) {
    --bg: #1a1a18; --ink: #ecebe7; --soft: #c2c1bc; --faint: #8f8e88;
    --rule: #ecebe7; --hair: rgba(236, 235, 231, .28); --inset: #262624;
    --logo-f: invert(.93);
  }
}
:root[data-theme="dark"] {
  --bg: #1a1a18; --ink: #ecebe7; --soft: #c2c1bc; --faint: #8f8e88;
  --rule: #ecebe7; --hair: rgba(236, 235, 231, .28); --inset: #262624;
  --logo-f: invert(.93);
}

* { box-sizing: border-box; }

body.admin {
  margin: 0;
  font-family: var(--font);
  font-size: 15px;
  line-height: 1.55;
  color: var(--ink);
  background: var(--bg);
  -webkit-font-smoothing: antialiased;
}

:focus-visible { outline: 2px solid var(--ink); outline-offset: 2px; }

/* Text selection inverts the ink (black in light, near-white in dark) instead of
   the browser's default blue — matching the public site. */
::selection { background: var(--ink); color: var(--bg); }

@media (prefers-reduced-motion: reduce) {
  *, *::before, *::after {
    animation-duration: .01ms !important; animation-iteration-count: 1 !important;
    transition-duration: .01ms !important; scroll-behavior: auto !important;
  }
}

a { color: inherit; text-decoration: none; }

/* --- Header --------------------------------------------------------------- */
.admin-header { border-bottom: 2px solid var(--rule); }
.admin-bar {
  display: flex; align-items: center; gap: 32px;
  padding: 20px 48px;
  /* Everything here has a minimum size and there are six of them, so between the
     menu breakpoint and about 1060px the row does not fit. Wrapping is what it
     did below 900 already; without it those widths pushed the page sideways. */
  flex-wrap: wrap;
}
.admin-brand { display: flex; align-items: center; flex: none; }
.admin-brand img { height: 26px; width: auto; display: block; filter: var(--logo-f); }

.admin-nav { display: flex; gap: 24px; }
.admin-nav a {
  font-size: 14px; font-weight: 600;
  border-bottom: 2px solid transparent; padding-bottom: 2px;
  transition: border-color .12s ease;
}
.admin-nav a:hover, .admin-nav a.active { border-bottom-color: var(--rule); }

.admin-user { display: flex; align-items: center; gap: 20px; margin-left: auto; }
.viewsite, .account-link { font-size: 13px; font-weight: 600; border-bottom: 1px solid transparent; transition: border-color .12s ease; }
.viewsite:hover, .account-link:hover { border-bottom-color: currentColor; }
.whoami { display: flex; align-items: center; gap: 8px; font-size: 13px; color: var(--soft); }

/* Role tags: 1px box, no fill — an admin reads heavier than an editor. */
.role {
  font-size: 10px; font-weight: 800; text-transform: uppercase; letter-spacing: .1em;
  padding: 2px 6px; border: 1px solid var(--hair); color: var(--soft);
}
.role-admin { border-color: var(--rule); color: var(--ink); }
/* A reader writes nothing, so it reads lighter still — dashed, not solid. */
.role-reader { border-style: dashed; color: var(--faint); }

.theme-toggle {
  border: 0; background: transparent; color: var(--ink);
  font-size: 17px; line-height: 1; padding: 4px; cursor: pointer;
}

/* --- The header menu ------------------------------------------------------- *
 * On a phone the nav and the account box fold behind one button; on a wide
 * screen they are the bar, as they always were.
 *
 * The page carries both: the flat row, and a <details> holding the same two
 * blocks, with a media query showing exactly one. They are rendered from the
 * same two template strings, so they cannot drift, and the hidden one is
 * `display: none` — out of the layout and out of the accessibility tree, so
 * nothing is announced twice. Nothing in either block carries an id, and
 * theme.js already keeps every .theme-toggle on a page in step, so the second
 * copy needs no special handling.
 *
 * The obvious alternative — ONE copy inside a <details>, flattened on desktop
 * with `display: contents` — does not work, and looks as though it does: the
 * content renders, but <details> lays it out through a slot whose box no
 * property of ours can remove, so the bar silently stacks into three rows. It
 * was measured, not assumed.
 *
 * A <details> and not a button: the browser gives it toggling, keyboard
 * handling and the expanded/collapsed announcement, and it works with
 * JavaScript switched off.
 * -------------------------------------------------------------------------- */
.admin-menu { display: none; }

/* --- Layout --------------------------------------------------------------- */
.admin-main { padding: 48px 48px 72px; }
.page-head-admin {
  display: flex; align-items: center; justify-content: space-between; gap: 24px;
}
.page-head-admin h1 { font-size: 56px; font-weight: 800; letter-spacing: -0.03em; margin: 0; line-height: 1.05; }
h2.section-title { font-size: 24px; font-weight: 800; letter-spacing: -0.02em; margin: 44px 0 16px; }
.muted { color: var(--faint); }

/* --- Buttons -------------------------------------------------------------- */
.btn {
  font: inherit; font-size: 14px; font-weight: 800; line-height: 1;
  padding: 12px 18px; border: 2px solid var(--rule); background: transparent;
  color: var(--ink); cursor: pointer; display: inline-flex; align-items: center;
  gap: 12px; transition: background .12s ease, color .12s ease;
}
.btn:hover { background: var(--inset); }
.btn-primary { background: var(--ink); color: var(--bg); border-color: var(--ink); }
.btn-primary:hover { background: var(--ink); color: var(--bg); opacity: .85; }
.btn-sm { font-size: 13px; font-weight: 600; padding: 8px 12px; border-width: 2px; }
/* Row actions are links, not chrome: no box, just the shared hover underline. */
.btn-ghost { border: 0; padding: 0; font-weight: 600; border-bottom: 1px solid transparent; }
.btn-ghost:hover { background: transparent; border-bottom-color: currentColor; }
.btn:disabled, .btn[disabled] { opacity: .45; cursor: default; pointer-events: none; }

/* --- Forms ---------------------------------------------------------------- */
input[type="text"], input[type="email"], input[type="password"], input[type="search"], select, textarea {
  font: inherit; font-size: 14px; padding: 11px 12px;
  border: 2px solid var(--rule); background: transparent; color: var(--ink);
  width: 100%; border-radius: 0;
}
input::placeholder { color: var(--faint); }
select { cursor: pointer; }
label { display: block; font-size: 13px; font-weight: 800; }
label input, label select, label textarea { margin-top: 8px; font-weight: 400; }
.hint { display: block; font-size: 12px; font-weight: 500; color: var(--faint); margin-top: 8px; }
.stack { display: flex; flex-direction: column; gap: 20px; max-width: 560px; }
.card { border: 2px solid var(--rule); padding: 28px; margin-top: 24px; }
.form-actions { display: flex; gap: 16px; align-items: center; }
.inline { display: inline; margin: 0; }

/* Equal-height controls: see .entity-form, which both "add a thing" forms share.
   An input is padding + a 1.55 line-box, a button is padding + a line-height:1
   label, and a native select is shorter still — without one token they stair-step.
   Deliberately not global: inline controls (the section-rename field, the users
   table's reset input) keep their own size. */
/* The password field is a bordered wrapper around a border-less input: the
   wrapper carries the shared height and the input fills it. Giving the input the
   height directly would add the wrapper's 2px border on top, making this one
   control taller than its neighbours. */
.user-form .pw-wrap input { height: auto; align-self: stretch; }

/* --- Filters -------------------------------------------------------------- */
.filters {
  display: flex; gap: 16px; margin-top: 36px; flex-wrap: wrap; align-items: stretch;
  /* Stays put, with the column heads sticking directly beneath it — the two are
     one band of controls and it reads as one. admin.js keeps --sticky-top equal to
     this bar's height, so a bar that wraps on a narrow window still adds up. */
  position: sticky; top: 0; z-index: 30; background: var(--bg);
  padding: 14px 0; margin-top: 22px;
}
/* The search box carries a trailing → like the public one; the button sits
   inside the same 2px box rather than beside it. */
.filters .search-wrap { flex: 1 1 20rem; min-width: 14rem; display: flex; border: 2px solid var(--rule); }
.filters .search-wrap input { border: 0; flex: 1; }
.filters .search-wrap input:focus { outline: none; }
.filters .search-wrap:focus-within { outline: 2px solid var(--ink); outline-offset: 2px; }
.filters .search-wrap button {
  font: inherit; font-size: 15px; font-weight: 700; border: 0; background: transparent;
  color: var(--ink); padding: 0 16px; cursor: pointer;
}
.filters select { width: auto; min-width: 11rem; flex: 0 0 auto; }
.search-summary { font-size: 13px; margin: 12px 0 0; }
.search-summary a { border-bottom: 1px solid var(--hair); }
.search-summary a:hover { border-bottom-color: currentColor; }

/* --- Tables --------------------------------------------------------------- */
.table-scroll { overflow-x: auto; margin-top: 36px; }
/* The Pages table does NOT get a scroll box of its own.
 *
 * A container that scrolls is the scrollport for everything inside it: a sticky
 * `thead` sticks to THAT box rather than to the window (so it slid away with the
 * page), and a row's ⋯ menu opening past its edge is clipped. Both were worked
 * around, and both stop existing once the page itself does the scrolling. On a
 * narrow window the table pushes the page sideways instead, which is the ordinary
 * behaviour and leaves the header where it belongs. */
.table-scroll.has-row-menus { overflow: visible; }
.data-table { width: 100%; border-collapse: collapse; }
.data-table thead th {
  font-family: var(--mono); font-size: 10.5px; font-weight: 400;
  text-transform: uppercase; letter-spacing: .14em; color: var(--faint);
  text-align: left; padding: 10px 16px 10px 0;
  background: var(--bg);
  /* A border on a sticky cell tears away from it as the rows pass under; the
     shadow is painted with the cell and stays put. */
  box-shadow: inset 0 -2px 0 var(--rule);
}

/* Sticky column heads work in exactly ONE place, and this is the selector for it.
 *
 * `position: sticky` sticks to the nearest scrollport, not the viewport — and
 * .table-scroll is `overflow-x: auto`, which makes it one (auto on either axis
 * turns the box into a scroll container on both). A head inside it therefore
 * sticks to the box rather than to the page, and misbehaves in a way that
 * depends on the layout: on Users it slid down under its own first row.
 *
 * The dashboard's list is the sole exception, and only by accident of another
 * fix: .has-row-menus is `overflow: visible` so the row menus can escape the box,
 * which also hands its head back to the viewport. Tying the rule to that class
 * rather than to the presence of a filter bar keeps the two facts together —
 * this is the one container a viewport-sticky head can live in.
 *
 * Every other list (Users, Sections, Deleted pages) is short and keeps a plain
 * head that simply sits above its rows. */
.table-scroll.has-row-menus .data-table thead th {
  /* A pixel of overlap, so no rounding anywhere — the measurement, the zoom
     level, a fractional device pixel — can open a seam between the two sticky
     bands for a row to show through. The bar sits above (z-index 30) and hides it. */
  position: sticky; top: calc(var(--sticky-top, 74px) - 1px); z-index: 20;
}
.data-table td { padding: 13px 16px 13px 0; border-bottom: 1px solid var(--hair); vertical-align: middle; }
.data-table tbody tr:hover td { background: var(--inset); }
/* Inset the outer columns so the drag handle and the last action aren't jammed
   against the table edge. Padding on the cells rather than the table keeps the
   hover fill and the drop outline spanning the full row width. */
.data-table th:first-child, .data-table td:first-child { padding-left: 12px; }
.data-table th:last-child, .data-table td:last-child { padding-right: 12px; text-align: left; }

/* 36px of column + the 12px inset added above. */
.col-handle { width: 48px; }
.col-title a { font-size: 16px; font-weight: 700; border-bottom: 1px solid transparent; transition: border-color .12s ease; }
.col-title a:hover { border-bottom-color: currentColor; }
/* Sub-pages read as a branch of the row above. */
/* A short step is enough to read as "inside": the rows are adjacent and the tick
   already says it, so a deep indent only costs the title its width. */
.col-title.depth-2 { padding-left: 16px; }
.col-title.depth-3 { padding-left: 32px; }
.col-title.depth-2 a, .col-title.depth-3 a { font-weight: 700; }
.col-title.depth-2::before, .col-title.depth-3::before {
  content: "└"; margin-right: 6px; color: var(--faint); font-size: 11px; opacity: .7;
}
.kid-count { font-family: var(--mono); font-size: 11px; color: var(--faint); margin-left: 8px; }
.col-section { font-size: 13px; font-weight: 500; color: var(--soft); }
.col-updated { font-family: var(--mono); font-size: 12px; color: var(--faint); white-space: nowrap; }
.col-updated-by { font-size: 13px; font-weight: 500; color: var(--soft); white-space: nowrap; }
.col-actions { white-space: nowrap; }
/* Left-aligned so the password field sits at the same x in every row — a right
   alignment drifts, because the trailing item ("(you)" vs the Delete button)
   differs in width. */
.col-actions .actions { display: flex; gap: 16px; align-items: center; justify-content: flex-start; }
/* The per-row "reset password" form. Its field would otherwise inherit the base
   width:100%, stretch across the actions cell and drag the trailing "(you)" or
   Delete on top of the Reset button. A fixed width + inline-flex keeps the field,
   its button and whatever follows in one clean row. */
.col-actions .actions form { display: inline-flex; align-items: center; gap: 8px; margin: 0; }
.col-actions .actions input[type="password"] { width: 150px; height: 40px; }
.empty-row { color: var(--faint); padding: 32px 0; }

/* Status is typographic — the rule under the word carries the meaning, so the
   table stays monochrome and no legend is needed. */
.status { font-size: 12px; font-weight: 600; }
.status-published { color: var(--ink); }
.status-draft { color: var(--soft); border-bottom: 2px dotted var(--hair); }
.status-archived { color: var(--faint); text-decoration: line-through; }



/* --- Sortable columns (flat list) ------------------------------------------ */
/* The head IS the control: a link per column, cycling its own direction and then
   back to site order. Links, not buttons, so sorting survives with JavaScript off
   and a sorted list has an address you can keep. */
.th-sort { padding: 0 !important; }
.sort-link {
  display: inline-flex; align-items: center; gap: 6px;
  padding: 12px 16px 12px 0; font: inherit; color: inherit;
}
.data-table th:first-child .sort-link { padding-left: 12px; }
.sort-arrow {
  font-size: 11px; line-height: 1; border: 1px solid transparent; padding: 2px 4px;
  /* Present but invisible at rest, so nothing shifts when a column becomes the
     sorted one — and so the affordance appears exactly where it will be used. */
  opacity: 0; transition: opacity .12s ease;
}
.sort-link:hover .sort-arrow, .sort-link:focus-visible .sort-arrow { opacity: .55; }
.th-sort.sorted .sort-link { color: var(--ink); }
.th-sort.sorted .sort-arrow { opacity: 1; border-color: var(--rule); }

/* Once a column sort breaks the tree apart, the Section column says which page a
   sub-page hangs from — in front of the title it started every title at a
   different x and weighed as much as the title did. */
.row-parent { color: var(--faint); }

/* --- Sections view --------------------------------------------------------- */
/* A heading row is a BREAK in the list, not another row of data.
 *
 * So: no hover, no rule of its own, and space above instead. A rule under a
 * heading is the same shape as the table's own header rule, and a page of them
 * reads as a stack of little tables rather than one list — which is exactly how
 * this looked before. The rows below bring their own hairlines; the heading only
 * has to sit above them.
 *
 * Note the cell stays a table cell: giving it `display: flex` (to push a count to
 * the right) drops it out of the table layout, `colspan` stops applying, and the
 * heading ends up half a table wide. */
.section-row td { background: var(--bg); border-bottom: 0; }
.data-table tbody tr.section-row:hover td { background: var(--bg); }
/* Starts at the table's own inset, left of the titles beneath it: the label is
   the outdent, the pages are the block. */
.section-cell {
  padding: 36px 0 10px 12px !important; border-bottom: 0 !important;
  text-align: left; font-weight: 400;
}
.section-row:first-child .section-cell { padding-top: 24px !important; }
/* Quieter than a page title (16px/700 in full ink): a label is found by its
   position and its space, and does not need to outweigh the things it names. Both
   levels share one grey, so the step between them is size, weight and indent —
   colour would make the header a second kind of thing rather than a bigger one. */
.section-title {
  font-size: 15px; font-weight: 800; letter-spacing: .01em; color: var(--soft);
}
/* A section nested under a header sits in from it, as it does on the homepage. */
.section-nested .section-title { padding-left: 22px; font-size: 13px; font-weight: 600; }
/* Dropping onto the heading puts the page at the top of that section — the only
   way into a section that has no pages yet. */
.section-row.drop-section td { background: var(--inset); }

/* --- Row actions menu ------------------------------------------------------ */
/* Edit stays in the open; everything else lives behind ⋯. Five verbs spelled out
   on every row took more width than the titles did, and repeated text is the one
   thing in a list that can never help you tell two rows apart.
   A <details>, so it opens with no JavaScript and is keyboard-operable; admin.js
   only adds the conveniences (one at a time, Escape, click-away). */
.row-menu { position: relative; display: inline-block; }
.row-menu-btn {
  list-style: none; cursor: pointer; user-select: none;
  font-size: 15px; line-height: 1; padding: 4px 8px;
  border: 1px solid transparent; color: var(--soft);
}
.row-menu-btn::-webkit-details-marker { display: none; }
.row-menu-btn:hover { color: var(--ink); background: var(--inset); }
.row-menu[open] .row-menu-btn { border-color: var(--rule); color: var(--ink); background: var(--bg); }
.row-menu-btn:focus-visible { outline: 2px solid var(--ink); outline-offset: 1px; }
.row-menu-list {
  position: absolute; right: 0; top: calc(100% + 4px); z-index: 40;
  min-width: 12rem; padding: 6px 0;
  background: var(--bg); border: 2px solid var(--rule);
}
.row-menu-list a, .row-menu-list button {
  display: block; width: 100%; text-align: left; box-sizing: border-box;
  padding: 9px 16px; font: inherit; font-size: 13px; font-weight: 600;
  background: none; border: 0; color: var(--ink); cursor: pointer;
}
.row-menu-list form { margin: 0; }
.row-menu-list a:hover, .row-menu-list button:hover { background: var(--inset); }
.row-menu-list a:focus-visible, .row-menu-list button:focus-visible {
  outline: 2px solid var(--ink); outline-offset: -2px;
}
/* Last, muted, and one step apart: the one action that takes something away. */
.row-menu-danger { color: var(--faint) !important; border-top: 1px solid var(--hair); margin-top: 6px; padding-top: 12px !important; }

/* --- View toggle ----------------------------------------------------------- */
.view-toggle {
  display: inline-flex; align-items: stretch; border: 2px solid var(--rule);
  margin-left: auto; height: var(--control-h);
}
.view-link {
  display: flex; align-items: center; padding: 0 16px;
  font-size: 13px; font-weight: 700; white-space: nowrap;
}
.view-link + .view-link { border-left: 2px solid var(--rule); }
.view-link:hover { background: var(--inset); }
.view-link.active { background: var(--ink); color: var(--bg); }

/* --- Dragging ------------------------------------------------------------- */
.draggable-tree .page-row.dragging { opacity: .4; }
.drag-handle { color: var(--faint); cursor: grab; user-select: none; font-size: 14px; }
.drag-handle:hover { color: var(--ink); }
.drag-handle:active { cursor: grabbing; }
/* Monochrome drop feedback: a fill to nest, a rule to reorder. box-shadow (not a
   border) so the row height — and everything below it — never shifts.
 *
 * "Nest inside" has to outline the ROW, but the shadow can only be drawn on the
 * cells. A ring on every <td> boxes each cell individually; the edges have to be
 * split instead — top and bottom across all of them, and the left/right sides on
 * the first and last cell only — so the four pieces join into one rectangle. */
.page-row.drop-inside td {
  background: var(--inset);
  box-shadow: inset 0 2px 0 var(--rule), inset 0 -2px 0 var(--rule);
}
.page-row.drop-inside td:first-child {
  box-shadow: inset 0 2px 0 var(--rule), inset 0 -2px 0 var(--rule), inset 2px 0 0 var(--rule);
}
.page-row.drop-inside td:last-child {
  box-shadow: inset 0 2px 0 var(--rule), inset 0 -2px 0 var(--rule), inset -2px 0 0 var(--rule);
}
/* Reordering is shown by the drop line instead (see .drop-line): a rule inset on
   the row could only ever say "somewhere in this gap", and the whole question
   when a page has sub-pages is WHICH side of that gap you are on. */

/* The drop line: where the page would land, at the depth it would land at.
   Positioned by admin.js in viewport coordinates and drawn over the table, so it
   can sit in the gap between two rows without changing anyone's height. The
   square cap marks the indent — the one difference between "after this page" and
   "inside it as its last sub-page". */
.drop-line {
  position: fixed; z-index: 60; height: 3px; margin-top: -1px;
  background: var(--rule); pointer-events: none;
}
.drop-line::before {
  content: ""; position: absolute; left: 0; top: -3px;
  width: 9px; height: 9px; background: var(--rule);
}
.drop-line[hidden] { display: none; }

.move-status {
  position: fixed; left: 50%; bottom: 24px; transform: translateX(-50%);
  z-index: 50; padding: 14px 18px; background: var(--bg);
  border: 2px solid var(--rule); font-size: 13px; font-weight: 600;
  color: var(--ink); max-width: min(90vw, 40rem);
}
.move-status.error { border-style: dashed; }
.move-status[hidden] { display: none; }
#new-page-section-field[hidden] { display: none; }

/* --- Sections --------------------------------------------------------------- */
.section-list { list-style: none; margin: 32px 0 0; padding: 0; max-width: 760px; border-top: 2px solid var(--rule); }
.section-item {
  display: flex; align-items: center; gap: 16px;
  /* Matches the inset on the page table: handle and Delete off the edges. */
  padding: 14px 12px; border-bottom: 1px solid var(--hair);
}
.section-item:hover { background: var(--inset); }
.section-item.dragging { opacity: .4; }
.section-item.depth-1 { font-size: 17px; font-weight: 800; }
.section-item.depth-2 { padding-left: 40px; font-size: 15px; font-weight: 500; }
.section-item.drop-inside { background: var(--inset); box-shadow: inset 0 0 0 2px var(--rule); }
/* Sections reorder against the same drop line the page tree uses. */
.section-item .count {
  font-family: var(--mono); font-size: 11px; text-transform: uppercase;
  letter-spacing: .1em; color: var(--faint); white-space: nowrap;
}
/* The page count opens the Pages list filtered to this section. Dotted like the
   dashboard's Updated date, which is the other number that doubles as a way in. */
.count-link { border-bottom: 1px dotted var(--hair); }
.count-link:hover { color: var(--ink); border-bottom-color: currentColor; }
.section-item form.grow { flex: 1; display: flex; gap: 12px; align-items: center; min-width: 0; }
/* The title edits in place: borderless until focused, so the list reads as a
   list rather than a wall of input boxes. */
.section-item input[name="title"] {
  flex: 1; min-width: 6rem; border: 0; padding: 2px 0;
  font-size: inherit; font-weight: inherit; background: transparent;
}
.section-item input[name="title"]:focus { outline: 2px solid var(--ink); outline-offset: 2px; }
.save-flash { flex: 0 0 4.5rem; font-family: var(--mono); font-size: 10.5px; color: var(--faint); text-align: right; white-space: nowrap; }
.save-flash.error { color: var(--ink); font-weight: 700; }
.js-hide-when-enhanced[hidden] { display: none; }

/* --- Users ---------------------------------------------------------------- */
.col-name { font-size: 16px; font-weight: 700; }
.col-email { font-family: var(--mono); font-size: 12.5px; color: var(--soft); }
/* The hint belongs to the Password field, so it sits under it — but the row is
   bottom-aligned (align-items: flex-end), and a hint in normal flow would make
   the Password label taller and lift its field out of line with the others.
   Taking it out of flow keeps every field's box the same height; the hint just
   hangs beneath. */
.user-form .hint { position: absolute; top: 100%; left: 0; margin: 6px 0 0; white-space: nowrap; }
/* The password field carries a SHOW toggle inside its own border box. */
.pw-wrap { display: flex; border: 2px solid var(--rule); margin-top: 8px; }
.pw-wrap input { border: 0; flex: 1; }
.pw-wrap input:focus { outline: none; }
.pw-wrap:focus-within { outline: 2px solid var(--ink); outline-offset: 2px; }
.pw-toggle {
  font-family: var(--mono); font-size: 10.5px; letter-spacing: .1em; text-transform: uppercase;
  border: 0; background: transparent; color: var(--faint); padding: 0 12px; cursor: pointer;
}
.pw-toggle:hover { color: var(--ink); }

/* Per-editor "Access" cell: a Pages / Pages + Sections dropdown (+ no-JS Save). */
.col-perm { white-space: nowrap; }
.col-perm form { display: inline-flex; gap: 8px; align-items: center; }
.col-perm select { width: auto; min-width: 8.5rem; font-size: 13px; padding: 8px 10px; }

/* --- Login ---------------------------------------------------------------- */
.login-body { display: grid; place-items: center; min-height: 100vh; position: relative; }
.login-body > .theme-toggle { position: absolute; top: 24px; right: 24px; }
.login-card { border: 2px solid var(--rule); padding: 44px; width: min(92vw, 420px); }
.login-card h1 { font-size: 24px; font-weight: 800; margin: 0; }
.login-sub { font-size: 13px; color: var(--soft); margin: 6px 0 32px; }
.login-card .stack { max-width: none; }
.login-card .btn-primary { width: 100%; justify-content: space-between; }
.login-foot { margin: 28px 0 0; font-size: 13px; font-weight: 600; }
.login-foot a { border-bottom: 1px solid var(--hair); }
.login-foot a:hover { border-bottom-color: currentColor; }

/* --- Messages ------------------------------------------------------------- */
.form-error, .notice {
  border: 2px solid var(--rule); padding: 14px 16px; font-size: 14px; margin-bottom: 20px;
}
.form-error { font-weight: 600; }
.notice-warn { border-style: dashed; }
.notice-ok { border-style: solid; }
.notice a { border-bottom: 1px solid currentColor; }


/* --- Delete confirmation + trash ------------------------------------------ */
/* Deleting is the one admin action that can lose work, so it gets a page rather
   than a dialog. Monochrome like everything else — the weight comes from the
   typing, not from a red button. */
.confirm-card { max-width: 640px; }
.confirm-lead { font-size: 18px; font-weight: 500; line-height: 1.45; margin: 0; }
.confirm-lead .mono { font-family: var(--mono); font-size: 13px; color: var(--soft); }
.confirm-sub { font-size: 14px; color: var(--soft); margin: 0 0 4px; line-height: 1.5; }
.confirm-lead + .confirm-sub { margin-top: 20px; }
.confirm-card .notice { margin: 24px 0 0; }
/* The card holds one question and one field, so the form needs no top rule when
   it is the only thing in it. */
.confirm-card .stack { margin-top: 0; max-width: none; }
.confirm-card .confirm-sub + .stack, .confirm-card .notice + .stack { margin-top: 28px; }

/* Deleted pages: a fold at the foot of the Pages list, not a page of its own.
   Shut by default with its count on the summary, so it is findable without
   taking up the room a table would. */
.deleted-pages { margin-top: 56px; border-top: 2px solid var(--rule); padding-top: 20px; }
.deleted-pages > summary {
  display: flex; align-items: center; gap: 10px; cursor: pointer;
  font-size: 15px; font-weight: 800; list-style: none; padding: 4px 0;
}
.deleted-pages > summary::-webkit-details-marker { display: none; }
/* The triangle is drawn rather than inherited, so it matches the type and turns
   with the fold in every browser. */
.deleted-pages > summary::before {
  content: "▸"; color: var(--faint); font-size: 12px; transition: transform .12s ease;
}
.deleted-pages[open] > summary::before { transform: rotate(90deg); }
.deleted-pages > summary:hover { background: var(--inset); }
.deleted-pages > summary:focus-visible { outline: 2px solid var(--ink); outline-offset: 2px; }
.deleted-pages > summary .count {
  font-family: var(--mono); font-size: 11px; color: var(--faint);
  border: 1px solid var(--hair); padding: 1px 7px;
}
.deleted-pages .table-scroll { margin-top: 16px; }
.trash-intro { font-size: 14px; color: var(--soft); max-width: 62ch; margin: 16px 0 0; line-height: 1.5; }
.deleted-pages .notice, .deleted-pages .form-error { margin-top: 20px; margin-bottom: 0; }
.trash-title { font-size: 16px; font-weight: 700; }
.trash-slug { display: block; font-family: var(--mono); font-size: 11px; color: var(--faint); margin-top: 4px; }


/* --- Page history + diff --------------------------------------------------- */
.history-intro { font-size: 14px; color: var(--soft); max-width: 70ch; margin: 20px 0 24px; line-height: 1.5; }
.history-intro .mono { font-family: var(--mono); font-size: 12px; color: var(--faint); }
.col-what { font-size: 13px; font-weight: 600; }
/* The live row: a rule on the left, so it reads as "you are here" without colour. */
.version-current td:first-child { box-shadow: inset 3px 0 0 var(--rule); }
/* The Updated date doubles as the way into a page's history. Dotted, so it is
   discoverable without adding another button to a crowded actions row. */
.updated-link { border-bottom: 1px dotted var(--hair); }
.updated-link:hover { border-bottom-color: currentColor; }

/* A diff is monochrome like everything else: the +/− gutter and the rules carry
   the meaning, so it survives greyscale, dark mode and colour-blindness alike. */
.diff-stats { font-size: 12px; color: var(--faint); margin: 0 0 12px; }
.diff-legend { display: inline-flex; gap: 12px; align-items: center; margin-left: 12px; color: var(--faint); }
.diff-key {
  font-family: var(--mono); font-weight: 700; border: 1px solid var(--hair);
  padding: 0 5px; margin-right: 4px;
}
.diff-table {
  width: 100%; border-collapse: collapse;
  font-family: var(--mono); font-size: 12.5px; line-height: 1.6;
  border-top: 2px solid var(--rule); border-bottom: 2px solid var(--rule);
}
.diff-table td { padding: 2px 10px 2px 0; vertical-align: top; }
.diff-num { width: 48px; text-align: right; color: var(--faint); font-size: 11px; user-select: none; }
.diff-mark { width: 18px; text-align: center; font-weight: 700; user-select: none; }
.diff-text { white-space: pre-wrap; word-break: break-word; }
.diff-same { color: var(--faint); }
.diff-del { background: var(--inset); }
.diff-del .diff-text { text-decoration: line-through; text-decoration-thickness: 1px; }
.diff-add { background: var(--inset); font-weight: 600; }
.diff-add .diff-mark, .diff-del .diff-mark { color: var(--ink); }
.diff-skip td {
  padding: 6px 0; font-size: 11px; color: var(--faint);
  border-top: 1px dashed var(--hair); border-bottom: 1px dashed var(--hair);
}
.diff-actions { display: flex; gap: 16px; align-items: center; margin-top: 28px; }
.sr-only {
  position: absolute; width: 1px; height: 1px; padding: 0; margin: -1px;
  overflow: hidden; clip: rect(0 0 0 0); white-space: nowrap; border: 0;
}

/* --- Theme transition ----------------------------------------------------- */
::view-transition-old(root) { animation: none; }
::view-transition-new(root) { animation: theme-reveal .6s cubic-bezier(.4, 0, .2, 1); }
@keyframes theme-reveal {
  from { clip-path: circle(0px at var(--tx, 50%) var(--ty, 50%)); }
  to   { clip-path: circle(var(--tr, 150vmax) at var(--tx, 50%) var(--ty, 50%)); }
}
@media (prefers-reduced-motion: reduce) {
  ::view-transition-new(root) { animation: none; }
}

/* --- Responsive ----------------------------------------------------------- */
@media (max-width: 900px) {
  /* Neither band sticks down here. The filter bar would eat a third of a phone
     screen, and the column heads have to go with it: they stick BENEATH the bar
     (see --sticky-top), so heads left sticky on their own float over the rows
     with nothing above them. The selector below has to be as specific as the one
     that made them sticky, or it silently loses and that is exactly what
     happened. */
  .filters { position: static; }
  .table-scroll.has-row-menus .data-table thead th { position: static; }

  .admin-bar { flex-wrap: wrap; gap: 16px 24px; padding: 16px 20px; }
  .admin-nav { gap: 18px; }
  /* A segmented control squeezed narrower than its two labels is not a control
     any more; it keeps its size and something else wraps. */
  .wiki-switch { flex: none; }
  /* The account row wraps rather than compressing what is in it. Without this it
     was one nowrap line that crushed its items instead: "View site ↗" came out
     29px wide over three lines, and the theme toggle was pushed off the edge. */
  .admin-user { width: 100%; margin-left: 0; flex-wrap: wrap; gap: 12px 16px; }
  .viewsite, .account-link, .whoami { white-space: nowrap; }

  .admin-main { padding: 32px 20px 56px; }
  .page-head-admin { flex-direction: column; align-items: flex-start; gap: 16px; }
  .page-head-admin h1 { font-size: 40px; }
  .data-table { font-size: 14px; }
}

/* The header, folded behind one button.
 *
 * Scoped tighter than the rest of the responsive work: at tablet widths the bar
 * still fits in two tidy rows, and a nav you can see beats a nav you have to
 * open. Only where it cannot fit does it fold.
 *
 * What stays out in the open is what tells you where you are — the wordmark, and
 * which wiki you are in. That second one is not decoration: it is the difference
 * between editing the public site and the internal one, and it must not be a
 * thing you open a menu to check. Everything you might GO to folds away. */
@media (max-width: 700px) {
  .admin-header { position: relative; }
  /* The flat copies step aside for the button; only the ones in the panel show. */
  .admin-bar > .admin-nav, .admin-bar > .admin-user { display: none; }
  .admin-menu { display: block; flex: none; margin-left: auto; }
  .admin-menu-btn {
    display: flex; align-items: center; justify-content: center;
    width: 44px; height: 44px; margin: -9px -10px -9px 0; /* 44px touch target, same optical box */
    cursor: pointer; list-style: none; color: var(--ink);
  }
  .admin-menu-btn::-webkit-details-marker { display: none; }
  .admin-menu-btn svg { width: 22px; height: 22px; }
  .admin-menu-btn .menu-cross, .admin-menu[open] .menu-bars { display: none; }
  .admin-menu[open] .menu-cross { display: block; }

  /* Shut means shut, said in our own words.
     Relying on how the browser hides a closed <details> does not survive contact
     with a panel that has styling of its own — measured here: with the rule left
     out the menu stood permanently open, whether or not the panel was given a
     `display`. One line, no dependence on a UA internal. */
  .admin-menu:not([open]) > .admin-menu-panel { display: none; }

  /* Open, it hangs off the header, so it covers the page rather than shoving it
     down the screen. Above the sticky filter bar (z-index 30). */
  .admin-menu[open] > .admin-menu-panel {
    display: block; position: absolute; top: 100%; left: 0; right: 0; z-index: 40;
    background: var(--bg); border-bottom: 2px solid var(--rule);
    padding: 4px 20px 16px;
  }
  /* Everything in it becomes a row with a hairline above it — the same list
     language the rest of the admin speaks.
     The account block is a grid rather than a column so its last row can hold
     two things: who you are, and the theme control at the far end of it. Both
     are statements about the session rather than places to go, and neither is
     worth a row of its own at the foot of a menu. */
  .admin-menu-panel .admin-nav { display: flex; flex-direction: column; align-items: stretch; gap: 0; width: auto; }
  .admin-menu-panel .admin-user {
    /* Cells stretch rather than centre. Centred, the two halves of the last row
       were different heights and each carried its own top border, so the hairline
       came out in two pieces two pixels apart — a visible step. Stretched, both
       cells span the row and their borders land on one line; the content inside
       each is what gets centred. */
    display: grid; grid-template-columns: 1fr auto; align-items: stretch; gap: 0; width: auto;
  }
  .admin-menu-panel .admin-nav a,
  .admin-menu-panel .admin-user > * {
    display: block; width: 100%; text-align: left;
    padding: 13px 0; border-top: 1px solid var(--hair); border-bottom: 0;
    grid-column: 1 / -1; /* a full-width row, unless placed otherwise below */
  }
  /* The last row, made of the two that are ordered to the end of the grid. */
  .admin-menu-panel .whoami,
  .admin-menu-panel .editor-whoami {
    display: flex; align-items: center; order: 1; grid-column: 1; width: auto;
  }
  .admin-menu-panel .theme-toggle {
    order: 2; grid-column: 2; width: auto; justify-self: stretch;
    /* Its own box now fills the row's height, so the mark is centred within it
       rather than sitting wherever the button's baseline fell. */
    display: flex; align-items: center; justify-content: flex-end;
    /* Sized to a finger, not to the glyph: on its own the mark is 14px across. */
    min-width: 44px;
  }
  .admin-menu-panel .admin-nav a.active { font-weight: 800; }

  /* --- the same menu, in the editor's bar ---------------------------------- *
   * The editor page loads this stylesheet too, so it is the same control, not a
   * second one that looks like it. What differs is only what stays out: the
   * status, Preview and Save, because a Save behind a menu is not a Save.
   *
   *   [logo]  Pages / About the Bachelor                                    ☰
   *           Published                            [Preview ↗]      [Save]
   * ------------------------------------------------------------------------ */
  .editor-actions > .editor-whoami,
  .editor-actions > .editor-account,
  .editor-actions > .editor-logout,
  .editor-actions > .theme-toggle { display: none; }
  /* The three that stay take the row under the wordmark, so the button can sit
     up on the first one where a menu button belongs. */
  .editor-actions { flex: 1 1 100%; justify-content: flex-end; }
  .editor-topbar .admin-menu { margin-left: auto; }
  /* From zero, so the breadcrumb gives rather than pushing the button onto a row
     of its own: a line is chosen from what items ASK for, and only then are they
     shrunk. It has its ellipsis already (editor.css), so what it gives up is the
     middle of a long title rather than the button's place on the row. */
  .editor-crumb { flex: 1 1 0; }
  /* The bar is already sticky, so it is a positioned box and the panel hangs off
     it — no extra `position: relative` needed here. */
}

/* Four controls, one per row, is a column of nearly a phone screen before the
   list even starts — and the two selects sat at odd widths with the view toggle
   pushed off to the right on a row of its own. So: the search takes its own row,
   the two filters share the next as equal halves, and the toggle spans the
   third as the segmented control it is. Nothing is measured in absolute widths,
   so it holds from 320px up to the breakpoint. */
@media (max-width: 620px) {
  /* A section row is four things — grip, name field, count, Delete — and below
     this they stop fitting on one line: at 320px the row ran 150px past the
     screen and took the page with it. So the name takes the first line and the
     count and Delete share the second, the count absorbing the slack so Delete
     stays at the right edge where it is on a wide screen. */
  .section-item { flex-wrap: wrap; }
  .section-item form.grow { flex: 1 1 60%; }
  .section-item .count { flex: 1 1 auto; }

  .filters { gap: 12px; }
  .filters .search-wrap { flex: 1 1 100%; min-width: 0; }
  .filters select { flex: 1 1 0; min-width: 0; width: auto; }
  .view-toggle { flex: 1 1 100%; margin-left: 0; }
  .view-toggle .view-link { flex: 1 1 0; justify-content: center; }

  /* Two columns come off the Pages table on a phone, so the rest of it fits the
     screen instead of hiding "Edit" past the right edge.
     - The grip, because dragging is genuinely not available here: HTML5
       drag-and-drop never fires from touch, so the column was 39px of a control
       that could not be used.
     - "Updated by", the audit column — the widest thing in the row that is not a
       fact about the page you came here to find. The date stays.
     Both are addressed by their own class (see colClass in admin-templates), not
     by counting cells: the two views put different columns in the same position,
     and the trash and history tables would be counted too. */
  .data-table .col-handle,
  .data-table .col-updated-by, .data-table .th-col-updated-by { display: none; }
  /* And the flat list's Section column, which is the one thing in that view the
     other view already says out loud — it groups by section under headings. This
     table cannot fall back on a sideways scroll the way the others do: its
     scroller is overflow:visible so the row menus are not clipped, so anything
     too wide pushes the whole page sideways instead. */
  .data-table .col-section, .data-table .th-col-section { display: none; }
  .data-table th, .data-table td { padding-left: 8px; padding-right: 8px; }
}

/* Phone-width: the date goes too, and what is left — title, status, and the way
   in — fits the screen with room to spare. Kept as its own step rather than
   folded into the one above, so a large phone or a small tablet still gets the
   date; each breakpoint drops the next least useful column, and none of them
   leaves the table needing a sideways scroll to reach Edit. */
@media (max-width: 430px) {
  .data-table .col-updated, .data-table .th-col-updated { display: none; }
}

/* Which wiki you are editing. Two links rather than a dropdown: it says what
   exists, and the one you are in is marked rather than hidden. Sits before the
   section nav, because it scopes everything that follows it.
   Built from the same parts as the Sections/List toggle — one segmented control,
   the current segment filled — so the two read as the same kind of switch. It is
   shorter, though: it lives in the header beside the nav, not in a row of 46px
   form controls. */
.wiki-switch {
  display: inline-flex; align-items: stretch;
  border: 2px solid var(--rule); margin-right: 8px;
}
.wiki-switch a {
  display: flex; align-items: center; padding: 0 14px; height: 32px;
  font-size: 13px; font-weight: 700; white-space: nowrap;
}
.wiki-switch a + a { border-left: 2px solid var(--rule); }
.wiki-switch a:hover { background: var(--inset); }
.wiki-switch a.current { background: var(--ink); color: var(--bg); }

/* "must change password" beside the role. It had no style at all, so it landed
   as bare text against the role's pill and read as a layout fault rather than a
   note. Same shape as .role, lighter, so the pair reads as one section. */
.badge {
  display: inline-block; margin-left: 6px;
  font-size: 10px; font-weight: 700; text-transform: uppercase; letter-spacing: .08em;
  padding: 2px 6px; border: 1px dashed var(--hair); color: var(--faint);
}

/* Users list: Delete sits away from Reset, at the far end of the row. They were
   16px apart — a destructive action one slip away from a routine one — and the
   actions column is the full width of the table, so the gap costs nothing. */
.col-actions .actions .user-delete { margin-left: auto; padding-left: 32px; }

/* A reader holds no capabilities, so the Access control has nothing to say for
   one. Disabled rather than hidden: the column keeps its shape as the role
   changes, and the reason stays on screen instead of the control vanishing.
   The server ignores grants for readers regardless (createUser/setUserPermissions),
   so this only spares someone choosing something that would not have applied. */
.user-form select:disabled,
.col-perm select:disabled { opacity: .45; cursor: not-allowed; }
.user-form label:has(select:disabled) { color: var(--faint); }

/* --- "Add a thing" forms --------------------------------------------------- *
 * New user and New section are the same kind of form, so they are now the same
 * form: a heading with a rule under it, one labelled field per column, the
 * primary button last. The section form used bare placeholders and a card border
 * while the user form used labels and no border — two answers to one question.
 * ------------------------------------------------------------------------- */
.entity-form-head {
  font-size: 15px; font-weight: 800; border-bottom: 2px solid var(--rule);
  padding-bottom: 10px; margin: 44px 0 20px;
}
.entity-form {
  display: flex; flex-wrap: wrap; gap: 16px; align-items: flex-end;
  margin: 0 0 40px;
}
.entity-form label { flex: 1 1 12rem; position: relative; }
.entity-form > button { flex: 0 0 auto; }
.entity-form input, .entity-form select, .entity-form .btn, .entity-form .pw-wrap {
  height: var(--control-h);
}
/* The section form is two fields, not five: give them the same measure they had
   rather than letting two labels stretch across the whole page. */
.add-section { max-width: 760px; }
.add-section label { flex: 1 1 14rem; }

/* The actions cell lays ITS forms out inline — status, duplicate, delete all sit
   in a row there. The forms inside the row menu are menu items and must stack;
   without this they inherited that inline-flex and ran across the menu. */
.col-actions .actions .row-menu-list form { display: block; }

/* --- One bar across all three surfaces ------------------------------------ *
 * See the matching block in styles.css for why. In short: the four headers were
 * each sized by their tallest control, so they stood 83 / 78 / 77 tall and the
 * wordmark moved as you navigated between them. These three values are the same
 * in both stylesheets; the sheets never load together, so each declares its own.
 * ------------------------------------------------------------------------- */
:root { --bar-h: 84px; --bar-pad-x: 48px; --bar-gap: 24px; }

/* The admin bar carries its rule on the PARENT .admin-header, so its own box is
   the bar height minus that border — otherwise the header measures 2px taller
   than the other three. */
.admin-bar { min-height: calc(var(--bar-h) - 2px); }
.admin-bar, .editor-topbar {
  padding: 12px var(--bar-pad-x);
  gap: var(--bar-gap);
  align-items: center;
}
.admin-brand, .editor-brand { flex: none; }
/* Utility text is one size on every surface (the admin said 13, the editor 12). */
.whoami, .viewsite, .account-link { font-size: 13px; }

@media (max-width: 900px) {
  :root { --bar-h: 72px; --bar-pad-x: 20px; }
  .admin-bar, .editor-topbar { padding: 10px var(--bar-pad-x); gap: 16px var(--bar-gap); }
}

/* --- The "add a thing" dock ----------------------------------------------- *
 * New user and New section stay on screen while the list above them scrolls. On a
 * long list the form was a page-length away from the rows you were reading, so
 * adding one meant scrolling to the end and back.
 *
 * `position: sticky` and not `fixed`: sticky holds the band at the bottom edge
 * only while its containing block still runs past the viewport, then lets it
 * settle into its own place at the end of the page. So nothing is ever
 * permanently covered — scroll to the bottom and the last row is there, with the
 * form beneath it exactly where the markup puts it.
 * ------------------------------------------------------------------------- */
.entity-dock {
  position: sticky; bottom: 0; z-index: 25;
  background: var(--bg);
  border-top: 2px solid var(--rule);
  margin-top: 40px;
  padding: 18px 0 24px;
}
/* The dock's own rule is the separator now, so the heading drops its underline
   and its outer margins. */
.entity-dock .entity-form-head {
  border-bottom: 0; padding-bottom: 0; margin: 0 0 14px;
}
/* Room under the row for the password hint, which hangs out of flow below its
   field (see .user-form .hint). */
.entity-dock .entity-form { margin: 0 0 18px; }

/* The dock is the last thing on the page, and .admin-main puts 72px of padding
   under it. That padding is what the dock finally settles onto: scroll to the
   very bottom and the band lifted 72px clear of the window edge, which read as
   the form jumping away just as you reached it.
   With no padding beneath it, the dock's resting place IS the bottom of the
   window, so releasing from sticky changes nothing you can see — it stays put
   the whole way down. Its own padding gives the form its breathing room. */
.admin-main:has(.entity-dock) { padding-bottom: 0; }
