/* ==========================================================================
   Admin styling.

   Loaded after site.css and reuses its tokens, so the admin area is visibly
   the same site rather than a bolted-on control panel. It reads slightly
   flatter and denser on purpose: this is a working surface, not a display one.
   ========================================================================== */

/*
 * The same bar as the public side.
 *
 * These two headers used to be built separately and had drifted into two
 * different sites: a dot and plain text against a wordmark, 14px of padding
 * against 11, a scrolling bar against a sticky one. Every value below is
 * .site-nav's, and the wordmark and the buttons now come from the shared
 * definitions in site.css rather than being reproduced here — the only way
 * "identical" survives the next change to either one.
 *
 * What differs is the contents, which is as it should be: the public bar
 * carries sections and a language, this one carries who you are and the way
 * out.
 */
.admin-bar {
  position: sticky;
  top: 0;
  z-index: 20;
  border-bottom: 1px solid color-mix(in srgb, var(--eye-c) 14%, var(--line));
  background: color-mix(in srgb, var(--bg) 82%, transparent);
  backdrop-filter: blur(10px);
}

.admin-bar__inner {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 10px 18px;
  padding-block: 11px;
}

/* Layout only. The look is .wordmark--nav, shared with the public bar. */
.admin-bar__brand { flex: none; }

/* A plate is drawn taller than it measures; give that height back so the
   row sits straight. Same compensation as .site-nav__links. */
.admin-bar__inner .plate { margin-bottom: var(--plate-edge); }

.admin-bar__spacer { flex: 1 1 auto; }
.admin-bar__who {
  font-size: 11px;
  letter-spacing: 0.1em;
  text-transform: uppercase;
  color: var(--text-faint);
}
.admin-bar nav { display: flex; gap: 16px; font-size: 12.5px; }
.admin-bar nav a { color: var(--text-dim); text-decoration: none; }
.admin-bar nav a:hover,
.admin-bar nav a[aria-current='page'] { color: var(--text-strong); }

.admin-main { position: relative; z-index: 2; padding-block: 34px 90px; }

.page-head {
  display: flex;
  flex-wrap: wrap;
  align-items: baseline;
  gap: 12px 20px;
  margin-bottom: 26px;
}
.page-head h1 {
  font-family: var(--font-display);
  font-size: 28px;
  font-weight: 600;
  letter-spacing: -0.02em;
  color: var(--text-strong);
  margin: 0;
}
.page-head__note { font-size: 12px; color: var(--text-faint); }
.page-head__actions { margin-left: auto; display: flex; gap: 10px; }

/* --------------------------------------------------------------------------
   Buttons
   -------------------------------------------------------------------------- */
/* --------------------------------------------------------------------------
   Buttons

   The same pressable plate the public bar uses (see .plate in site.css), so
   the admin does not have a button language of its own. They were flat
   outlined rectangles, which next to the public side looked like a different
   application.

   Three weights, and the difference between them is fill rather than shape:
   a primary is the brand colour, an ordinary one is a raised panel, a
   destructive one is red. All three stand on an edge and sink onto it when
   pressed.
   -------------------------------------------------------------------------- */
.btn {
  --btn-edge: #0A1214;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: 8px;
  font-family: var(--font-mono);
  font-size: 12.5px;
  font-weight: 600;
  padding: 9px 16px;
  /* A tap target worth the name — the buttons this replaced measured under
     20px tall in places (see .btn--icon below), well short of the ~40px a
     thumb can reliably hit. */
  min-height: 40px;
  border: none;
  border-radius: 5px;
  background: var(--panel-raised);
  color: var(--text);
  text-decoration: none;
  cursor: pointer;
  /* The plate is drawn taller than it measures, so the row it sits in owes
     that height back — otherwise the edge hangs past everything beside it. */
  margin-bottom: var(--plate-edge);
  box-shadow: 0 var(--plate-edge) 0 0 var(--btn-edge);
  transition: transform 0.1s var(--ease),
              box-shadow 0.1s var(--ease),
              background 0.15s var(--ease);
}
.btn:hover { background: color-mix(in srgb, var(--panel-raised) 78%, var(--text)); }
.btn:active {
  transform: translateY(var(--plate-edge));
  box-shadow: 0 0 0 0 var(--btn-edge);
}
.btn[disabled] {
  opacity: 0.4;
  cursor: default;
  transform: none;
  box-shadow: 0 var(--plate-edge) 0 0 var(--btn-edge);
}

