/* admin.wolfyk.com - keyboard-driven control console. Near-monochrome, monospace,
   self-contained (strict CSP: fonts from /static, no external assets, no inline).
   Colour is reserved strictly for status: green = OK, amber = warn, red = down. */

@font-face {
  font-family: "JetBrains Mono";
  font-style: normal;
  font-weight: 400;
  font-display: swap;
  src: url("fonts/jetbrains-mono-latin-400-normal.caf0dfde4e44.woff2") format("woff2");
}
@font-face {
  font-family: "JetBrains Mono";
  font-style: normal;
  font-weight: 500;
  font-display: swap;
  src: url("fonts/jetbrains-mono-latin-500-normal.a21e038a03f7.woff2") format("woff2");
}
@font-face {
  font-family: "JetBrains Mono";
  font-style: normal;
  font-weight: 700;
  font-display: swap;
  src: url("fonts/jetbrains-mono-latin-700-normal.69002db50656.woff2") format("woff2");
}

:root {
  --bg: #0a0c10;
  --surface: #101319;
  --surface-2: #161a22;
  --line: #242a33;
  --line-bright: #333b47;
  --ink: #e7ebf0;
  --ink-2: #9aa4b1;
  /* Lifted from #626c7a on 2026-08-28: that failed WCAG AA everywhere it was
     used - 3.27:1 on --surface-2, 3.49 on --surface, 3.68 on --bg, against a
     4.5 requirement - which axe reported as 20 violations on the Book page
     alone. Same hue, 23 points lighter on each channel; worst case is now
     4.54:1. This is the panel's muted ink, so it lightens section labels,
     table headers, .muted and .empty across every page. */
  --ink-3: #798391;
  --rail: rgba(255, 255, 255, 0.045);
  --rail-strong: rgba(255, 255, 255, 0.09);
  --ok: #35d07f;
  --warn: #e0a53a;
  --down: #ff5f57;
  --focus: #cdd6e2;
  --mono:
    "JetBrains Mono", ui-monospace, "SFMono-Regular", "Consolas", monospace;
}

* {
  box-sizing: border-box;
}
html {
  -webkit-text-size-adjust: 100%;
}
body {
  margin: 0;
  background: var(--bg);
  color: var(--ink);
  font-family: var(--mono);
  font-size: 14px;
  line-height: 1.6;
  font-variant-numeric: tabular-nums;
  -webkit-font-smoothing: antialiased;
}
a {
  color: var(--ink-2);
  text-decoration: underline;
  text-underline-offset: 3px;
  text-decoration-color: var(--line-bright);
}
a:hover {
  color: var(--ink);
  text-decoration-color: var(--ink-2);
}
h1,
h2,
h3 {
  font-weight: 700;
  letter-spacing: -0.01em;
  margin: 0;
}
.skip-link {
  position: absolute;
  left: -999px;
  top: 0;
  background: var(--ink);
  color: var(--bg);
  padding: 8px 14px;
  z-index: 200;
}
.skip-link:focus {
  left: 8px;
  top: 8px;
}
.muted {
  color: var(--ink-3);
}

/* ---- the keyboard cursor: focused nav items get the full-width bar ---- */
.kbd-item {
  outline: none;
}
.kbd-item:focus,
.kbd-item:focus-visible {
  outline: none;
  background: var(--rail-strong);
  color: var(--ink);
  box-shadow: inset 2px 0 0 var(--ink);
}

