/* ============================================================
   after.css — Pico-style semantic theme for After admin
   Write semantic HTML, get a styled dashboard. Minimal classes.

   CLASSES USED (the full list):
     Layout:    .stats-grid, .env-banner[data-env], .actions
     Tables:    td.actions
     Location:  .breadcrumbs
     Badges:    [data-status] on <mark>
     Buttons:   .secondary, .outline, .danger, .small
     Messages:  .messages, .message, .success/.warning/.error,
                .publish-problems
     Utility:   .mono, .shrink
     Sorting:   .drag-handle
     Filters:   .filter-bar, .filter-actions
     Log table: .log, .log-when/-who/-sev/-type/-what
     Devices:   .device-list, .device-group, .device-row, .device-name,
                .device-when, .device-facts, .boot-bars, .pseudonym
     Detail:    .device-title, .copy, .soc-chart, .chart-axis,
                .timeline, .timeline-when, .timeline-dot, .timeline-section
     Editor:    .audio-editor, .edit-fields, .editor-timeline, .transport,
                .clock, .meter, .grow, .check, .editor-heads, .ruler-head,
                .track-head, .editor-scroll, .clip-bar,
                .clip-fields, .clip-name, .gain, .clip-actions,
                .editor-foot, .keys, .hot

   Everything else: plain HTML elements.
   ============================================================ */


/* --- TOKENS ---
   light-dark() resolves against the color-scheme in effect, so each token
   holds both values and the theme switch is color-scheme alone. */