.btn--primary {
  --btn-edge: var(--brand-edge);
  background: var(--brand);
  color: var(--bg);
}
.btn--primary:hover { background: color-mix(in srgb, var(--brand) 86%, #fff); }

.btn--danger {
  --btn-edge: #5E2A2A;
  background: #43201F;
  color: var(--danger);
}
.btn--danger:hover { background: #542827; }

.btn--small { padding: 7px 12px; font-size: 11.5px; min-height: 34px; }

/* --------------------------------------------------------------------------
   Flash messages
   -------------------------------------------------------------------------- */
.flash { display: grid; gap: 10px; margin-bottom: 24px; }
.flash__item {
  display: flex;
  gap: 11px;
  padding: 12px 15px;
  border-radius: 2px;
  font-size: 13px;
  border: 1px solid var(--line-bright);
  background: var(--panel);
}
.flash__item::before { content: ''; width: 3px; border-radius: 2px; flex: none; }
.flash__item--ok::before    { background: var(--ok); box-shadow: 0 0 10px var(--ok); }
.flash__item--error::before { background: var(--danger); box-shadow: 0 0 10px var(--danger); }
.flash__item--error { border-color: var(--danger-dim); }

/* --------------------------------------------------------------------------
   Cat list
   -------------------------------------------------------------------------- */
.cat-rows { display: grid; gap: 10px; margin: 0; padding: 0; list-style: none; }

/* Each row carries its cat's colour as a hard left edge over a lightly
   tinted fill — enough to find a cat at a glance, without the gradient
   border that made these read as generic cards. */
.cat-row {
  display: grid;
  grid-template-columns: 56px 1fr auto;
  align-items: center;
  gap: 16px;
  padding: 12px 16px 12px 12px;
  border: 1px solid var(--line);
  border-left: 2px solid var(--eye-c);
  border-radius: 2px;
  background: linear-gradient(100deg,
    color-mix(in srgb, var(--eye-c) 8%, var(--panel)) 0%,
    var(--panel) 46%);
  transition: transform 0.15s var(--ease), box-shadow 0.15s var(--ease);
}
.cat-row:hover {
  transform: translateX(2px);
  box-shadow: 0 0 26px -14px var(--eye-c);
}
.cat-row__thumb {
  width: 56px; height: 56px;
  border-radius: 2px;
  overflow: hidden;
  background: var(--panel-inset);
  display: grid;
  place-items: center;
}
.cat-row__thumb img { width: 100%; height: 100%; object-fit: cover; }
.cat-row__thumb span {
  width: 10px; height: 10px; border-radius: 50%;
  background: var(--eye-c); box-shadow: 0 0 8px var(--eye-c);
}
.cat-row__name {
  font-family: var(--font-display);
  font-size: 17px;
  font-weight: 500;
  color: var(--text-strong);
  margin: 0 0 2px;
}
.cat-row__meta { font-size: 11.5px; color: var(--text-faint); }
.cat-row__meta code { color: var(--text-dim); }
.cat-row__actions { display: flex; gap: 8px; flex-wrap: wrap; justify-content: flex-end; }
.cat-row__order { display: flex; flex-direction: column; gap: 3px; }

/* --------------------------------------------------------------------------
   Form
   -------------------------------------------------------------------------- */
/* One colour, hard corners — the same frame language as the public profile
   panel. The rainbow gradient border this replaces read as a generic dark
   card with decoration applied. */
.form-card {
  position: relative;
  border: 1px solid color-mix(in srgb, var(--eye-c) 24%, transparent);
  border-radius: 2px;
  overflow: hidden;
  background: linear-gradient(168deg, #0D1517 0%, #0A1012 100%);
  box-shadow:
    0 22px 56px -32px rgba(0, 0, 0, 0.95),
    inset 0 0 60px -34px color-mix(in srgb, var(--eye-c) 40%, transparent);
}
.form-section { padding: 24px 26px; border-bottom: 1px solid var(--line); }
.form-section:last-of-type { border-bottom: none; }
.form-section > h2 {
  font-family: var(--font-mono);
  font-size: 10.5px;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  color: var(--text-faint);
  font-weight: 500;
  margin: 0 0 18px;
}

.field-grid {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(220px, 1fr));
  gap: 18px;
}
.field { display: flex; flex-direction: column; gap: 7px; min-width: 0; }
.field--full { grid-column: 1 / -1; }

.field label {
  font-size: 10.5px;
  letter-spacing: 0.09em;
  text-transform: uppercase;
  color: var(--text-faint);
}
.field__hint { font-size: 11.5px; color: var(--text-faint); line-height: 1.5; }

.caption-row input[type='text'],
.field input[type='text'],
.field input[type='date'],
.field input[type='password'],
.field input[type='file'],
.field select,
.field textarea {
  font-family: var(--font-mono);
  /* 16px, not smaller: below this, iOS Safari zooms the whole page in on
     focus rather than just showing the keyboard — the exact size read back
     as 13.5px until this was found. */
  font-size: 16px;
  padding: 11px 12px;
  background: var(--panel-inset);
  border: 1px solid var(--line);
  border-radius: 2px;
  color: var(--text);
  width: 100%;
}

/* Native select chrome is light-on-light in a dark theme, so draw our own
   arrow and keep the control on the panel background. The arrow is an inline
   data: URI rather than a background-image file — one less request, and
   img-src in the CSP already allows data:. */
.field select {
  appearance: none;
  -webkit-appearance: none;
  padding-right: 34px;
  cursor: pointer;
  background-image: url("data:image/svg+xml;charset=utf-8,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 10 6'%3E%3Cpath d='M1 1l4 4 4-4' fill='none' stroke='%236E8482' stroke-width='1.5' stroke-linecap='round'/%3E%3C/svg%3E");
  background-repeat: no-repeat;
  background-position: right 12px center;
  background-size: 10px 6px;
}
.field select:focus { border-color: var(--eye-c); }
/* The dropdown list itself is drawn by the OS; this is the most we can style. */
.field select option { background: var(--panel); color: var(--text); }
.field textarea {
  font-family: var(--font-display);
  font-size: 16px;
  line-height: 1.65;
  resize: vertical;
  min-height: 130px;
}
.field input::placeholder, .field textarea::placeholder, .caption-row input::placeholder { color: var(--text-faint); }
.field input:focus, .field textarea:focus, .caption-row input:focus { border-color: var(--eye-c); }

/* Native date pickers render a dark-on-dark icon otherwise. */
.field input[type='date']::-webkit-calendar-picker-indicator { filter: invert(0.7); cursor: pointer; }

.field--invalid input, .field--invalid textarea { border-color: var(--danger); }
.field__error { font-size: 11.5px; color: var(--danger); }

/* Colour swatches: real radio inputs, so they are keyboard- and
   screen-reader-navigable rather than clickable divs. */
.swatches { display: flex; flex-wrap: wrap; gap: 10px; }
.swatch { position: relative; }
.swatch input {
  position: absolute; inset: 0; opacity: 0; margin: 0; cursor: pointer;
}
/* The swatch reads its own accent class (see site.css), so no colour is
   hard-coded here and none is emitted inline. */
.swatch span {
  display: grid;
  place-items: center;
  width: 32px; height: 32px;
  border-radius: 50%;
  border: 2px solid transparent;
  background: var(--eye-c);
  box-shadow: 0 0 10px var(--eye-c), inset 0 0 0 2px var(--bg);
  transition: transform 0.15s var(--ease), border-color 0.15s var(--ease);
}
.swatch input:checked + span { border-color: var(--text-strong); transform: scale(1.08); }
.swatch input:focus-visible + span { outline: 2px solid var(--text-strong); outline-offset: 3px; }

/* Photo */
.photo-field { display: flex; flex-wrap: wrap; gap: 20px; align-items: flex-start; }
.photo-preview {
  width: 148px; height: 148px;
  border: 1px solid var(--line);
  border-radius: 2px;
  background: var(--panel-inset);
  overflow: hidden;
  display: grid;
  place-items: center;
  flex: none;
}
.photo-preview img { width: 100%; height: 100%; object-fit: cover; }
.photo-preview__empty { font-size: 10.5px; letter-spacing: 0.1em; text-transform: uppercase; color: var(--text-faint); }
.photo-controls { flex: 1 1 260px; display: flex; flex-direction: column; gap: 12px; min-width: 0; }

.form-actions {
  display: flex;
  flex-wrap: wrap;
  gap: 12px;
  align-items: center;
  padding: 20px 26px;
  background: var(--panel-raised);
  border-top: 1px solid var(--line);
}
.form-actions .spacer { flex: 1 1 auto; }

/* The standalone "remove photo" strip below the main form. */
.photo-remove-bar {
  margin-top: 14px;
  border: 1px solid var(--line);
  border-radius: var(--radius-lg);
  padding: 16px 20px;
}

/* Native disclosure — no JavaScript, which the CSP would block anyway (see
   Security::sendHeaders()). A one-click delete was too easy to trigger by
   accident on an unrepeatable action; this asks for a second, deliberate
   click on a button that only appears once you have opened the panel. */
.confirm-delete > summary {
  cursor: pointer;
  list-style: none;
}
.confirm-delete > summary::-webkit-details-marker { display: none; }
.confirm-delete__panel {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 10px;
  margin-top: 12px;
}
.confirm-delete__panel form { margin: 0; }
.confirm-delete__panel .spacer { flex: 1 1 auto; }

/* --------------------------------------------------------------------------
   Delete confirmation
   -------------------------------------------------------------------------- */
.danger-zone {
  margin-top: 28px;
  border: 1px solid var(--danger-dim);
  border-radius: var(--radius-lg);
  background: color-mix(in srgb, var(--danger) 4%, var(--panel));
  padding: 22px 26px;
}
.danger-zone :is(h2, h3) {
  font-family: var(--font-mono);
  font-size: 10.5px;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  color: var(--danger);
  margin: 0 0 10px;
  font-weight: 500;
}
.danger-zone p { font-size: 13px; color: var(--text-dim); margin: 0 0 16px; max-width: 62ch; }
.danger-zone form { display: flex; flex-wrap: wrap; gap: 10px; align-items: flex-end; }
.danger-zone .field { flex: 1 1 240px; }

/* --------------------------------------------------------------------------
   Login
   -------------------------------------------------------------------------- */
.login-wrap {
  position: relative;
  z-index: 2;
  min-height: 100vh;
  display: grid;
  place-items: center;
  padding: 32px 20px;
}
/*
 * There was a wash of coloured glow behind the card here, and its whole
 * reason was that the card would otherwise sit on flat black. The page is a
 * sky now, so that reason is gone — and the glow was bright enough to rub out
 * the stars on the half of the screen it covered, which looked like a fault
 * rather than like depth.
 *
 * The faint one that is left only lifts the card off its own background; it
 * is too weak to erase anything behind it.
 */
.login-wrap::before {
  content: '';
  position: absolute;
  inset: 0;
  pointer-events: none;
  z-index: -1;
  background: radial-gradient(40rem 30rem at 50% 45%,
    rgba(110, 231, 183, 0.05), transparent 70%);
}

/*
 * The card does not float — it stands on the page, with a thick brand edge
 * and a hard slab of shadow behind it instead of the usual soft blur. Every
 * surface inside it is flat; depth comes only from offset, which is the one
 * trick the whole screen is built on.
 *
 * The slab is drawn twice: a brand-coloured plate, and a near-black rim a
 * touch larger, so the plate keeps a defined edge where it crosses the
 * coloured wash behind the card rather than dissolving into it.
 */
.login-card {
  position: relative;
  width: 100%;
  max-width: 420px;
  padding: 32px 30px;
  border: 3px solid var(--brand);
  border-radius: 4px;
  background: #0B1214;
  box-shadow:
    14px 14px 0 -3px var(--brand-edge),
    14px 14px 0  0   #04090A;
}
.login-card h1 {
  font-family: var(--font-display);
  font-size: 23px;
  font-weight: 600;
  letter-spacing: -0.02em;
  color: var(--text-strong);
  margin: 0 0 26px;
}

/*
 * The sign-in heading is the wordmark, centred over the card.
 *
 * The heading is a plain block so the text-align can reach it; the wordmark
 * stays inline-flex inside it, which is what keeps its rule the width of the
 * words rather than the width of the card. Everything else about it — the
 * chip, the pupil, the type — comes from .wordmark unchanged.
 */
.login-mark { text-align: center; margin: 0 0 26px; }
.wordmark--login {
  font-size: 30px;
  margin: 0;
  border-bottom-color: var(--line-bright);
}
/* Used by the 2FA screens, which do need a line of explanation. The login
   page deliberately carries none — it says nothing about whose site it is. */
.login-card__sub { font-size: 12.5px; color: var(--text-faint); margin: -18px 0 24px; line-height: 1.55; }
.login-card .field + .field { margin-top: 20px; }
.login-card__back { display: block; margin-top: 20px; text-align: center; font-size: 11.5px; color: var(--text-faint); text-decoration: none; }
.login-card__back:hover { color: var(--text-dim); }

/*
 * Fields on the sign-in screens only. Elsewhere in the admin a form is a
 * working surface with a dozen inputs on it and this would be far too loud;
 * here there are two, and they are the whole page.
 *
 * The label is a chip standing on the top edge of its own input, so the pair
 * reads as one labelled object rather than as text that happens to sit above
 * a box.
 */
.login-card .field { position: relative; gap: 0; }
.login-card .field label {
  align-self: flex-start;
  position: relative;
  z-index: 2;
  margin: 0 0 -3px 12px;
  padding: 4px 9px;
  border: 2px solid var(--brand-edge);
  border-radius: 3px;
  background: var(--brand);
  color: var(--bg);
  font-weight: 700;
}
.login-card .field input {
  position: relative;
  z-index: 1;
  padding: 13px 12px;
  border: 2px solid var(--brand-edge);
  border-radius: 3px;
  background: var(--panel-inset);
  transition: transform 0.2s var(--ease),
              box-shadow 0.2s var(--ease),
              border-color 0.2s var(--ease),
              background 0.2s var(--ease);
}
/* Pointed at or typed into, the input steps off the card towards you. */
.login-card .field input:hover,
.login-card .field input:focus {
  background: var(--panel);
  border-color: var(--brand);
  transform: translate(-3px, -3px);
  box-shadow: 3px 3px 0 0 var(--brand-edge);
}

/* The button is the same plate as the public navigation: solid, standing on
   its own edge, and pressed flat when you push it. */
.login-card .btn {
  width: 100%;
  justify-content: center;
  margin-top: 26px;
  border: none;
  border-radius: 4px;
  background: var(--brand);
  color: var(--bg);
  font-weight: 700;
  text-transform: uppercase;
  letter-spacing: 0.08em;
  padding-block: 13px;
  box-shadow: 0 5px 0 0 var(--brand-edge);
  transition: transform 0.1s var(--ease),
              box-shadow 0.1s var(--ease),
              background 0.15s var(--ease);
}
.login-card .btn:hover { background: color-mix(in srgb, var(--brand) 86%, #fff); }
.login-card .btn:active {
  transform: translateY(5px);
  box-shadow: 0 0 0 0 var(--brand-edge);
}

.login-error {
  display: flex;
  gap: 10px;
  font-size: 12.5px;
  color: var(--danger);
  border: 2px solid var(--danger);
  background: rgba(255, 107, 107, 0.08);
  border-radius: 3px;
  box-shadow: 5px 5px 0 0 rgba(122, 58, 58, 0.55);
  padding: 11px 13px;
  margin-bottom: 24px;
}

@media (max-width: 720px) {
  .cat-row { grid-template-columns: 44px 1fr; }
  .cat-row__thumb { width: 44px; height: 44px; }
  .cat-row__actions { grid-column: 1 / -1; justify-content: flex-start; }
}

/* --------------------------------------------------------------------------
   Two-factor
   -------------------------------------------------------------------------- */

/* One-time codes read as digits, not prose: wide tracking, tabular figures. */
.code-input {
  font-size: 22px !important;
  letter-spacing: 0.42em;
  text-align: center;
  font-variant-numeric: tabular-nums;
  padding-block: 14px !important;
}

.form-actions--bare {
  padding-inline: 0;
  background: none;
  border: none;
}

/* The secret, sized to be copied off the screen by hand without errors. */
.secret-box {
  border: 1px solid var(--line-bright);
  border-radius: var(--radius);
  background: var(--panel-inset);
  padding: 18px 20px;
  margin-bottom: 18px;
}
.secret-box__value {
  display: block;
  font-family: var(--font-mono);
  font-size: 19px;
  letter-spacing: 0.14em;
  color: var(--eye-c);
  word-break: break-all;
  line-height: 1.7;
}
.secret-box__note { font-size: 11.5px; color: var(--text-faint); margin: 10px 0 0; }
.secret-box__uri {
  display: block;
  font-size: 11.5px;
  color: var(--text-dim);
  background: var(--panel-inset);
  border: 1px solid var(--line);
  border-radius: 2px;
  padding: 10px 12px;
  word-break: break-all;
  line-height: 1.6;
}

/* Native disclosure — no JavaScript, which the CSP would block anyway. */
.recovery-toggle { margin-top: 22px; border-top: 1px solid var(--line); padding-top: 16px; }
.recovery-toggle > summary {
  cursor: pointer;
  font-size: 12.5px;
  color: var(--text-dim);
  list-style: none;
}
.recovery-toggle > summary::-webkit-details-marker { display: none; }
.recovery-toggle > summary::before { content: '+ '; color: var(--text-faint); }
.recovery-toggle[open] > summary::before { content: '– '; }
.recovery-toggle > summary:hover { color: var(--text); }
.recovery-toggle .field { margin-top: 12px; }
.recovery-toggle .btn { margin-top: 12px; }

/* On/off state line. */
.status-line {
  display: flex;
  align-items: center;
  gap: 10px;
  font-size: 13.5px;
  margin: 0 0 14px;
  color: var(--text);
}
.status-dot { width: 8px; height: 8px; border-radius: 50%; flex: none; }
.status-line--on  .status-dot { background: var(--ok);     box-shadow: 0 0 9px var(--ok); }
.status-line--off .status-dot { background: var(--danger); box-shadow: 0 0 9px var(--danger); }

/* Recovery codes, shown exactly once. Loud on purpose — this is the only
   chance to write them down. */
.codes-panel {
  border: 1px solid var(--eye-c);
  border-radius: var(--radius-lg);
  background: color-mix(in srgb, var(--eye-c) 6%, var(--panel));
  padding: 24px 26px;
  margin-bottom: 26px;
}
.codes-panel h2 {
  font-family: var(--font-display);
  font-size: 18px;
  font-weight: 600;
  color: var(--text-strong);
  margin: 0 0 10px;
}
.codes-panel p { font-size: 13px; color: var(--text-dim); margin: 0 0 18px; max-width: 64ch; }
.codes-panel strong { color: var(--text-strong); }
.codes-grid {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(160px, 1fr));
  gap: 8px;
  list-style: none;
  margin: 0;
  padding: 0;
}
.codes-grid code {
  display: block;
  background: var(--panel-inset);
  border: 1px solid var(--line);
  border-radius: 2px;
  padding: 10px 12px;
  font-size: 15px;
  letter-spacing: 0.08em;
  color: var(--text-strong);
  text-align: center;
}

/* Sign-out control on the challenge screen is a form button, not a link. */
.login-card__back--button {
  width: 100%;
  background: none;
  border: none;
  cursor: pointer;
  font-family: var(--font-mono);
  padding: 0;
}

/* Marks which language a field belongs to on the cat form. */
.lang-tag {
  display: inline-block;
  margin-left: 6px;
  padding: 1px 5px;
  border: 1px solid var(--line-bright);
  border-radius: 2px;
  font-size: 9px;
  letter-spacing: 0.1em;
  color: var(--text-dim);
  vertical-align: 1px;
}

/* --------------------------------------------------------------------------
   Album management
   -------------------------------------------------------------------------- */
.album__intro { max-width: 62ch; margin-bottom: 18px; }
.album__empty { color: var(--text-faint); font-size: 13px; margin: 18px 0 0; }

.album-grid {
  list-style: none;
  margin: 22px 0 0;
  padding: 0;
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(148px, 1fr));
  gap: 12px;
}

.album-item {
  border: 1px solid var(--line);
  border-radius: var(--radius);
  overflow: hidden;
  background: var(--panel);
}

.album-item__img {
  width: 100%;
  aspect-ratio: 4 / 3;
  object-fit: cover;
  display: block;
}

/* Two rows, stacked — not one wrapping row. A jump-to-position control plus
   four icon buttons does not fit a 148px-wide card on one line; letting them
   wrap freely split related controls unpredictably across two lines instead
   of a clean, always-in-the-same-place layout. */
.album-item__bar {
  display: flex;
  flex-direction: column;
  gap: 6px;
  padding: 8px;
  border-top: 1px solid var(--line);
}
.album-item__bar form { margin: 0; }

.album-item__row {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 6px;
}
.album-item__row--jump { padding-bottom: 6px; border-bottom: 1px dashed var(--line); }

/* The delete confirmation drops to its own full-width line within its row
   when open, rather than trying to fit its "yes, remove" button into the
   same narrow row as the other icons. */
.album-item__row .confirm-delete { margin: 0; }
.album-item__row .confirm-delete[open] { flex: 1 0 100%; order: 1; }
.album-item__row .confirm-delete__panel { margin-top: 8px; }

.album-item__n {
  font-family: var(--font-mono);
  font-size: 11px;
  color: var(--text-faint);
  margin-right: 2px;
}

/* The "jump to position" control, only shown on an album long enough that
   the up/down arrows alone would take a while. */
.album-item__jump { display: inline-flex; gap: 4px; }
.album-item__jump input {
  width: 44px;
  min-width: 0;
  font-family: var(--font-mono);
  font-size: 14px;
  padding: 6px 4px;
  text-align: center;
  background: var(--panel-inset);
  border: 1px solid var(--line);
  border-radius: 2px;
  color: var(--text);
}
.album-item__jump input:focus { border-color: var(--eye-c); }

/* Square, quiet buttons — there are three or four per photo and they must not
   shout. Still a real tap target though: these used to be under 20px tall
   (min-width 26px, padding 2px 6px), the smallest touch targets in the admin
   UI by a wide margin. */
.btn--icon {
  min-width: 36px;
  min-height: 36px;
  padding: 6px 8px;
  font-size: 14px;
  line-height: 1.3;
}
.btn--icon[disabled] { opacity: 0.3; cursor: not-allowed; }
.btn--danger-quiet:hover:not([disabled]) {
  border-color: var(--danger);
  color: var(--danger);
}

/* Read back a code that was set earlier — it is meant to be copied out, not
   hidden, since it is a maintenance sign rather than a credential. */
.lock-code {
  font-family: var(--font-mono);
  font-size: 14px;
  letter-spacing: 0.24em;
  color: var(--text-strong);
  background: var(--panel-inset);
  border: 1px solid var(--line-bright);
  border-radius: var(--radius);
  padding: 2px 8px 2px 10px;
}

/* The lock's own field sits in a normal form, not on a centred card. */
.code-input--inline { max-width: 220px; }

/* The GIF above the access code on the lock screen. Decorative, so its alt is
   empty — a screen reader announcing a joke here would only be noise between
   the heading and the field that matters. */
/* <picture> is inline by default; block stops a baseline gap under it. */
.lock-shot { display: block; }

.lock-gif {
  display: block;
  width: 100%;
  max-width: 260px;
  height: auto;
  margin: 0 auto 22px;
  border: 1px solid var(--line);
  border-radius: var(--radius);
}

/* How long the site-lock section stays open after a confirmation. */
.stepup-note { color: var(--text-faint); margin-top: 14px; }

/* --------------------------------------------------------------------------
   Traits — the shared vocabulary, and picking from it
   -------------------------------------------------------------------------- */
.trait-rows { list-style: none; margin: 20px 0 0; padding: 0; display: grid; gap: 10px; }

.trait-row {
  display: grid;
  grid-template-columns: 1fr 1fr 12ch;
  gap: 12px;
  align-items: end;
  padding-bottom: 10px;
  border-bottom: 1px solid var(--line);
}
.trait-row .field { margin: 0; }
.trait-row--new { opacity: 0.72; }
.trait-row--new:focus-within { opacity: 1; }

.trait-row__use {
  font-family: var(--font-mono);
  font-size: 11px;
  color: var(--text-faint);
  padding-bottom: 10px;
}

.trait-picker {
  list-style: none;
  margin: 0 0 16px;
  padding: 0;
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(200px, 1fr));
  gap: 6px 16px;
}

.trait-pick {
  display: flex;
  align-items: center;
  gap: 8px;
  font-size: 13.5px;
  cursor: pointer;
  padding: 3px 0;
}

@media (max-width: 620px) {
  .trait-row { grid-template-columns: 1fr; gap: 8px; }
  .trait-row__use { padding-bottom: 0; }
}


/* --------------------------------------------------------------------------
   Failed sign-ins

   Deliberately not styled as an alarm. It reports a fact that is usually
   mundane — you mistyped your password twice — and only becomes interesting
   when the number is large, which the number itself conveys.
   -------------------------------------------------------------------------- */
.signins {
  margin: 0 0 22px;
  padding: 12px 16px;
  border: 1px solid var(--line);
  border-left: 2px solid var(--text-dim);
  border-radius: 2px;
  background: var(--panel-inset);
  font-size: 13px;
  color: var(--text-dim);
}
.signins p { margin: 0; }
.signins p + p { margin-top: 6px; }
.signins strong { color: var(--text-strong); font-variant-numeric: tabular-nums; }
.signins__old { color: var(--text-faint); }

/* --------------------------------------------------------------------------
   The hub

   The admin's front page. Cards rather than a list, because each one is a
   destination with a sentence explaining what it is for — a list of bare
   links would need that sentence somewhere else, or nowhere.
   -------------------------------------------------------------------------- */
.hub-heading {
  font-family: var(--font-display);
  font-size: 12px;
  letter-spacing: 0.1em;
  text-transform: uppercase;
  color: var(--text-faint);
  margin: 28px 0 10px;
}
.hub-heading:first-of-type { margin-top: 18px; }

.hub {
  list-style: none;
  margin: 0;
  padding: 0;
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(280px, 1fr));
  gap: 12px;
}

.hub-card {
  border: 1px solid var(--line);
  border-radius: 2px;
  background: var(--panel);
  padding: 16px 18px 14px;
  display: flex;
  flex-direction: column;
}
.hub-card h3 {
  margin: 0 0 6px;
  font-family: var(--font-display);
  font-size: 15px;
  color: var(--text-strong);
}
.hub-card p {
  margin: 0 0 14px;
  font-size: 13px;
  line-height: 1.55;
  color: var(--text-dim);
}
/* Pushed to the bottom so cards of different text lengths still line their
   buttons up with each other. */
.hub-card__actions {
  margin-top: auto;
  display: flex;
  flex-wrap: wrap;
  gap: 6px;
}

/* A .hub-notice counted failed sign-ins here. The count moved to its own page
   under Security, where there is room to say who and from where, so the box
   it was drawn in went with it. */

/* --------------------------------------------------------------------------
   The one thing on this page you might be here to undo

   A locked site is invisible to everyone but you, and the old notice said so
   in the same grey as everything else with a sentence of explanation after
   it. Red, and short: the state, and the way to change it.
   -------------------------------------------------------------------------- */
.lock-banner {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 8px 16px;
  margin: 0 0 20px;
  padding: 14px 18px;
  border: 1px solid var(--danger);
  border-radius: 6px;
  background: rgba(255, 107, 107, 0.09);
  box-shadow: 0 0 22px -6px rgba(255, 107, 107, 0.35);
  font-size: 14px;
  color: var(--danger);
}
.lock-banner strong { color: var(--danger); font-weight: 700; }
.lock-banner a { color: var(--text-strong); }

/* --------------------------------------------------------------------------
   The dashboard

   One row for the way out to the site, then the settings side by side. Two
   columns rather than one so both fit above the fold on a laptop, which is
   the whole point of putting them on the same page.
   -------------------------------------------------------------------------- */
.admin-top {
  display: flex;
  justify-content: center;
  margin: 0 0 22px;
}

/* Bigger than a form button, because it is the only thing on its row and the
   thing most often reached for. */
.btn--wide { padding: 12px 34px; font-size: 13.5px; }

.admin-grid {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(320px, 1fr));
  gap: 18px;
  align-items: start;
  margin-bottom: 34px;
}