/* ============================ contents (home) ============================ */
.toc {
  max-width: 760px;
  margin: 0 auto;
  padding: 6vh 1.5rem 5rem;
}
.toc-head {
  margin-bottom: 2.4rem;
}
.mark {
  width: 44px;
  height: 44px;
  display: block;
  margin-bottom: 1.1rem;
}
.mark-box {
  fill: none;
  stroke: var(--ink-3);
  stroke-width: 2;
}
.mark-line {
  fill: none;
  stroke: var(--ink);
  stroke-width: 2.4;
  stroke-linecap: round;
  stroke-linejoin: round;
}
.toc-head h1 {
  font-size: 1.7rem;
  letter-spacing: -0.02em;
}
.toc-head .sub {
  color: var(--ink-3);
  margin: 0.4rem 0 0;
  text-transform: uppercase;
  letter-spacing: 0.16em;
  font-size: 0.72rem;
}
.toc-group {
  margin-top: 1.9rem;
}
.toc-group h2 {
  color: var(--ink-3);
  text-transform: uppercase;
  letter-spacing: 0.2em;
  font-size: 0.72rem;
  font-weight: 500;
  padding: 0 0 0.5rem 0.6rem;
}
.toc-group ul {
  list-style: none;
  margin: 0;
  padding: 0;
}
.toc-link {
  display: block;
  padding: 0.4rem 0.6rem;
  text-decoration: none;
  border-left: 2px solid transparent;
}
.toc-link .toc-label {
  color: var(--ink-2);
  text-decoration: underline;
  text-underline-offset: 3px;
  text-decoration-color: var(--line-bright);
}
.toc-link .toc-desc {
  display: block;
  color: var(--ink-3);
  font-size: 0.82rem;
  text-decoration: none;
}
.toc-link:hover .toc-label {
  color: var(--ink);
}
.toc-link:focus .toc-label {
  color: var(--ink);
}

/* ============================ inner-page shell ========================== */
.screen {
  max-width: 1180px;
  margin: 0 auto;
}
.crumb {
  padding: 0.9rem 1.5rem 0.4rem;
  color: var(--ink-3);
  font-size: 0.82rem;
}
.crumb .home {
  text-decoration: underline;
  text-underline-offset: 3px;
}
.crumb .csep {
  margin: 0 0.5rem;
  color: var(--line-bright);
}
.crumb .chere {
  color: var(--ink);
}
.content {
  padding: 0.6rem 1.5rem 4.5rem;
}
.page-head {
  margin: 0.5rem 0 1.4rem;
}
.page-head h1 {
  font-size: 1.4rem;
}
.page-head p {
  color: var(--ink-3);
  margin: 0.3rem 0 0;
}
.section {
  color: var(--ink-3);
  text-transform: uppercase;
  letter-spacing: 0.18em;
  font-size: 0.72rem;
  margin: 2rem 0 0.85rem;
  padding-bottom: 0.4rem;
  border-bottom: 1px solid var(--line);
}

/* The book pages are the one place in the console that is about writing rather
   than about the box, and the uppercasing was shouting the owner's own words back
   at them - a part called "The ground floor" arrived as "PART 1 - THE GROUND
   FLOOR". Sentence case there; wide-tracked capitals stay the console's furniture
   on every other page. Opted in per page via the screenmod block, not applied to
   .content, so the mode-line keeps its page chip either way.
   The tracking existed to keep capitals legible and only smears lowercase, and
   0.72rem in sentence case reads as a caption, so the section rule takes a little
   size and a lighter ink to hold its place in the hierarchy. */
.plain-labels .section {
  text-transform: none;
  letter-spacing: normal;
  font-size: 0.86rem;
  color: var(--ink-2);
}
.plain-labels .stat .l,
.plain-labels .tbl th,
.plain-labels .stack label {
  text-transform: none;
  letter-spacing: normal;
}

