/* PLN rig admin home, in the show's own Stamped Ledger language.
 *
 * This is the page a browser lands on when somebody types the rig's hostname
 * and nothing else. Three people open it and none of them are the same:
 *
 *   a producer, twenty minutes before air, who needs the console and the two
 *   OBS sources and needs to know the rig is running the build they deployed;
 *
 *   a member of talent who was sent the wrong link, is already nervous, and
 *   needs one obvious door with their name on it;
 *
 *   a partner being walked through the rig, who needs to understand what the
 *   nine surfaces are before they can be impressed by any of them.
 *
 * So the page is a directory, not a control room. Nothing here acts on the
 * show. Its whole job is to be read correctly by someone who has never seen
 * it, which is why every entry states who opens it and on what device in
 * words, and why the groups are by WHO HOLDS THE SURFACE rather than by what
 * it is built out of. "What goes into OBS" is a sentence a producer already
 * says out loud. "Browser sources" is not.
 *
 * Every panel is the same lot ticket /play, /green-room and /console use:
 * solid plate, 2px hairline border, a hard offset shadow with no blur, a
 * perforated left edge in the panel's own accent, and no rounded corners
 * anywhere. Radius zero is the language, not an oversight.
 *
 * The palette is semantic and never decorative (docs/design/stamped-ledger-
 * handoff.md). Cyan is platform truth, red is the Broker and anything that
 * will bite, gold is money, purple is the crowd. On this page:
 *
 *   purple   the audience group, because those surfaces ARE the crowd
 *   cyan     talent and public proof, both of which are the platform letting
 *            somebody in or handing its own numbers back
 *   gold     the producer group, because that is where money gets spent
 *   neutral  the OBS outputs, which are pictures and claim nothing, so they
 *            get the plain hairline rather than a colour that would be lying
 *   red      the two or three things that will go wrong, and the token strip
 *            while it is empty, because a producer with no token is not a
 *            producer with a mild inconvenience
 *
 * Nothing on this page is carried by colour alone. Every group has a heading
 * in words, every entry spells out who opens it, and a locked entry carries
 * the words TOKEN NEEDED and is not a link at all.
 */

:root {
  --bg: #0a0d16;
  --plate: #0e1016;
  --strip: #12141b;
  --rule: #2a3347;
  --edge: #3d4a66;
  --ink: #e8ecf2;
  --dim: #7d8797;
  --truth: #3fd8ff;
  --truth-ink: #062733;
  --broker: #ff2e55;
  --broker-ink: #2b040d;
  --gold: #ffc63d;
  --gold-ink: #17130a;
  --crowd: #b45bff;
  --crowd-ink: #1d0a2e;
  --ok: #43e39a;

  --display: "Anton", "Haettenschweiler", "Franklin Gothic Medium", "Archivo Narrow", sans-serif;
  --mono: "IBM Plex Mono", Consolas, "Courier New", monospace;
  --sans: -apple-system, "Segoe UI", Roboto, Helvetica, Arial, sans-serif;

  --hard: 6px 6px 0 rgba(0, 0, 0, 0.5);
  --hard-sm: 4px 4px 0 rgba(0, 0, 0, 0.5);
}

* { margin: 0; padding: 0; box-sizing: border-box; -webkit-tap-highlight-color: transparent; }

body {
  background: var(--bg);
  color: var(--ink);
  font-family: var(--sans);
  font-size: 15px;
  line-height: 1.5;
  min-height: 100dvh;
}

.hidden { display: none !important; }

/* The half a sighted reader gets from layout, reaching a screen reader
   without reaching the layout. Same class, same rules, as every other
   surface here. */
.vh {
  position: absolute;
  width: 1px;
  height: 1px;
  margin: -1px;
  padding: 0;
  overflow: hidden;
  clip: rect(0 0 0 0);
  clip-path: inset(50%);
  white-space: nowrap;
  border: 0;
}

/* Focus has to survive on a page that is almost entirely links. The show has
   no rounded corners, so the ring is a hard offset outline in platform cyan,
   which reads against every plate on the page. */
a:focus-visible,
button:focus-visible,
input:focus-visible {
  outline: 3px solid var(--truth);
  outline-offset: 2px;
}

/* ── the lot ticket ── */

.plate {
  background: var(--plate);
  border: 2px solid var(--edge);
  box-shadow: var(--hard);
}

.ticket { border-left: 4px dotted var(--accent, var(--edge)); }