/* .form-card carries no padding of its own — it is normally a stack of
   .form-sections that each bring theirs. Used directly, it needs some. */
.form-card--pad { padding: 22px 24px 24px; }
.form-card--pad > h2 {
  font-family: var(--font-mono);
  font-size: 10.5px;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  color: var(--text-faint);
  font-weight: 500;
  margin: 0 0 12px;
}
.form-card--pad .switches { margin: 4px 0 18px; }
.form-card--pad .field + .field { margin-top: 14px; }

/* --------------------------------------------------------------------------
   Switches and narrow fields
   -------------------------------------------------------------------------- */
/* --------------------------------------------------------------------------
   Checkboxes

   A native checkbox says "on" with a tick and "off" with an empty square, and
   an empty square is ambiguous at a glance: it reads as "not answered" as
   easily as "no". These say both answers out loud — a green tick or a red
   cross — so a column of them can be read without stopping to work out which
   ones are blank.

   The box is the same pressable plate as the buttons: it stands on an edge
   and sinks onto it while you hold it down.
   -------------------------------------------------------------------------- */
.switches { list-style: none; margin: 0; padding: 0; }

.switch {
  padding: 4px 0;
  border-bottom: 1px solid var(--line);
}
.switch:last-child { border-bottom: none; }

.check {
  display: flex;
  align-items: center;
  gap: 12px;
  padding: 7px 0;
  cursor: pointer;
  color: var(--text);
}