/* ============================ mode-line + help ========================= */
.modeline {
  position: fixed;
  left: 0;
  right: 0;
  bottom: 0;
  z-index: 40;
  display: flex;
  align-items: center;
  /* right-aligned so the bottom-left corner stays clear of the browser's
     link-hover URL preview, which otherwise overlaps the bar */
  justify-content: flex-end;
  gap: 0.7rem;
  height: 30px;
  padding: 0 1rem;
  background: var(--surface);
  border-top: 1px solid var(--line);
  color: var(--ink-3);
  font-size: 0.74rem;
  letter-spacing: 0.02em;
}
.modeline .ml-page {
  color: var(--bg);
  background: var(--ink-2);
  padding: 0 0.5rem;
  border-radius: 2px;
  text-transform: uppercase;
  letter-spacing: 0.08em;
  font-weight: 500;
}
.modeline .ml-op {
  color: var(--ink-2);
}
.modeline .ml-keys {
  color: var(--ink-3);
}
.modeline .ml-clock {
  color: var(--ink-2);
}
.help {
  position: fixed;
  inset: 0;
  z-index: 100;
  display: flex;
  align-items: center;
  justify-content: center;
  background: rgba(5, 6, 9, 0.7);
}
.help[hidden] {
  display: none;
}
.help-inner {
  background: var(--surface);
  border: 1px solid var(--line-bright);
  border-radius: 5px;
  padding: 1.4rem 1.6rem;
  min-width: 300px;
}
.help-inner h2 {
  font-size: 0.8rem;
  color: var(--ink-3);
  letter-spacing: 0.2em;
  margin-bottom: 0.9rem;
}
.help-inner dl {
  margin: 0;
  display: grid;
  gap: 0.35rem;
}
.help-inner dl div {
  display: flex;
  gap: 1rem;
}
.help-inner dt {
  color: var(--ink);
  min-width: 4.5rem;
}
.help-inner dd {
  margin: 0;
  color: var(--ink-2);
}
.help-inner .hint {
  color: var(--ink-3);
  font-size: 0.76rem;
  margin: 1rem 0 0;
}

/* ============================ components ================================ */
/* terminal-style status tag ([ ACTIVE ] / [ DOWN ]) - no glowing LEDs */
.tag {
  font-size: 0.72rem;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: var(--ink-3);
  white-space: nowrap;
}
.tag-ok {
  color: var(--ok);
}
.tag-warn {
  color: var(--warn);
}
.tag-down {
  color: var(--down);
}
.status-txt-ok {
  color: var(--ok);
}
.status-txt-warn {
  color: var(--warn);
}
.status-txt-down {
  color: var(--down);
}

.grid {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(300px, 1fr));
  gap: 1rem;
}
.card {
  background: var(--surface);
  border: 1px solid var(--line);
  border-radius: 4px;
}
.card.pad {
  padding: 1.05rem 1.15rem;
}
.svc {
  display: flex;
  flex-direction: column;
}
.svc-head {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 0.35rem 0.55rem;
  padding: 0.8rem 1.05rem;
  border-bottom: 1px solid var(--line);
}
.svc-head h2 {
  font-size: 0.92rem;
  font-weight: 500;
  /* Push the status/maintenance tags to the right on a wide head; when they no longer
     fit (narrow cards on mobile) flex-wrap drops them to their own line rather than
     overflowing off the edge - two tags used to run a status + [ MAINTENANCE ] pair
     ~65px past the card. */
  margin-right: auto;
}
.svc-head .tag {
  margin-left: 0;
}
.svc-body {
  padding: 0.85rem 1.05rem;
  display: flex;
  flex-direction: column;
  gap: 0.6rem;
  flex: 1;
}
.svc-body h2 {
  font-size: 0.95rem;
  font-weight: 500;
}
.svc-desc {
  color: var(--ink-2);
  margin: 0;
  font-size: 0.82rem;
}
/* A component service folded into its project card (airtemp-bet/auth, clonechat-llm):
   a subtle sub-block under the primary's controls, with its own status + buttons. */
.svc-unit {
  padding-top: 0.55rem;
  border-top: 1px dashed var(--line);
}
.svc-unit-head {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 0.35rem 0.5rem;
  margin-bottom: 0.45rem;
}
.svc-unit-name {
  font-size: 0.82rem;
  color: var(--ink-2);
}
.kv {
  display: flex;
  gap: 1.3rem;
  flex-wrap: wrap;
  font-size: 0.8rem;
  margin: 0;
}
.kv div {
  display: flex;
  gap: 0.4rem;
}
.kv dt {
  color: var(--ink-3);
  margin: 0;
}
.kv dd {
  margin: 0;
  color: var(--ink-2);
}
.actions {
  display: flex;
  gap: 0.45rem;
  flex-wrap: wrap;
  align-items: center;
  margin-top: auto;
  padding-top: 0.3rem;
}