/* ── rubber stamps ── */

.stamp {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  flex: none;
  font-family: var(--display);
  font-size: 13px;
  letter-spacing: 2px;
  text-transform: uppercase;
  border: 2px solid currentColor;
  background: var(--plate);
  padding: 2px 8px;
  transform: rotate(-3deg);
}
.stamp .dot { width: 9px; height: 9px; background: currentColor; }
.stamp.live { color: var(--broker); }
.stamp.live .dot { animation: blink 1.6s infinite; }
.stamp.idle { color: var(--dim); }
.stamp.ok { color: var(--ok); }
.stamp.locked { color: var(--broker); }
@keyframes blink { 0%, 100% { opacity: 1; } 50% { opacity: 0.3; } }

@media (prefers-reduced-motion: reduce) {
  .stamp.live .dot { animation: none; }
}

/* ══ the show bug ══════════════════════════════════════════════════════════ */

header.bug {
  display: flex;
  align-items: center;
  gap: 12px;
  flex-wrap: wrap;
  padding: 12px 18px;
  background: var(--plate);
  border-bottom: 2px solid var(--edge);
}
header.bug .plnmark { height: 26px; display: block; }
header.bug .showname {
  font-family: var(--display);
  font-size: 21px;
  letter-spacing: 3px;
  background: var(--plate);
  border: 2px solid var(--edge);
  border-left: 4px dotted var(--edge);
  padding: 3px 10px;
}
header.bug .roomname {
  font-family: var(--mono);
  font-size: 13px;
  font-weight: 600;
  letter-spacing: 0.06em;
  color: var(--dim);
  text-transform: uppercase;
}

main {
  max-width: 1080px;
  margin: 0 auto;
  padding: 22px 16px 64px;
}

.lede {
  max-width: 62ch;
  color: var(--dim);
  font-size: 15px;
  margin-bottom: 24px;
}
.lede b { color: var(--ink); font-weight: 600; }

/* ══ what the rig is running ═══════════════════════════════════════════════
 *
 * Two plates, side by side on a desk and stacked on a phone. The left one is
 * the machine (is this the build I deployed, is video wired up) and the right
 * one is the show (is anything on air right now). A rig with nothing running
 * is the normal resting state of this hardware for most of any week, so the
 * idle case is written to look calm and deliberate rather than broken. */

.status-row {
  display: grid;
  grid-template-columns: 1fr;
  gap: 14px;
  margin-bottom: 26px;
}
@media (min-width: 760px) {
  .status-row { grid-template-columns: 1fr 1fr; }
}

.status {
  padding: 14px 18px;
  --accent: var(--edge);
}
.status.truth { --accent: var(--truth); }
.status.onair { --accent: var(--broker); }
.status.fault { --accent: var(--broker); }

.status h2 {
  font-family: var(--mono);
  font-size: 13px;
  font-weight: 600;
  letter-spacing: 0.06em;
  text-transform: uppercase;
  color: var(--dim);
  margin-bottom: 10px;
}
.status .headline {
  display: flex;
  align-items: center;
  gap: 12px;
  flex-wrap: wrap;
  font-family: var(--display);
  font-size: 26px;
  line-height: 1.1;
  letter-spacing: 1px;
  margin-bottom: 10px;
}
.status dl {
  display: grid;
  grid-template-columns: max-content 1fr;
  gap: 4px 14px;
  font-size: 14px;
}
.status dt {
  font-family: var(--mono);
  font-size: 13px;
  letter-spacing: 0.06em;
  text-transform: uppercase;
  color: var(--dim);
}
.status dd {
  font-family: var(--mono);
  font-size: 13px;
  font-variant-numeric: tabular-nums;
  word-break: break-all;
}
.status dd.warn { color: var(--gold); }
.status dd.bad { color: var(--broker); }
.status .note { color: var(--dim); font-size: 14px; margin-top: 8px; }

/* ══ the producer token ════════════════════════════════════════════════════
 *
 * This is the page that hands out tokened links, so it is also the page that
 * has to be honest about not having one. Before this existed, the directory
 * printed a plain /console link that looked exactly as live as /play and then
 * refused on arrival, which is the failure a recent fix went in to kill. So
 * while the field is empty the strip is red, says so in a sentence, and every
 * producer link on the page is not a link.
 *
 * The token is never printed back. The strip confirms a token is set by its
 * length and its last four characters, which is enough for a producer to tell
 * two tokens apart and not enough for a shoulder or a screen share to learn
 * one. */