/* The real control: invisible, but still the thing that is focused, clicked
   and read out. Nothing here is a fake checkbox made of divs. */
.check input {
  position: absolute;
  opacity: 0;
  width: 0;
  height: 0;
}

.check__box {
  position: relative;
  flex: none;
  width: 22px;
  height: 22px;
  border-radius: 5px;
  background: #43201F;                 /* off */
  box-shadow: 0 3px 0 0 #5E2A2A;
  transition: background 0.18s var(--ease),
              box-shadow 0.12s var(--ease),
              transform 0.12s var(--ease);
}
.check input:checked ~ .check__box {
  background: #123C2E;                 /* on */
  box-shadow: 0 3px 0 0 var(--brand-edge);
}

/* The tick: two sides of a square, turned. Hidden unless checked. */
.check__box::before {
  content: '';
  position: absolute;
  left: 7.5px;
  top: 4px;
  width: 6px;
  height: 11px;
  border: solid var(--brand);
  border-width: 0 2.5px 2.5px 0;
  transform: rotate(45deg);
  opacity: 0;
  transition: opacity 0.15s var(--ease);
}
.check input:checked ~ .check__box::before { opacity: 1; }

/* The cross: a plus made of two gradient bars, turned 45°. Shown when the
   tick is not. */
.check__box::after {
  content: '';
  position: absolute;
  inset: 0;
  background:
    linear-gradient(var(--danger), var(--danger)) center / 2.5px 12px no-repeat,
    linear-gradient(var(--danger), var(--danger)) center / 12px 2.5px no-repeat;
  transform: rotate(45deg);
  transition: opacity 0.15s var(--ease);
}
.check input:checked ~ .check__box::after { opacity: 0; }