/* `margin-top: auto` puts this at the bottom of a flex card, which is what it
   was built for - so it never needed space underneath. Used mid-page it butted
   straight into whatever came next: measured 0px against the publish notice and
   against both chapter tables on the Book page. Only applied when something
   actually follows, so an action row that really is the last thing in a card is
   unchanged. */
.actions:not(:last-child) {
  margin-bottom: 1.1rem;
}
/* Sets the destructive action apart from the rest of a row without moving it to
   its own line. margin-left:auto would push it to the far right, which on a wide
   page strands it; a fixed gap keeps it adjacent but visibly separate. It
   collapses at the wrap, so a narrow screen does not start a row with a blank. */
.actions__gap {
  width: 0.9rem;
}

.btn {
  font: inherit;
  font-size: 0.8rem;
  cursor: pointer;
  border-radius: 3px;
  padding: 0.4rem 0.8rem;
  border: 1px solid var(--line-bright);
  background: var(--surface-2);
  color: var(--ink);
  text-decoration: none;
  display: inline-flex;
  align-items: center;
  gap: 0.4rem;
}
.btn:hover {
  border-color: var(--ink-3);
  background: #1b212b;
}
.btn:focus,
.btn:focus-visible {
  outline: none;
  border-color: var(--focus);
  box-shadow: 0 0 0 2px rgba(205, 214, 226, 0.35);
  background: #1b212b;
}
.btn-warn {
  border-color: rgba(224, 165, 58, 0.5);
  color: var(--warn);
}
.btn-danger {
  border-color: rgba(255, 95, 87, 0.5);
  color: var(--down);
}
.btn-primary {
  justify-content: center;
  padding: 0.6rem 1.4rem;
}
/* Full width only inside the 440px .panel card (login, OTP, confirm), where the
   primary action is the whole point of the box. width:100% used to live on
   .btn-primary itself, which made Save on the full-width page forms an 1100px
   slab - a stretched button being one of the more obvious layout tells. */
.panel .btn-primary {
  width: 100%;
  padding: 0.6rem;
}
/* .stack is a column flex container, so it stretches its items to the full form
   width - a submit button included, whatever its own width says. Shrink it to its
   label everywhere except inside the card, where full width is the intent. */
.stack > .btn {
  align-self: start;
}
.panel .stack > .btn {
  align-self: stretch;
}
.btn-lock::after {
  content: "OTP";
  font-size: 0.6rem;
  color: var(--ink-3);
  border: 1px solid var(--line);
  border-radius: 2px;
  padding: 0 0.25rem;
  letter-spacing: 0.05em;
}
.inline {
  display: inline;
  margin: 0;
}
.rowlink {
  font-size: 0.78rem;
}
.linkbtn {
  background: none;
  border: none;
  color: var(--ink-2);
  font: inherit;
  cursor: pointer;
  padding: 0;
  text-decoration: underline;
  text-underline-offset: 3px;
}
.linkbtn:hover {
  color: var(--ink);
}

.stats {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(160px, 1fr));
  gap: 1rem;
}

/* Same shape as .actions, and the same fault: `gap` spaces the cards from each
   other and nothing spaces the row from what follows. On Traffic the per-site
   row sits directly above a table and the two touched - it only looked right
   elsewhere because a paragraph with its own margin happened to follow. Applied
   only when something follows, so a row that ends a card is unchanged. */
.stats:not(:last-child),
.tbl-wrap:not(:last-child),
.log:not(:last-child) {
  margin-bottom: 1.1rem;
}
.stat {
  background: var(--surface);
  border: 1px solid var(--line);
  border-radius: 4px;
  padding: 0.95rem 1.05rem;
}
.stat .n {
  display: block;
  font-size: 1.5rem;
  font-weight: 700;
  letter-spacing: -0.02em;
}
.stat .l {
  color: var(--ink-3);
  font-size: 0.72rem;
  text-transform: uppercase;
  letter-spacing: 0.06em;
}