.tokenbox {
  padding: 14px 18px;
  margin-bottom: 30px;
  --accent: var(--broker);
}
.tokenbox.set { --accent: var(--gold); }

.tokenbox h2 {
  font-family: var(--display);
  font-size: 20px;
  letter-spacing: 1.5px;
  margin-bottom: 6px;
}
.tokenbox .why { color: var(--dim); font-size: 14px; max-width: 70ch; margin-bottom: 12px; }

.tokenbox .state {
  font-family: var(--mono);
  font-size: 14px;
  font-weight: 600;
  letter-spacing: 0.02em;
  color: var(--broker);
  margin-bottom: 12px;
}
.tokenbox.set .state { color: var(--ok); }

.tokenform {
  display: flex;
  gap: 10px;
  flex-wrap: wrap;
  align-items: flex-end;
}
.tokenform .field { flex: 1 1 260px; min-width: 0; }
.tokenform label {
  display: block;
  font-family: var(--mono);
  font-size: 13px;
  letter-spacing: 0.06em;
  text-transform: uppercase;
  color: var(--dim);
  margin-bottom: 5px;
}
.tokenform input {
  width: 100%;
  min-height: 44px;
  padding: 10px 12px;
  background: var(--strip);
  border: 2px solid var(--edge);
  color: var(--ink);
  font-family: var(--mono);
  font-size: 15px;
  letter-spacing: 0.02em;
}
.tokenform input::placeholder { color: var(--dim); }

button.act {
  min-height: 44px;
  padding: 10px 20px;
  font-family: var(--display);
  font-size: 18px;
  letter-spacing: 2px;
  border: 2px solid var(--gold-ink);
  background: var(--gold);
  color: var(--gold-ink);
  cursor: pointer;
  box-shadow: var(--hard-sm);
}
button.act.quiet {
  background: var(--plate);
  color: var(--ink);
  border-color: var(--edge);
}

/* ══ the surface groups ════════════════════════════════════════════════════ */

section.group { margin-bottom: 34px; }

section.group > h2 {
  font-family: var(--display);
  font-size: 24px;
  letter-spacing: 2px;
  margin-bottom: 4px;
  color: var(--groupink, var(--ink));
}
section.group > p.sub {
  color: var(--dim);
  font-size: 14px;
  max-width: 70ch;
  margin-bottom: 14px;
}

.grid {
  display: grid;
  grid-template-columns: 1fr;
  gap: 12px;
}
@media (min-width: 760px) {
  .grid { grid-template-columns: 1fr 1fr; }
}

/* An entry is a plate whether it is a link or not, so a locked one is never
   a hole in the layout. The difference between the two is stated in words
   (TOKEN NEEDED, plus a sentence) and carried in the cursor and the edge, not
   in a colour a reader has to already know how to read. */
.entry {
  display: block;
  padding: 14px 16px;
  min-height: 44px;
  text-decoration: none;
  color: inherit;
  --accent: var(--groupaccent, var(--edge));
}
a.entry { transition: border-color 0.12s, box-shadow 0.12s; }
a.entry:hover { border-color: var(--groupaccent, var(--truth)); }
a.entry:active { box-shadow: var(--hard-sm); }

/* The entry's parts are spans so that the whole plate can be one anchor
   without an anchor ever containing a block element. They are laid out as
   blocks here, which is what makes them read as the four lines they are:
   name and path, the sentence, who opens it, and the reason it is dead. */
.entry .top,
.entry .what,
.entry .who,
.entry .needs { display: block; }

.entry .top {
  display: flex;
  align-items: baseline;
  gap: 10px;
  flex-wrap: wrap;
  margin-bottom: 4px;
}
.entry .name {
  font-family: var(--display);
  font-size: 21px;
  letter-spacing: 1.5px;
}
.entry .path {
  font-family: var(--mono);
  font-size: 13px;
  font-weight: 600;
  letter-spacing: 0.02em;
  color: var(--groupaccent, var(--truth));
  word-break: break-all;
}
.entry .what { font-size: 14px; color: var(--ink); margin-bottom: 8px; }
.entry .who {
  font-family: var(--mono);
  font-size: 13px;
  letter-spacing: 0.02em;
  color: var(--dim);
}
.entry .who b { color: var(--ink); font-weight: 600; }