.check:hover .check__box { background: color-mix(in srgb, #43201F 82%, #fff); }
.check:hover input:checked ~ .check__box { background: color-mix(in srgb, #123C2E 82%, #fff); }

.check input:active ~ .check__box {
  transform: translateY(3px);
  box-shadow: 0 0 0 0 transparent;
}

/* The control is invisible, so its focus ring belongs on the box it drives. */
.check input:focus-visible ~ .check__box {
  outline: 2px solid var(--eye-c);
  outline-offset: 2px;
}

.check__label { cursor: pointer; }

/* --------------------------------------------------------------------------
   Picks — a number chosen from a row of plates

   The one setting here that is a count used to be a spinner box: a narrow
   field with two arrows too small to hit, asking to be typed into. Nobody has
   a considered opinion about 23 versus 24, so the choice is offered as a short
   row of the same buttons the rest of the page is made of. The real control is
   a radio — this is a styled fieldset, not a widget made of divs — so it works
   with the keyboard and posts exactly as the box did.
   -------------------------------------------------------------------------- */
.picks-field {
  border: none;
  margin: 0;
  padding: 0;
  min-width: 0;                          /* fieldsets refuse to shrink otherwise */
}

/* A legend is a label, and should look like every other one on the page. */
.picks-field legend {
  padding: 0;
  margin-bottom: 9px;
  font-family: var(--font-mono);
  font-size: 11px;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: var(--text-faint);
}

.picks {
  list-style: none;
  display: flex;
  flex-wrap: wrap;
  gap: 8px;
  /* The plates stand on a 4px edge that the row owes back, so the gap to the
     hint below is smaller than it reads here. */
  margin: 0 0 8px;
  padding: 0;
}

.pick { display: block; }

/* The real radio: invisible, still focusable and still the thing clicked. */
.pick input {
  position: absolute;
  opacity: 0;
  width: 0;
  height: 0;
}

/* Same plate as .btn — same fill, radius, edge and press. */
.pick__face {
  display: flex;
  align-items: center;
  justify-content: center;
  min-width: 46px;
  padding: 9px 12px;
  font-family: var(--font-mono);
  font-size: 12.5px;
  font-weight: 600;
  /* A <button> gets `normal` from the browser and a <span> inherits the body's
     1.6, which is a three-pixel-taller plate standing beside the real ones. */
  line-height: normal;
  border-radius: 5px;
  background: var(--panel-raised);
  color: var(--text);
  cursor: pointer;
  margin-bottom: var(--plate-edge);
  box-shadow: 0 var(--plate-edge) 0 0 #0A1214;
  transition: transform 0.1s var(--ease),
              box-shadow 0.1s var(--ease),
              background 0.15s var(--ease);
}

.pick:hover .pick__face {
  background: color-mix(in srgb, var(--panel-raised) 78%, var(--text));
}

/* Held down: the plate sinks onto its edge, like every button here. */
.pick input:active ~ .pick__face {
  transform: translateY(var(--plate-edge));
  box-shadow: 0 0 0 0 #0A1214;
}

/* Chosen: the brand fill of a primary button, and pressed in rather than
   standing out — the same thing the nav does to say "you are here". */
.pick input:checked ~ .pick__face {
  background: var(--brand);
  color: var(--bg);
  cursor: default;
  transform: translateY(var(--plate-edge));
  box-shadow: inset 0 2px 3px 0 rgb(0 0 0 / 0.28);
}
/* The invisible radio is never itself hovered, so the hover lives on the
   label — and the chosen plate keeps its fill under the pointer, because it
   is already the answer and has nowhere to go. */
.pick:hover input:checked ~ .pick__face { background: var(--brand); }

.pick input:focus-visible ~ .pick__face {
  outline: 2px solid var(--eye-c);
  outline-offset: 2px;
}

/* --------------------------------------------------------------------------
   Album captions

   A row per photo: a thumbnail small enough to identify it and a field wide
   enough to write a sentence in. The thumbnail is decoration — the label is
   hidden text, because a visible one per row would be twenty repetitions of
   the same word.
   -------------------------------------------------------------------------- */
.caption-list { list-style: none; margin: 0 0 14px; padding: 0; }

.caption-row {
  display: flex;
  align-items: center;
  gap: 12px;
  padding: 7px 0;
  border-bottom: 1px solid var(--line);
}
.caption-row:last-child { border-bottom: none; }

.caption-row__thumb {
  width: 56px;
  height: 42px;
  object-fit: contain;
  background: var(--panel-inset);
  border: 1px solid var(--line);
  border-radius: 2px;
  flex: none;
}
.caption-row input { flex: 1 1 auto; min-width: 0; }

/* --------------------------------------------------------------------------
   The failed sign-in log

   A table, because it is a table: five short facts per row that are only
   worth anything read down a column. Rows of cards would hide the one thing
   the page is for — noticing that thirty of them share an address.

   It is the widest thing in the admin, so it scrolls inside its own box
   rather than making the page scroll sideways.
   -------------------------------------------------------------------------- */
.log-summary {
  margin: 0 0 16px;
  font-size: 13px;
  line-height: 1.6;
  color: var(--text-dim);
  max-width: 68ch;
}
.log-summary strong { color: var(--text-strong); }

.log-scroll {
  overflow-x: auto;
  border: 1px solid var(--line);
  border-radius: 3px;
  background: var(--panel);
}

.log {
  width: 100%;
  /* Below this the browser column is crushed to a word per line while the
     others keep their width, which is the one column worth reading. Narrower
     than this the box scrolls instead. */
  min-width: 660px;
  border-collapse: collapse;
  font-size: 12.5px;
}

/* A typed name can be any length and arrives from outside, so it wraps inside
   its column rather than widening the table for everything else. */
.log__name { max-width: 22ch; }
.log__name code { overflow-wrap: anywhere; }

.log th {
  position: sticky;
  top: 0;
  z-index: 1;
  text-align: left;
  font-family: var(--font-mono);
  font-size: 10.5px;
  font-weight: 500;
  letter-spacing: 0.12em;
  text-transform: uppercase;
  color: var(--text-faint);
  background: var(--panel-raised);
  padding: 10px 14px;
  border-bottom: 1px solid var(--line-bright);
  white-space: nowrap;
}

.log td {
  padding: 10px 14px;
  border-bottom: 1px solid var(--line);
  vertical-align: top;
  color: var(--text);
}
.log tbody tr:last-child td { border-bottom: none; }
.log tbody tr:hover td { background: var(--panel-inset); }

.log code {
  font-family: var(--font-mono);
  font-size: 12px;
  color: var(--text-strong);
}

.log__when, .log__ip { white-space: nowrap; }

/* The raw error log: free-form lines, not columns, so a table would be the
   wrong shape — this is the .log-scroll box's other occupant. */
.error-log {
  margin: 0;
  padding: 14px;
  font-family: var(--font-mono);
  font-size: 12px;
  line-height: 1.6;
  color: var(--text);
  white-space: pre-wrap;
  overflow-wrap: anywhere;
}

/* The door is a label, not prose: same chip as everywhere else in the admin. */
.log__door {
  display: inline-block;
  font-family: var(--font-mono);
  font-size: 10.5px;
  letter-spacing: 0.06em;
  text-transform: uppercase;
  color: var(--text-dim);
  background: var(--panel-inset);
  border: 1px solid var(--line-bright);
  border-radius: 2px;
  padding: 3px 7px;
  white-space: nowrap;
}

.log__muted { color: var(--text-faint); }

/* The raw user agent, under the reading of it. Long, so it is clamped to one
   line and carries the whole string in its title for anyone who wants it. */
.log__raw {
  display: block;
  max-width: 34ch;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
  font-family: var(--font-mono);
  font-size: 10.5px;
  color: var(--text-faint);
  margin-top: 3px;
}

@media (max-width: 720px) {
  /* The raw agent is the first thing to go when there is no room: it is the
     detail you go looking for, not the one you scan. */
  .log__raw { display: none; }
  .log th, .log td { padding: 9px 10px; }
}