.tbl-wrap {
  overflow-x: auto;
  border: 1px solid var(--line);
  border-radius: 4px;
}
table.tbl {
  width: 100%;
  border-collapse: collapse;
  font-size: 0.82rem;
}
.tbl th {
  text-align: left;
  color: var(--ink-3);
  text-transform: uppercase;
  letter-spacing: 0.08em;
  font-size: 0.7rem;
  font-weight: 500;
  padding: 0.55rem 0.85rem;
  background: var(--surface-2);
  border-bottom: 1px solid var(--line);
}
.tbl td {
  padding: 0.5rem 0.85rem;
  border-bottom: 1px solid var(--line);
  color: var(--ink-2);
  white-space: nowrap;
}
.tbl tr:last-child td {
  border-bottom: none;
}
.tbl tbody tr:hover td {
  background: var(--rail);
  color: var(--ink);
}
.tbl td.wrap {
  white-space: normal;
}
.result-ok {
  color: var(--ok);
}
.result-fail,
.result-otp_fail {
  color: var(--down);
}
.result-reject {
  color: var(--warn);
}

.log {
  background: #07090c;
  border: 1px solid var(--line);
  border-radius: 4px;
  padding: 0.85rem 1rem;
  overflow: auto;
  max-height: 60vh;
  font-size: 0.78rem;
  line-height: 1.55;
  color: var(--ink-2);
  white-space: pre;
  tab-size: 4;
}
.tabs {
  display: flex;
  flex-wrap: wrap;
  gap: 0.35rem;
  margin-bottom: 0.9rem;
}
.tabs a {
  text-decoration: none;
  padding: 0.32rem 0.7rem;
  border: 1px solid var(--line);
  border-radius: 3px;
  color: var(--ink-2);
  font-size: 0.78rem;
}
.tabs a:hover {
  border-color: var(--ink-3);
  color: var(--ink);
}
.tabs a[aria-current="page"] {
  background: var(--rail-strong);
  color: var(--ink);
  border-color: var(--line-bright);
}
.logfilter {
  display: flex;
  gap: 0.5rem;
  margin-bottom: 0.9rem;
  flex-wrap: wrap;
}
.logfilter .search {
  flex: 1;
  min-width: 180px;
}
.logfilter .lines {
  width: 90px;
}

.messages {
  list-style: none;
  margin: 0 0 1.2rem;
  padding: 0;
  display: flex;
  flex-direction: column;
  gap: 0.5rem;
}
.msg {
  padding: 0.6rem 0.9rem;
  border: 1px solid var(--line);
  border-left-width: 3px;
  border-radius: 3px;
  background: var(--surface);
  font-size: 0.82rem;
}
.msg-success {
  border-left-color: var(--ok);
}
.msg-error {
  border-left-color: var(--down);
}
.msg-warning {
  border-left-color: var(--warn);
}

.panel {
  max-width: 440px;
  margin: 8vh auto 0;
  background: var(--surface);
  border: 1px solid var(--line);
  border-radius: 5px;
  padding: 1.6rem 1.7rem;
}
.panel h1 {
  font-size: 1.15rem;
  margin-bottom: 0.3rem;
}
.panel .lede {
  color: var(--ink-3);
  font-size: 0.82rem;
  margin: 0 0 1.2rem;
}
.stack {
  display: flex;
  flex-direction: column;
  gap: 0.5rem;
  margin: 1rem 0;
}
.stack label {
  font-size: 0.74rem;
  color: var(--ink-2);
  text-transform: uppercase;
  letter-spacing: 0.08em;
}
/* Field defaults, wrapped in :where() so they carry ZERO specificity and any
   class can override them. Without it, `input[type="text"]` (element+attribute)
   outranks a single class, and `font` is a shorthand that resets size AND
   weight - which silently killed .otp-input's 1.4rem/700 and rendered the
   authenticator code at body size with only its letter-spacing applied. */