:root {
  color-scheme: light dark;

  /* The brand palette. Surfaces, text and accent map onto it, a step
     between two brand grays mixed from them; status and environment
     colors are the only hues outside it. */
  --white:         #ffffff;
  --black:         #000000;
  --orange:        #fa5005;
  --gray-50:       #f3f2f1;
  --gray-100:      #e1e0df;
  --gray-300:      #c1c0bd;
  --gray-500:      #979591;
  --gray-700:      #5c5a57;
  --gray-900:      #272625;

  --bg:            light-dark(var(--gray-50), color-mix(in srgb, var(--gray-900) 60%, var(--black)));
  --bg-card:       light-dark(var(--white), var(--gray-900));
  --bg-sidebar:    light-dark(var(--white), var(--black));
  --bg-muted:      light-dark(var(--gray-100), var(--black));
  --bg-overlay:    light-dark(rgba(0,0,0,.04), rgba(255,255,255,.05));
  --bg-active:     light-dark(rgba(0,0,0,.06), rgba(255,255,255,.12));

  --border:        light-dark(var(--gray-300), color-mix(in srgb, var(--gray-700) 50%, var(--gray-900)));
  --border-subtle: light-dark(var(--gray-100), color-mix(in srgb, var(--gray-700) 25%, var(--gray-900)));

  --text:          light-dark(var(--black), var(--gray-50));
  --text-2:        light-dark(var(--gray-700), var(--gray-300));
  /* 3:1 on white — decoration only, never text. Hairlines, separator dots,
     hover borders, the link underline, disabled text. */
  --text-3:        var(--gray-500);

  /* Orange is a mark — focus ring, current-page rule, drop target — never
     a fill under text: white on it is 3.4:1. Primary actions are black
     (white in dark mode) instead. */
  --accent:        var(--orange);
  --accent-ghost:  light-dark(rgba(250,80,5,.08), rgba(250,80,5,.12));
  --primary:       var(--text);
  --primary-hover: var(--text-2);
  --primary-fg:    var(--bg-card);

  /* Each colour clears 4.5:1 against its own -bg tint in both schemes. */
  --ok:            light-dark(#2f6b38, #5cb85c);
  --ok-bg:         light-dark(rgba(47,107,56,.08), rgba(92,184,92,.1));
  --warn:          light-dark(#7d5a07, #d4a84b);
  --warn-bg:       light-dark(rgba(125,90,7,.10), rgba(212,168,75,.1));
  --err:           light-dark(#b5332c, #e87f7b);
  --err-bg:        light-dark(rgba(181,51,44,.08), rgba(232,127,123,.1));
  --muted:         var(--text-2);
  --muted-bg:      var(--bg-active);

  --font:          system-ui, -apple-system, "Segoe UI", Helvetica, Arial, sans-serif;
  --font-mono:     ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
  --transition:    120ms ease;

  --text-xs:       0.75rem;    /* 12px — badges, column heads, meta */
  --text-sm:       0.8125rem;  /* 13px — dense secondary, small buttons */
  --text-base:     0.875rem;   /* 14px — tables, inputs, buttons, nav */
  --text-lg:       1rem;       /* 16px — prose, h3 */
  --text-xl:       1.25rem;    /* 20px — h2 */
  --text-2xl:      1.625rem;   /* 26px — h1, stat figures */
}


/* Absent data-theme leaves color-scheme at "light dark" — the OS decides. */
:root[data-theme="light"] { color-scheme: light; }
:root[data-theme="dark"]  { color-scheme: dark; }


/* ============================================================
   RESET & BASE
   ============================================================ */

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

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

html {
  /* Percentage, not px: keeps the reader's browser font-size in play. */
  font-size: 100%;
  -webkit-font-smoothing: antialiased;
}

body {
  font-family: var(--font);
  background: var(--bg);
  color: var(--text);
  line-height: 1.55;
  min-height: 100vh;
  display: grid;
  grid-template-columns: 220px 1fr;
}


/* ============================================================
   TYPOGRAPHY — element selectors
   ============================================================ */

h1, h2, h3, h4 { font-weight: 500; line-height: 1.25; }
h1 { font-size: var(--text-2xl);  letter-spacing: -0.01em; }
h2 { font-size: var(--text-xl); }
h3 { font-size: var(--text-lg); }
h4 {
  font-size: var(--text-xs);
  color: var(--text-2);
  text-transform: uppercase;
  letter-spacing: 0.08em;
  font-weight: 500;
}

p           { color: var(--text-2); }
small       { font-size: var(--text-xs); color: var(--text-2); }
strong      { font-weight: 600; }
a           { color: var(--text); text-decoration: underline; text-decoration-color: var(--text-3); text-underline-offset: 2px; text-decoration-thickness: 1px; transition: text-decoration-color var(--transition); }
a:hover     { text-decoration-color: var(--text); }
hr          { border: none; border-top: 1px solid var(--border); margin: 1.5rem 0; }

code {
  font-family: var(--font-mono);
  font-size: 0.875em;
  background: var(--bg-overlay);
  padding: 0.15em 0.35em;
}

pre {
  background: var(--bg-muted);
  border: 1px solid var(--border-subtle);
  padding: 1rem;
  overflow-x: auto;
  font-size: var(--text-base);
  margin-top: 0.75rem;
}
pre code { background: none; padding: 0; }

.mono { font-family: var(--font-mono); }

/* A table column only as wide as its content. */
.shrink { width: 1%; white-space: nowrap; }

/* The grip a sortable row is dragged by. */
.drag-handle { cursor: grab; user-select: none; color: var(--text-3); }

/* Kill transitions during theme switch */
.no-transition,
.no-transition *,
.no-transition *::before,
.no-transition *::after {
  transition: none !important;
}


/* ============================================================
   ENVIRONMENT BANNER
   <div class="env-banner" data-env="production|staging|deprecated|local">
   Fixed over every page, sign-in included, so no scroll position hides
   which deployment a change lands in. Same colors in both themes.
   ============================================================ */

body:has(> .env-banner) {
  --env-banner-height: 2.25rem;
  padding-top: var(--env-banner-height);
}

.env-banner {
  position: fixed;
  inset: 0 0 auto;
  z-index: 10;
  height: var(--env-banner-height);
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 0.4rem;
  padding: 0 1rem;
  font-size: var(--text-sm);
  white-space: nowrap;
  overflow: hidden;
  background: var(--gray-300);
  color: var(--black);
}

.env-banner strong {
  text-transform: uppercase;
  letter-spacing: 0.02em;
}

.env-banner span::before {
  content: "·";
  margin-right: 0.4rem;
}

/* White on this red is 6:1, on this purple 9:1; black on this yellow 13:1. */
.env-banner[data-env="production"] { background: #b5332c; color: var(--white); }
.env-banner[data-env="staging"]    { background: #f5c518; color: var(--black); }
.env-banner[data-env="deprecated"] { background: #6b21a8; color: var(--white); }

@media (max-width: 560px) {
  .env-banner span { display: none; }
}


/* ============================================================
   NAV (sidebar)
   Semantic: body > nav > header + details > (ul + footer)
   ============================================================ */

body > nav {
  background: var(--bg-sidebar);
  border-right: 1px solid var(--border);
  display: flex;
  flex-direction: column;
  position: sticky;
  top: var(--env-banner-height, 0px);
  height: calc(100vh - var(--env-banner-height, 0px));
  overflow-y: auto;
}

/* The icon sprite, referenced by <use>. */
body > nav > svg { display: none; }

body > nav svg {
  width: 1rem;
  height: 1rem;
  flex: none;
  fill: none;
  stroke: currentColor;
  stroke-width: 1.5;
  stroke-linecap: round;
  stroke-linejoin: round;
}

body > nav > header {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 0.5rem;
  min-height: 3.25rem;
  padding: 0 0.75rem 0 1.25rem;
  font-weight: 600;
  font-size: var(--text-lg);
}

body > nav > header > a {
  color: inherit;
  text-decoration: none;
}

body > nav > header small {
  font-size: inherit;
  font-weight: 400;
}

/* Icon-only buttons: the theme switch and sign-out. */
body > nav :is(header, footer) button {
  padding: 0.4rem;
  border-color: transparent;
  background: none;
  color: var(--text-2);
}

body > nav :is(header, footer) button:hover {
  border-color: var(--border);
  background: var(--bg-overlay);
  color: var(--text);
}

/* The theme switch shows the icon of the theme in effect. */
body > nav [data-theme-choice] svg { display: none; }
body > nav [data-theme-choice="system"] svg[data-for="system"],
body > nav [data-theme-choice="light"] svg[data-for="light"],
body > nav [data-theme-choice="dark"] svg[data-for="dark"] { display: block; }

/* On desktop: details fills sidebar, summary hidden.
   Style the internal ::details-content slot so the
   ul + footer layout fills the available height. */
body > nav > details {
  flex: 1;
}

body > nav > details::details-content {
  display: flex;
  flex-direction: column;
  height: 100%;
}

body > nav summary {
  display: none;
}

body > nav ul {
  list-style: none;
  flex: 1;
  padding: 0.25rem 0.625rem 1rem;
}

/* Section labels run into a rule across the sidebar. */
body > nav li > strong {
  display: flex;
  align-items: center;
  gap: 0.6rem;
  padding: 1.35rem 0.625rem 0.45rem;
  font-size: var(--text-xs);
  font-weight: 600;
  letter-spacing: 0.1em;
  text-transform: uppercase;
  color: var(--text-2);
}

body > nav li > strong::after {
  content: "";
  flex: 1;
  border-top: 1px solid var(--border);
}

body > nav li > a {
  display: flex;
  align-items: center;
  gap: 0.6rem;
  padding: 0.45rem 0.625rem;
  font-size: var(--text-base);
  color: var(--text-2);
  text-decoration: none;
  transition: background var(--transition), color var(--transition);
}

body > nav li > a svg {
  color: var(--text-3);
  transition: color var(--transition);
}

body > nav li > a:hover {
  color: var(--text);
  background: var(--bg-overlay);
}

body > nav li > a[aria-current="page"] {
  color: var(--text);
  font-weight: 500;
  background: var(--bg-active);
}

body > nav li > a[aria-current="page"] svg {
  color: var(--accent);
}

body > nav footer {
  display: flex;
  align-items: center;
  gap: 0.6rem;
  padding: 0.75rem 0.75rem 0.75rem 1.25rem;
  border-top: 1px solid var(--border-subtle);
}

/* The signed-in user's initial. */
body > nav footer > span {
  display: grid;
  place-items: center;
  flex: none;
  width: 1.75rem;
  height: 1.75rem;
  border-radius: 50%;
  background: var(--text);
  color: var(--bg-sidebar);
  font-size: var(--text-xs);
  font-weight: 600;
}

body > nav footer > p {
  flex: 1;
  min-width: 0;
  line-height: 1.35;
}

body > nav footer small {
  display: block;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

body > nav footer small:first-child {
  font-size: var(--text-sm);
  color: var(--text);
}

body > nav footer small.build {
  font-family: var(--font-mono);
}


/* ============================================================
   MAIN
   ============================================================ */

/* min-width: 0 lets the grid track shrink below a wide table's content
   width, so the table scrolls inside its card instead of widening the page. */
main {
  padding: 2rem 2.5rem;
  max-width: 100rem;
  min-width: 0;
}

/* Every page names its place: the section, then each parent, then the page
   itself as aria-current. Long names truncate; the page heading has them
   in full. */
.breadcrumbs ol {
  list-style: none;
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 0.35rem;
  margin-bottom: 1.5rem;
  font-size: var(--text-sm);
}

.breadcrumbs li {
  display: flex;
  align-items: center;
  gap: 0.35rem;
  min-width: 0;
}

.breadcrumbs li + li::before {
  content: "›";
  color: var(--text-3);
}

.breadcrumbs :is(a, [aria-current]) {
  max-width: 24ch;
  padding: 0.25rem 0.75rem;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
  border-radius: 999px;
  color: var(--text-2);
  text-decoration: none;
  transition: background var(--transition), color var(--transition);
}

.breadcrumbs a:hover {
  background: var(--bg-active);
  color: var(--text);
}

/* The page's own actions — new, edit, upload — sit in a .actions group
   top right, level with the title, on every page. Whatever else the header
   holds stays in the left column. */
main > header {
  display: grid;
  grid-template-columns: minmax(0, 1fr) auto;
  align-items: center;
  column-gap: 1.5rem;
  margin-bottom: 2rem;
  padding-bottom: 1.5rem;
  border-bottom: 1px solid var(--border);
}

main > header > * { grid-column: 1; }

main > header > .actions {
  grid-column: 2;
  grid-row: 1;
  display: flex;
  flex-wrap: wrap;
  justify-content: flex-end;
  gap: 0.5rem;
}

/* Too narrow to share a line with the title: the actions take the line
   under it instead. */
@media (max-width: 560px) {
  main > header > .actions {
    grid-column: 1;
    grid-row: 2;
    justify-content: flex-start;
    margin: 0.75rem 0 0.5rem;
  }
}

main > header p {
  margin-top: 0.35rem;
  font-size: var(--text-lg);
}

/* A form that spans several sections wraps them, and lays out the same. */
:is(main, main > form) > section {
  margin-top: 2rem;
}

:is(main, main > form) > section > header {
  display: flex;
  justify-content: space-between;
  align-items: center;
  margin-bottom: 1rem;
}

/* A section's own actions sit beside its title, small and secondary so
   they never compete with the page's actions. */
:is(main, main > form) > section > header > .actions {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 0.5rem;
}

/* A note on the card above it. */
main > section > article + p { margin-top: 0.5rem; }


/* ============================================================
   ARTICLE = card
   ============================================================ */

article {
  background: var(--bg-card);
  border: 1px solid var(--border);
  padding: 1.25rem 1.5rem;
}

article > header {
  display: flex;
  justify-content: space-between;
  align-items: center;
  margin-bottom: 1rem;
  padding-bottom: 0.75rem;
  border-bottom: 1px solid var(--border-subtle);
}


/* ============================================================
   LOGIN — the one page with no sidebar
   ============================================================ */

/* Undoes the two-column grid body carries for the sidebar layout. Keyed on
   the card rather than a body class so the page stays plain HTML. */
body:has(> .login-card) {
  display: flex;
  align-items: center;
  justify-content: center;
  min-height: 100vh;
  grid-template-columns: none;
}

.login-card {
  width: 100%;
  max-width: 360px;
  text-align: center;
}

.login-card h1 { margin-bottom: 0.25rem; }
.login-card p  { margin-bottom: 1.5rem; }

.login-card button,
.login-card [role="button"] {
  width: 100%;
  justify-content: center;
}

.login-card .error {
  color: var(--err);
  font-size: var(--text-sm);
  margin-top: 1rem;
}


/* ============================================================
   STATS GRID
   <div class="stats-grid"> <article> <h4> <data> <small> ...
   ============================================================ */

.stats-grid {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(180px, 1fr));
  gap: 1px;
  background: var(--border);
  border: 1px solid var(--border);
}

.stats-grid article { border: none; }

.stats-grid h4 { margin-bottom: 0.35rem; }

.stats-grid data {
  display: block;
  font-size: var(--text-2xl);
  font-weight: 300;
  font-variant-numeric: tabular-nums;
  letter-spacing: -0.02em;
  color: var(--text);
  line-height: 1.1;
}

.stats-grid small { display: block; margin-top: 0.25rem; }
.stats-grid small[data-trend="up"]   { color: var(--ok); }
.stats-grid small[data-trend="down"] { color: var(--err); }


/* ============================================================
   TABLE — classless
   ============================================================ */

table {
  width: 100%;
  border-collapse: collapse;
  font-size: var(--text-base);
  font-variant-numeric: tabular-nums;
}

thead { border-bottom: 2px solid var(--border); }

th {
  text-align: left;
  font-weight: 500;
  font-size: var(--text-xs);
  letter-spacing: 0.06em;
  text-transform: uppercase;
  color: var(--text-2);
  padding: 0.65rem 1rem;
}

td {
  padding: 0.65rem 1rem;
  border-bottom: 1px solid var(--border-subtle);
  color: var(--text-2);
}

tr:hover td {
  background: var(--bg-overlay);
  color: var(--text);
}

td:first-child {
  color: var(--text);
  font-weight: 500;
}

/* Table cells bring their own padding. A table wider than the card
   scrolls inside it rather than spilling past its border. */
article:has(> table) {
  padding: 0;
  overflow-x: auto;
}

/* Wraps, so a traceback in a row doesn't widen every column. */
td pre { white-space: pre-wrap; overflow-wrap: anywhere; }

/* Table inside a padding-less article fills naturally.
   Just align first/last cells with the card gutter. */
article > table { width: 100%; }
article > table th:first-child,
article > table td:first-child { padding-left: 1.5rem; }
article > table th:last-child,
article > table td:last-child { padding-right: 1.5rem; }

/* Row actions: links and buttons share one quiet style, so an Edit link
   and a Delete form sit on one line at one size. Width 1% shrinks the
   column to its content. */
td.actions {
  width: 1%;
  white-space: nowrap;
  text-align: right;
}

td.actions form { display: inline; }

td.actions :is(a, button) {
  display: inline-flex;
  padding: 0.25rem 0.5rem;
  border: none;
  background: none;
  font-size: var(--text-sm);
  color: var(--text-2);
  text-decoration: none;
}

td.actions :is(a, button):hover {
  color: var(--text);
  background: var(--bg-active);
}

td.actions button.danger:hover {
  color: var(--err);
  background: var(--err-bg);
}

/* The control's own padding supplies the rest of the card gutter. */
article > table td.actions:last-child { padding-right: 1rem; }

/* A form alone in its card keeps a readable line length however wide the
   page gets. */
article:has(> form:only-child) { max-width: 40rem; }

td audio {
  display: block;
  width: 100%;
  min-width: 14rem;
  max-width: 22rem;
  height: 2.25rem;
}


/* ============================================================
   FILTER BAR — compact inline form above a table
   <form class="filter-bar"> <label>Name <select>...</select></label> ...
   Overrides the classless full-width block form layout, which stacks
   into most of a screen once there is more than one control.
   ============================================================ */

/* Controls and buttons share one explicit height. A label is two stacked
   rows and a button is one, so leaving the heights implicit makes the row
   depend on how each control type resolves its intrinsic height — which is
   what pushes the buttons off the control line. */
.filter-bar {
  --control-height: 2rem;
  display: flex;
  flex-wrap: wrap;
  align-items: flex-end;
  gap: 0.5rem 0.75rem;
}

.filter-bar label {
  display: flex;
  flex-direction: column;
  /* Keeps the control on the bottom edge even if the item gets stretched. */
  justify-content: flex-end;
  gap: 0.25rem;
  margin-bottom: 0;
  flex: 1 1 8rem;
  min-width: 0;
}

.filter-bar select,
.filter-bar input:not([type="checkbox"]):not([type="radio"]) {
  height: var(--control-height);
  padding: 0.35rem 0.5rem;
  font-size: var(--text-sm);
  box-sizing: border-box;
  /* The classless form rule gives controls a 1.25rem bottom margin for
     stacked forms. On a bottom-aligned row that margin is invisible but
     still occupies the line, lifting the controls off the baseline the
     buttons sit on. */
  margin-bottom: 0;
}

/* Buttons live in their own single-row item, so bottom-aligning them is
   just aligning that item — no need for each button to resolve to the same
   height as a two-row label. */
.filter-actions {
  display: flex;
  align-items: center;
  gap: 0.5rem;
  flex: 0 0 auto;
  align-self: flex-end;
}

.filter-actions button,
.filter-actions [role="button"] {
  height: var(--control-height);
  box-sizing: border-box;
  display: inline-flex;
  align-items: center;
}


/* ============================================================
   LOG TABLE — dense, one wide description column
   <table class="log"> with .log-when / .log-who / .log-what cells
   ============================================================ */

.log { table-layout: fixed; }

.log td { padding-top: 0.55rem; padding-bottom: 0.55rem; }

/* Fixed-width, never-wrapping context columns; .log-what takes the rest. */
.log-when { width: 11rem; white-space: nowrap; }
/* Holds a pseudonym prefix or, for a named unit, its label — so it wraps
   rather than spilling into the next cell. */
.log-who  { width: 8rem;  overflow-wrap: anywhere; }
.log-sev  { width: 5.5rem; }
.log-type { width: 11rem; }
.log-what { width: auto; }

/* Event type: its own column, plain monospace rather than the inline-code
   chip — in a dense log the chip background just adds noise, and it
   collides with the row hover fill.  */
.log-type code {
  font-size: var(--text-sm);
  color: var(--text-2);
  background: none;
  padding: 0;
  overflow-wrap: anywhere;
}

/* Secondary detail sits on its own line under the primary value. Cells
   wrap each line in a <div> so the break survives without this rule; this
   only supplies the spacing. */
.log td > div + div { margin-top: 0.2rem; }

@media (max-width: 900px) {
  /* Let the fixed columns collapse rather than wrapping every cell. */
  .log-when, .log-who, .log-type { width: auto; white-space: normal; }
}

@media (max-width: 560px) {
  /* Facts wrap to their own line under the name; the boot strip follows. */
  .device-row { flex-wrap: wrap; }
}


/* ============================================================
   DEVICE LIST — one unit per row, deliberately not a table
   <ul class="device-list"> <li>
     <div class="device-row"> <a class="device-name"> <span class="device-when">
     <div class="device-row"> <div class="device-facts"> <div class="boot-bars">

   Two stacked lines rather than columns: a row keeps its shape on a phone,
   where an eight-column table can only scroll sideways.
   ============================================================ */

.device-group {
  font-size: var(--text-xs);
  color: var(--text-2);
  margin-top: 1.5rem;
}

.device-group:first-child { margin-top: 0; }

.device-list { list-style: none; }

.device-list > li {
  padding: 0.6rem 0;
  border-top: 1px solid var(--border-subtle);
}

.device-row {
  display: flex;
  justify-content: space-between;
  align-items: baseline;
  gap: 0.75rem;
}

.device-row + .device-row { margin-top: 0.15rem; }

.device-name {
  font-size: var(--text-lg);
  font-weight: 500;
  color: var(--text);
  text-decoration: none;
  background: none;
  padding: 0;
  min-width: 0;
  overflow-wrap: anywhere;
}

.device-name:hover { text-decoration: underline; }

.device-when {
  flex: 0 0 auto;
  font-size: var(--text-sm);
  color: var(--text-2);
  text-align: right;
  white-space: nowrap;
}

.device-when[data-status="err"] { color: var(--err); }
.device-when small { display: block; color: inherit; }

/* Whatever the row knows about the unit. Separators come from the layout,
   not the template, so an absent fact leaves no stray dot behind. */
.device-facts {
  display: flex;
  flex-wrap: wrap;
  align-items: baseline;
  gap: 0.1rem 0.45rem;
  font-size: var(--text-sm);
  color: var(--text-2);
  min-width: 0;
}

/* Dots separate facts from facts, and nothing else. The separator renders
   inside the item that follows it, so putting one before the trailing action
   link would make it part of the anchor — underlined, and inside its click
   target, which no amount of pointer-events fixes because the anchor's own
   box covers the same ground. Badges are skipped for the same reason in
   reverse: mark is a flex container, so a ::before lands inside the chip. */
.device-facts > * + *:not(mark):not(a)::before {
  content: "·";
  margin-right: 0.45rem;
  color: var(--text-3);
}

/* The action isn't a fact, so whitespace sets it apart instead of a dot. */
.device-facts > a { margin-left: 0.35rem; }

.device-facts code {
  font-size: var(--text-sm);
  background: none;
  padding: 0;
}

/* Pseudonyms render in full, grouped in fours like the app shows them, and
   the browser decides how much fits — the server has no idea how wide the
   viewport is. The full value is on the element's title either way. */
.pseudonym {
  display: block;
  font-family: var(--font-mono);
  min-width: 0;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

/* One bar per recent boot, coloured by the worst severity that boot. The
   geometry is in the SVG; only the fill is style. A stubby bar means we hold
   no events for that boot — not the same as a clean one, since uploads are
   opportunistic. */
.boot-bars {
  display: block;
  flex: 0 0 auto;
}

.boot-bars rect { fill: var(--text-3); opacity: 0.45; }
.boot-bars rect[data-status="warn"] { fill: var(--warn); opacity: 1; }
.boot-bars rect[data-status="err"]  { fill: var(--err);  opacity: 1; }
.boot-bars rect[data-silent] { opacity: 0.25; }


/* ============================================================
   DEVICE DETAIL — one unit: health, discharge curve, history
   ============================================================ */

.device-title {
  display: flex;
  align-items: center;
  gap: 0.5rem;
}

.device-title code {
  background: none;
  padding: 0;
  font-size: inherit;
}

.copy { padding: 0.3rem 0.4rem; }
.copy svg { display: block; }

/* Copying is otherwise silent — the clipboard gives no visible sign that
   the click did anything. */
.copy[data-copied]::after {
  content: "Copied";
  font-size: var(--text-xs);
  color: var(--text-2);
}

/* State of charge as points, never a line: a line between two samples
   asserts what the battery did in between, and between two uploads we have
   no idea. Gaps are drawn and labelled instead of spanned. */
.soc-chart {
  display: block;
  width: 100%;
  height: auto;
  margin-top: 0.75rem;
}

.soc-chart circle { fill: var(--text-2); }
.soc-chart circle[data-status="warn"] { fill: var(--warn); }
.soc-chart circle[data-status="err"]  { fill: var(--err); }

.soc-chart .gap { fill: var(--bg-overlay); }
.soc-chart .gap-label { fill: var(--text-2); font-size: 12px; font-family: var(--font); }
.soc-chart .axis { stroke: var(--border); }

.chart-axis {
  display: flex;
  justify-content: space-between;
  margin-top: 0.35rem;
  font-size: var(--text-xs);
  color: var(--text-2);
}

.timeline { list-style: none; }

.timeline > li {
  display: flex;
  align-items: flex-start;
  gap: 0.75rem;
  padding: 0.3rem 0;
  font-size: var(--text-base);
}

.timeline-when {
  flex: 0 0 5.5rem;
  font-family: var(--font-mono);
  font-size: var(--text-xs);
  color: var(--text-2);
  text-align: right;
  white-space: nowrap;
}

.timeline-dot {
  flex: 0 0 auto;
  width: 7px;
  height: 7px;
  border-radius: 50%;
  background: var(--text-3);
  /* The row aligns on its top edge so a wrapped description stays put; this
     drops the dot to the middle of the first line. */
  margin-top: 0.5em;
}

.timeline-dot[data-status="warn"] { background: var(--warn); }
.timeline-dot[data-status="err"]  { background: var(--err); }

.timeline-section + .timeline-section { margin-top: 1.25rem; }

/* A boot the device never had a clock for. Set apart because its timestamps
   are offsets from the boot, not times of day — reading them as wall clock
   would be wrong. */
.timeline-section[data-unclocked] {
  background: var(--bg-overlay);
  padding: 0.75rem 1rem;
}


/* ============================================================
   MARK = badge
   <mark>Default</mark>
   <mark data-status="ok|warn|err">Label</mark>
   ============================================================ */

mark {
  display: inline-flex;
  padding: 0.15rem 0.55rem;
  font-size: var(--text-xs);
  font-weight: 500;
  letter-spacing: 0.02em;
  background: var(--muted-bg);
  color: var(--muted);
  border-radius: 0;
}

mark[data-status="ok"]   { background: var(--ok-bg);   color: var(--ok); }
mark[data-status="warn"] { background: var(--warn-bg); color: var(--warn); }
mark[data-status="err"]  { background: var(--err-bg);  color: var(--err); }


/* ============================================================
   MESSAGES
   <ul class="messages"> <li class="message success">
   ============================================================ */

.messages {
  list-style: none;
  display: flex;
  flex-direction: column;
  gap: 0.5rem;
  margin-bottom: 1.5rem;
}

.message {
  padding: 0.65rem 1rem;
  font-size: var(--text-base);
  background: var(--bg-overlay);
  color: var(--text);
  border-left: 2px solid var(--text-3);
}

/* info and debug take the neutral treatment above. */
.message.success { background: var(--ok-bg);   color: var(--ok);   border-left-color: var(--ok); }
.message.warning { background: var(--warn-bg); color: var(--warn); border-left-color: var(--warn); }
.message.error   { background: var(--err-bg);  color: var(--err);  border-left-color: var(--err); }

.publish-problems {
  display: flex;
  align-items: start;
  justify-content: space-between;
  gap: 1rem;
  margin-bottom: 1rem;
}

.publish-problems[hidden] { display: none; }

.publish-problems ul { margin: 0.35rem 0 0; padding-left: 1.1rem; color: var(--text); }
.publish-problems li + li { margin-top: 0.3rem; }
.publish-problems li button { margin-left: 0.4rem; }


/* ============================================================
   BUTTONS
   <button>              -> default bordered
   <button type="submit"> -> primary accent
   .secondary             -> ghost
   .danger                -> destructive
   .small                 -> compact
   ============================================================ */

button, [role="button"] {
  display: inline-flex;
  align-items: center;
  gap: 0.4rem;
  padding: 0.5rem 1rem;
  font-family: var(--font);
  font-size: var(--text-base);
  font-weight: 500;
  /* Buttons don't inherit it, links do; without this a button stands
     shorter than a link-button beside it. */
  line-height: inherit;
  letter-spacing: 0.02em;
  border: 1px solid var(--border);
  background: var(--bg-card);
  color: var(--text);
  /* Link-buttons would otherwise take the underline from the a rule. */
  text-decoration: none;
  cursor: pointer;
  transition: all var(--transition);
}

button:hover, [role="button"]:hover {
  border-color: var(--text-3);
  background: var(--bg-overlay);
}

button[type="submit"] {
  background: var(--primary);
  color: var(--primary-fg);
  border-color: var(--primary);
}
button[type="submit"]:hover {
  background: var(--primary-hover);
  border-color: var(--primary-hover);
}

/* Qualified by button/[role=button], not a bare class: an unqualified
   .secondary would also match a mark and strip its badge background. */
button.secondary, [role="button"].secondary,
button.outline, [role="button"].outline {
  background: transparent;
  color: var(--text-2);
}
button.secondary:hover, [role="button"].secondary:hover,
button.outline:hover, [role="button"].outline:hover {
  color: var(--text);
  border-color: var(--text-3);
  background: var(--bg-overlay);
}

button.danger, [role="button"].danger {
  color: var(--err);
  border-color: var(--err);
  background: var(--err-bg);
}
button.danger:hover, [role="button"].danger:hover {
  background: var(--err);
  color: var(--white);
}

button.small, [role="button"].small {
  padding: 0.35rem 0.7rem;
  font-size: var(--text-sm);
}

/* A button inside a line of text keeps clear of the words before it. */
p > button { margin-left: 0.5rem; }

button:disabled {
  opacity: 0.4;
  cursor: not-allowed;
  pointer-events: none;
}


/* ============================================================
   FORMS — classless
   ============================================================ */

fieldset { border: none; }
form fieldset { margin-bottom: 1rem; }
fieldset:disabled :is(label, small) { color: var(--text-3); }

label, legend {
  display: block;
  font-size: var(--text-sm);
  font-weight: 500;
  color: var(--text-2);
  margin-bottom: 0.35rem;
}

input:not([type="checkbox"]):not([type="radio"]),
select,
textarea {
  display: block;
  width: 100%;
  padding: 0.55rem 0.75rem;
  font-family: var(--font);
  font-size: var(--text-base);
  color: var(--text);
  background: var(--bg);
  border: 1px solid var(--border);
  margin-bottom: 1.25rem;
}

input:not([type="checkbox"]):not([type="radio"]):disabled,
select:disabled,
textarea:disabled {
  background: var(--bg-muted);
  color: var(--text-3);
  border-style: dashed;
  cursor: not-allowed;
}

input::placeholder, textarea::placeholder { color: var(--text-2); }

textarea { resize: vertical; min-height: 5rem; }

/* <label>Name <input> <small>help</small> <small class="error"></label>:
   help and errors hug their own control, and the gap to the next field
   moves below them. */
label > small { display: block; margin-top: 0.1rem; }
label > small.error { color: var(--err); }
label > :is(input:not([type="checkbox"]):not([type="radio"]), select, textarea):has(~ small) {
  margin-bottom: 0.25rem;
}
label:has(> small) { margin-bottom: 1.25rem; }

/* Two-column row */
[role="group"] {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: 1rem;
}


/* ============================================================
   AUDIO EDITOR
   <section class="audio-editor">, run by admin/editor.js. The timeline is
   a canvas the script paints in these tokens; the rest is plain HTML.
   After FORMS, so its fields can sit in rows.
   ============================================================ */

.audio-editor {
  --lane:      light-dark(#f8f7f6, #1d1c1b);
  --lane-alt:  light-dark(var(--gray-50), #21201f);
  --clip:      light-dark(var(--white), #302f2d);
  --clip-fade: light-dark(rgba(0,0,0,.08), rgba(0,0,0,.4));
  display: grid;
  gap: 1rem;
}

.audio-editor :is(label, input:not([type="checkbox"]):not([type="radio"]), select) {
  margin-bottom: 0;
}

.audio-editor input[type="range"] {
  padding: 0;
  border: 0;
  background: none;
  accent-color: var(--text);
}

.audio-editor kbd {
  padding: 0 0.3rem;
  font: var(--text-xs) var(--font-mono);
  border: 1px solid var(--border);
  border-bottom-width: 2px;
  background: var(--bg-card);
  color: var(--text);
}

#render-note:not(:empty)::before { content: "· "; }

.edit-fields {
  display: flex;
  flex-wrap: wrap;
  align-items: end;
  gap: 0.75rem 1rem;
}

.edit-fields label { display: grid; gap: 0.3rem; }
.edit-fields label:first-child { flex: 1 1 18rem; }
.edit-fields label.wide { flex-basis: 100%; }
.edit-fields label:has(select) { flex: 0 1 14rem; }

/* Drawn like the text fields: a native select ignores their padding. */
.edit-fields select {
  appearance: none;
  border-radius: 0;
  padding-right: 2rem;
  background-image:
    linear-gradient(45deg, transparent 50%, var(--text-2) 50%),
    linear-gradient(135deg, var(--text-2) 50%, transparent 50%);
  background-position: calc(100% - 1.1rem) 55%, calc(100% - 0.8rem) 55%;
  background-size: 0.3rem 0.3rem;
  background-repeat: no-repeat;
}

/* Where the list can be styled too, it opens under the field, at its width. */
@supports (appearance: base-select) {
  .edit-fields select,
  .edit-fields ::picker(select) { appearance: base-select; }

  .edit-fields select {
    display: flex;
    justify-content: space-between;
    min-block-size: auto;
    padding-right: 0.75rem;
    line-height: normal;
    background-image: none;
  }

  .edit-fields select::picker-icon { color: var(--text-2); }

  /* Open, it keeps the focus ring a click otherwise leaves off; the list
     covers its lower edge. */
  .edit-fields select:open {
    outline: 2px solid var(--accent);
    outline-offset: 2px;
  }

  .edit-fields ::picker(select) {
    margin-top: -1px;
    padding: 0;
    border: 1px solid var(--border);
    border-radius: 0;
    background: var(--bg-card);
    color: var(--text);
  }

  .edit-fields option {
    padding: 0.55rem 0.75rem;
    outline: none;
  }

  .edit-fields option:is(:hover, :focus-visible) { background: var(--bg-active); }
  .edit-fields option:checked { font-weight: 500; }
  .edit-fields option::checkmark { display: none; }
}

.edit-fields textarea { margin-bottom: 0; }

.editor-timeline {
  display: grid;
  grid-template-columns: 9.5rem minmax(0, 1fr);
  background: var(--bg-card);
  border: 1px solid var(--border);
}

.transport,
.editor-foot {
  grid-column: 1 / -1;
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 0.5rem 1rem;
  padding: 0.6rem 0.75rem;
}

.transport { border-bottom: 1px solid var(--border); }

.transport svg {
  width: 1rem;
  height: 1rem;
  fill: currentColor;
  stroke: currentColor;
  stroke-width: 1.5;
  stroke-linejoin: round;
}

.editor-foot { border-top: 1px solid var(--border); }
.transport .grow { flex: 1; }

.clock {
  display: flex;
  align-items: baseline;
  gap: 0.4rem;
  min-width: 9rem;
}

.audio-editor #clock { font-size: var(--text-xl); }

.meter { display: grid; gap: 3px; width: 6rem; }

.meter > span {
  position: relative;
  display: block;
  height: 5px;
  overflow: hidden;
  background: var(--bg-active);
}

.meter i {
  position: absolute;
  inset: 0 auto 0 0;
  width: 0;
  background: var(--text-2);
}

.audio-editor .hot { color: var(--err); }
.meter i.hot { background: var(--err); }

.audio-editor .check {
  display: flex;
  align-items: center;
  gap: 0.4rem;
  font-size: var(--text-base);
  font-weight: 400;
  color: var(--text);
}

.editor-heads { min-width: 0; border-right: 1px solid var(--border); }

.ruler-head {
  display: flex;
  align-items: center;
  height: 28px;
  padding: 0 0.75rem;
  border-bottom: 1px solid var(--border);
  font-size: var(--text-xs);
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: var(--text-2);
}

.track-head {
  display: flex;
  flex-direction: column;
  justify-content: center;
  gap: 0.35rem;
  height: 84px;
  padding: 0 0.6rem;
  border-bottom: 1px solid var(--border-subtle);
}

.audio-editor .track-head input {
  padding: 0.15rem 0.3rem;
  font-weight: 500;
  background: transparent;
  border-color: transparent;
}

.audio-editor .track-head input:hover { border-color: var(--border); }
.track-head span { display: flex; gap: 0.25rem; }
.track-head button { padding: 0.1rem 0.45rem; font-size: var(--text-xs); }

.track-head button[aria-pressed="true"] {
  background: var(--text);
  border-color: var(--text);
  color: var(--bg-card);
}

.editor-scroll { min-width: 0; overflow-x: auto; overflow-y: hidden; }
.audio-editor #spacer { position: relative; }

/* Sticky, so the canvas stays in view and paints whatever is scrolled to. */
.audio-editor #canvas {
  position: sticky;
  left: 0;
  display: block;
  touch-action: none;
}

.audio-editor #status { flex: 1; color: var(--warn); }
.audio-editor #status:empty { display: none; }

/* The selected clip, under the tracks and edged in the accent it is drawn in. */
.clip-bar {
  grid-column: 1 / -1;
  display: flex;
  flex-wrap: wrap;
  align-items: end;
  gap: 0.6rem 1rem;
  padding: 0.6rem 0.75rem;
  border-top: 1px solid var(--border);
  box-shadow: inset 2px 0 var(--accent);
  background: var(--bg-overlay);
}

.clip-bar[hidden] { display: none; }

/* The actions wrap below before the fields do. */
.clip-fields {
  display: flex;
  flex: 1 1 auto;
  flex-wrap: wrap;
  gap: 0.6rem 1.5rem;
  min-width: 0;
}

/* Each group wraps as one. */
.clip-fields fieldset {
  display: flex;
  align-items: end;
  gap: 0.5rem;
  margin: 0;
  padding: 0;
}

.clip-fields legend {
  margin: 0;
  padding: 0;
  font-size: var(--text-xs);
  font-weight: 600;
  color: var(--text);
}

.clip-fields label { display: grid; gap: 0.25rem; margin: 0; font-size: var(--text-xs); }

.audio-editor .clip-fields input:not([type="range"]) {
  width: auto;
  padding: 0.3rem 0.5rem;
  font-size: var(--text-sm);
}

.audio-editor .clip-fields input.mono { width: 4.75rem; }

.clip-name {
  align-self: center;
  max-width: 12rem;
  margin: 0;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
  line-height: 1.3;
}

.clip-fields .gain :is(input, output) { height: 1.85rem; line-height: 1.85rem; }

.audio-editor .gain input { width: 6rem; }

.gain output {
  min-width: 4rem;
  font-family: var(--font-mono);
  font-size: var(--text-sm);
  font-variant-numeric: tabular-nums;
  color: var(--text);
}

.clip-actions { display: flex; gap: 0.4rem; margin-left: auto; }
/* The stylesheet's display on buttons would otherwise win over [hidden]. */
.clip-actions button[hidden] { display: none; }

.audio-editor #library-filter { margin-bottom: 0.75rem; }

.audio-editor #uploads { list-style: none; }
.audio-editor #upload-input { display: none; }

.audio-editor #sources {
  max-height: 24rem;
  margin-bottom: 0.75rem;
  overflow-y: auto;
  list-style: none;
}

.audio-editor :is(#sources, #uploads) li {
  display: grid;
  grid-template-columns: minmax(0, 1fr) auto auto;
  align-items: center;
  gap: 0.75rem;
  padding: 0.5rem 0;
  border-bottom: 1px solid var(--border-subtle);
}

.audio-editor #sources li[draggable="true"] { cursor: grab; }

.audio-editor #sources li > span:first-child {
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.audio-editor .keys { line-height: 2; }

@media (max-width: 560px) {
  .editor-timeline { grid-template-columns: 5.75rem minmax(0, 1fr); }
  .clip-bar { flex-direction: column; align-items: stretch; }
}


/* ============================================================
   RESPONSIVE — below 768px
   ============================================================ */

@media (max-width: 768px) {

  /* Stack sidebar above main */
  body {
    grid-template-columns: 1fr;
    grid-template-rows: auto 1fr;
  }

  /* Nav becomes a top bar */
  body > nav {
    position: relative;
    top: 0;
    height: auto;
    border-right: none;
    border-bottom: 1px solid var(--border);
  }

  body > nav > header {
    padding: 0.4rem 0.5rem 0.4rem 1rem;
    min-height: 0;
  }

  /* Restore details as block — native open/closed behavior works.
     JS removes [open] when entering mobile breakpoint. */
  body > nav details {
    display: block;
    border-top: 1px solid var(--border-subtle);
  }

  body > nav > details::details-content {
    height: auto;
  }

  body > nav summary {
    display: flex;
    align-items: center;
    justify-content: space-between;
    padding: 0.65rem 1rem;
    font-size: var(--text-sm);
    font-weight: 500;
    letter-spacing: 0.06em;
    text-transform: uppercase;
    color: var(--text-2);
    cursor: pointer;
    list-style: none;
  }

  body > nav summary::-webkit-details-marker { display: none; }
  body > nav summary::marker { content: ''; }

  body > nav summary::after {
    content: '+';
    font-size: 1rem;
    font-weight: 300;
    line-height: 1;
    color: var(--text-3);
  }

  body > nav details[open] > summary {
    border-bottom: 1px solid var(--border-subtle);
  }

  body > nav details[open] > summary::after {
    content: '\2212';
  }

  body > nav footer {
    padding: 0.6rem 0.5rem 0.6rem 1rem;
  }

  main {
    padding: 1.5rem 1rem;
    overflow-x: hidden;
  }

  /* Stats: 2 columns */
  .stats-grid {
    grid-template-columns: 1fr 1fr;
  }

  /* Form rows stack */
  [role="group"] {
    grid-template-columns: 1fr;
  }

  /* Section headers stack when buttons present */
  :is(main, main > form) > section > header {
    flex-wrap: wrap;
    gap: 0.75rem;
  }
}