/* The locked state. Not a link, not disabled-looking-but-clickable, and it
   says the reason on the plate rather than leaving it to be discovered on
   arrival. */
span.entry {
  border-style: dashed;
  box-shadow: none;
  cursor: default;
}
span.entry .name, span.entry .path { color: var(--dim); }
span.entry .what { color: var(--dim); }
.entry .needs {
  display: block;
  font-family: var(--mono);
  font-size: 13px;
  font-weight: 600;
  color: var(--broker);
  margin-top: 8px;
}

/* Group accents. Declared here rather than inline so the semantic mapping is
   in one place and can be read as a list. */
.g-audience { --groupaccent: var(--crowd); --groupink: var(--crowd); }
.g-talent   { --groupaccent: var(--truth);  --groupink: var(--truth); }
.g-producer { --groupaccent: var(--gold);   --groupink: var(--gold); }
.g-outputs  { --groupaccent: var(--edge);   --groupink: var(--ink); }
.g-proof    { --groupaccent: var(--truth);  --groupink: var(--truth); }

/* ══ what will go wrong ════════════════════════════════════════════════════
 *
 * Short on purpose. A list of everything that could ever go wrong is a list
 * nobody reads twenty minutes before air. These are the three that have
 * actually cost a taping. */

.care {
  padding: 16px 18px;
  margin-bottom: 30px;
  --accent: var(--broker);
}
.care h2 {
  font-family: var(--display);
  font-size: 22px;
  letter-spacing: 2px;
  color: var(--broker);
  margin-bottom: 12px;
}
.care ol { list-style: none; counter-reset: care; }
.care li {
  counter-increment: care;
  display: grid;
  grid-template-columns: max-content 1fr;
  gap: 12px;
  padding: 10px 0;
  border-top: 2px dotted var(--rule);
}
.care li:first-child { border-top: 0; padding-top: 0; }
.care li::before {
  content: counter(care);
  font-family: var(--mono);
  font-size: 15px;
  font-weight: 600;
  color: var(--broker);
}
.care li b { font-family: var(--mono); font-size: 14px; color: var(--ink); }
.care li p { font-size: 14px; color: var(--dim); margin-top: 2px; }
.care li p b { font-family: inherit; font-size: inherit; }

/* ══ the manuals ═══════════════════════════════════════════════════════════
 *
 * The manuals read as a group like every other group on this page, because
 * that is what they are now: entries this rig serves, not a footnote of paths
 * to go and find somewhere else. */
.g-docs { --groupink: var(--ink); }
.g-docs .foot {
  margin-top: 12px;
  color: var(--dim);
  font-size: 13px;
  max-width: 74ch;
}
.g-docs .foot code {
  font-family: var(--mono);
  color: var(--ink);
}

footer.colophon {
  margin-top: 30px;
  color: var(--dim);
  font-family: var(--mono);
  font-size: 13px;
  line-height: 1.7;
}

/* The slot an entry is rendered into. An entry is a link when a token is in
   hand and a plain plate when it is not, and rebuilding it in place is what
   keeps those two from ever being on screen at the same time. The slot is the
   grid item, so a rebuild never moves anything. */
.slot { display: grid; }
.slot > .entry { height: 100%; }

/* Inline machine shapes inside a sentence: a query string, an env var name.
   Mono, because that is what the show's language uses for anything a person
   types or copies. */
code {
  font-family: var(--mono);
  font-size: 0.92em;
  color: var(--truth);
  word-break: break-all;
}

/* There are no links inside a sentence on this page, on purpose. Every link
   here is a whole plate, or a button, which is the only way a directory read
   on a phone in a venue can promise a 44px target for all of them. A cross
   reference inside prose is written as the name of the thing plus where it
   is, and the reader scrolls: the page is short enough that this costs less
   than a 16px tap target would. */
.sub b { color: var(--ink); font-weight: 600; }

/* With scripting off this page is still a correct directory: every link in
   the markup goes where it says, and the two tokened entries ship locked, so
   nothing here can hand out a link that refuses on arrival. The only things
   that stop working are the two live readings and the token field, and saying
   so is cheaper than letting somebody wonder why a plate reads "Reading". */
.noscript {
  padding: 12px 16px;
  margin-bottom: 22px;
  background: var(--strip);
  border: 2px solid var(--gold);
  border-left: 4px dotted var(--gold);
  color: var(--ink);
  font-size: 14px;
}