:where(
  input[type="text"],
  input[type="password"],
  input[type="number"],
  input[type="url"],
  select,
  textarea
) {
  font: inherit;
  padding: 0.55rem 0.7rem;
  background: #0b0e13;
  border: 1px solid var(--line-bright);
  border-radius: 3px;
  color: var(--ink);
}
input:focus,
select:focus,
textarea:focus {
  outline: none;
  border-color: var(--focus);
}
/* projects */
.plainlist {
  list-style: none;
  margin: 0;
  padding: 0;
}
.toc-link.archived .toc-label {
  color: var(--ink-3);
  text-decoration-color: var(--line);
}
.addrow {
  margin-top: 1.5rem;
}
.projrow {
  display: flex;
  align-items: baseline;
  gap: 1rem;
}
.projrow .toc-link {
  flex: 1 1 auto;
  min-width: 0;
}
.projrow-meta {
  flex: 0 0 auto;
  display: flex;
  align-items: baseline;
  gap: 1rem;
  white-space: nowrap;
}
.tag-repo {
  color: var(--ink-2);
  text-decoration: underline;
  text-underline-offset: 3px;
}
.tag-repo:hover,
.tag-repo:focus,
.tag-repo:focus-visible {
  color: var(--ink);
  outline: none;
}
.projrow .del {
  padding: 0.4rem 0.6rem;
  color: var(--down);
  font-size: 0.78rem;
  text-decoration: underline;
  text-underline-offset: 3px;
}
.projrow .del:hover,
.projrow .del:focus,
.projrow .del:focus-visible {
  color: var(--ink);
  outline: none;
}
.proj-form input,
.proj-form select,
.proj-form textarea {
  width: 100%;
}
.proj-form label {
  margin-top: 0.4rem;
}

/* A value the operator cannot change - a Droplet Project's slug, which belongs to
   project_files.py - shown as text. It used to be a disabled input: safe, but it
   read as an editable box that silently ignored you, because the panel's explicit
   input colours override the browser's greyed-out disabled styling. */
.proj-form .field-static {
  margin-top: 0.4rem;
  color: var(--ink-2);
  font-size: 0.85rem;
}
.proj-form .field-static code {
  color: var(--ink);
}
/* The picture rows on the book page. A fixed square with object-fit, so a wide
   screenshot and a tall photograph both read as the same kind of thumbnail and
   the row height does not jump between them. */
.picrow__thumb {
  width: 40px;
  height: 40px;
  object-fit: cover;
  border: 1px solid var(--line);
  display: block;
}
.readme-area {
  min-height: 340px;
  line-height: 1.5;
  resize: vertical;
}
/* A six-digit code, so a narrow field. This was scoped to .proj-form, which
   meant the delete pages - which use the same class in a .stack - rendered it
   full width: an 1100px box for six digits. */
.otp-form {
  width: 10rem;
  max-width: 100%;
  letter-spacing: 0.3em;
  text-align: center;
}
/* The picture about to be deleted. Capped, or a full-size photograph fills the
   screen and pushes the code field below the fold. */
.confirm-box__preview {
  display: block;
  max-width: 100%;
  max-height: 220px;
  width: auto;
  height: auto;
  margin-bottom: 0.7rem;
  border: 1px solid var(--line);
}
.otp-input {
  letter-spacing: 0.55em;
  text-align: center;
  font-size: 1.4rem;
  font-weight: 700;
  padding: 0.7rem;
}
.hint {
  color: var(--ink-3);
  font-size: 0.8rem;
}
.formerror {
  color: var(--down);
  font-size: 0.82rem;
}
.confirm-box {
  border: 1px solid var(--line-bright);
  border-left: 3px solid var(--warn);
  border-radius: 3px;
  background: #0b0e13;
  padding: 0.8rem 1rem;
  margin: 1rem 0;
}
.confirm-box .op {
  color: var(--ink);
  font-weight: 500;
}
.confirm-box .cmd {
  margin-top: 0.35rem;
}
.confirm-box code {
  color: var(--ink-2);
  word-break: break-all;
}
.confirm-box .cdesc {
  color: var(--ink-3);
  font-size: 0.8rem;
  margin-top: 0.4rem;
}
.empty {
  color: var(--ink-3);
  border: 1px dashed var(--line-bright);
  border-radius: 4px;
  padding: 1.6rem 1rem;
  text-align: center;
}

@media (max-width: 720px) {
  .modeline .ml-keys,
  .modeline .ml-op {
    display: none;
  }
}
/* Narrow screens: stack a project row's meta column under its name.
   The meta is nowrap and never shrinks, so on a phone it starves the name
   beside it - worst on Archived rows, which carry an extra [ INACTIVE ] tag.
   A domain name is one unbreakable token, so the name then overflows and
   paints on top of the tags instead of wrapping. Full-width name + tags
   underneath keeps both readable. */
@media (max-width: 560px) {
  .projrow {
    flex-wrap: wrap;
    gap: 0.15rem 1rem;
  }
  .projrow .toc-link {
    flex: 1 1 100%;
  }
  .projrow-meta {
    flex-wrap: wrap;
    /* line the tags up with the name: .toc-link insets it by its 2px
       transparent border plus 0.6rem of padding */
    padding: 0 0.6rem 0.4rem calc(0.6rem + 2px);
  }
  /* .del brings its own 0.6rem inset; drop it so it aligns like the tags
     (vertical padding stays, so the tap target keeps its height) */
  .projrow .del {
    padding-left: 0;
    padding-right: 0;
  }
  /* Stacked, a row is three lines - without this the gap between rows matches
     the gap inside one and the list reads as an undifferentiated stream. */
  .projrow + .projrow {
    margin-top: 0.9rem;
  }
}
/* Reference columns in the book's chapter table. Measured at 390px: the table
   is 684px wide inside a 342px wrapper, which put the Move buttons at x 440 -
   fully off screen, reachable only by discovering that the table scrolls
   sideways. Ordering chapters is what that page is for, so on a narrow screen
   slug, words and updated give up their space. Width-only, deliberately not the
   (pointer: coarse) block below: a touch laptop gets the wide layout and should
   keep every column. */
@media (max-width: 560px) {
  .tbl .col-aside {
    display: none;
  }
}
/* Even three columns wide the table ran 49px past a 390px screen, because the
   row's two action links sit side by side and each is 44px minimum. Stacked they
   fit, and each keeps its tap height; the separator only earns its place when
   they share a line. display: flex on the link (not on the cell - changing a
   cell's display would drop it out of the table's column layout). */
@media (max-width: 560px) {
  .tbl td.rowacts .kbd-item {
    display: flex;
  }
  .tbl td.rowacts .sep {
    display: none;
  }
}
/* Tap target: .del is only ~33px tall from its 0.4rem padding, under the 44px
   minimum for a fingertip. Grow it on phones and on any touch pointer (a touch
   tablet gets the wide layout but still has fingers). It is a flex item, so it
   is blockified and min-* applies; with the global border-box that pins the box
   at exactly 44px, independent of font metrics - sizing by padding alone lands
   a hair under. The padding keeps the text centred in the taller box.
   On the wide layout the row is align-items: baseline, so the taller box pushes
   the baseline down and a User Project row grows ~4px (56 -> 60). Harmless, and
   a mouse never sees it. */
@media (max-width: 560px), (pointer: coarse) {
  .projrow .del {
    min-height: 44px;
    min-width: 44px;
    padding-top: 0.75rem;
    padding-bottom: 0.75rem;
  }
  /* iOS Safari zooms the whole page when a focused field is under 16px, then
     leaves it zoomed - and the sign-in fields are the first thing you touch.
     :where() again, so this stays a default and .otp-input's larger size wins. */
  :where(
    input[type="text"],
    input[type="password"],
    input[type="number"],
    input[type="url"],
    select,
    textarea
  ) {
    font-size: 16px;
  }
  /* Controls a finger has to hit: the OTP-gated action buttons, the Logs source
     tabs, the per-service "logs ->" links on the Overview, the Projects repo
     links, and the breadcrumb home link (the way back on every inner page). */
  .btn,
  .linkbtn,
  .tabs a,
  .rowlink,
  .tag-repo,
  .crumb .home,
  select {
    min-height: 44px;
    min-width: 44px;
    display: inline-flex;
    align-items: center;
  }
  /* Only .tag-repo is narrower than 44px naturally, so it is the one that gains
     width from the min - centre its label rather than leaving it left-hugging. */
  .tag-repo {
    justify-content: center;
  }
  /* Standalone links in plain lists (the Backups page's DigitalOcean and
     healthchecks.io links). These are the whole list item, not a link inside a
     sentence, so the inline-link exemption does not apply to them. */
  .content ul:not(.plainlist):not(.messages) a {
    min-height: 44px;
    display: inline-flex;
    align-items: center;
  }
  .tabs a {
    padding-top: 0;
    padding-bottom: 0;
  }
  /* The crumb's own top padding plus a 44px link would push every page down, so
     give the space to the link instead of the header. */
  .crumb {
    padding-top: 0.15rem;
    padding-bottom: 0;
  }
  /* The book manage page's table controls, which arrived after this block was
     written and so missed it: the four move buttons (.tbtn, used nowhere else)
     came out 31px tall with "up" only 34px wide, and the per-row chapter, write
     and source links were 17px tall. Reordering chapters from a phone is exactly
     what this page is for. td .kbd-item is only ever these links. */
  .tbtn,
  td .kbd-item {
    min-height: 44px;
    min-width: 44px;
    display: inline-flex;
    align-items: center;
    justify-content: center;
  }
}
@media (prefers-reduced-motion: reduce) {
  * {
    animation: none !important;
    transition: none !important;
  }
}

/* ---- Book editor ------------------------------------------------------- *\
   The surface is an iframe: the book's stylesheet sets body and :root styles, so
   loading it into this page would restyle the panel. The iframe isolates it and
   lets the editor show the real typography, which is the point of editing in
   place. Styles for inside the frame live in bookeditor.css.
\* ----------------------------------------------------------------------- */

.editor-meta {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(220px, 1fr));
  gap: 0.75rem 1rem;
}

.editor-field {
  display: flex;
  flex-direction: column;
  gap: 0.25rem;
}

.editor-field--wide {
  grid-column: 1 / -1;
}

.fieldnote {
  margin: 0;
  font-size: 0.75rem;
  color: var(--ink-2);
}

.toolbar {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 0.3rem;
  padding: 0.5rem 0;
  border-top: 1px solid var(--line);
  border-bottom: 1px solid var(--line);
}

.tbtn {
  padding: 0.3rem 0.55rem;
  background: transparent;
  color: var(--ink-2);
  border: 1px solid var(--line);
  border-radius: 0;
  font: inherit;
  font-size: 0.75rem;
  cursor: pointer;
}

.tbtn:hover {
  color: var(--ink);
  border-color: var(--line-bright);
}

.tsel {
  cursor: pointer;
}

.tsep {
  width: 1px;
  height: 1.2rem;
  background: var(--line);
}

.editor-surface {
  width: 100%;
  min-height: 68vh;
  border: 1px solid var(--line);
  background: #16161a;
}

.editor-status {
  margin: 0;
  min-height: 1.2em;
  font-size: 0.75rem;
  color: var(--ink-2);
}

.editor-status.has-problem {
  color: var(--warn);
}

.danger-link {
  color: var(--down);
}

.nowrap {
  white-space: nowrap;
}

/* Book page: the cover's own text. Not .kv - that is a flat flex row of pairs,
   right for "disk 42%" and wrong for a sentence-long tagline, which wrapped into
   a jumble between its own labels. */
.bookmeta {
  margin: 0.6rem 0 0;
}

.bookmeta__title {
  margin: 0;
  font-size: 0.95rem;
  color: var(--ink);
}

.bookmeta__tagline {
  margin: 0.15rem 0 0;
  font-size: 0.8rem;
  color: var(--ink-2);
}

/* The reasons a publish would be refused, inside one notice rather than a box
   each. */
.blockers {
  margin: 0.35rem 0 0;
  padding-left: 1.1rem;
}

.blockers li + li {
  margin-top: 0.2rem;
}
