/* ===========================================================================
   NexCarbon Plant Control
   ---------------------------------------------------------------------------
   Direction: "instrument panel". A CVD plant is a metrology job — every screen
   is really a readout, so the page is built like lab instrumentation: cool
   paper ground, hairline rules, condensed labels, monospaced figures that line
   up column to column.

   Colour carries process state and nothing else. Plasma violet is the actual
   colour of a CVD chamber in operation and is reserved for one meaning — this
   reactor is growing right now. Ember means something needs a person. Jade
   means finished and good. Nothing decorative is tinted.
   ========================================================================= */

:root {
  /* --- palette ---------------------------------------------------------- */
  --carbon: #16181d; /* the material itself — all primary text and structure */
  --ash: #6e7481; /* secondary text */
  --mist: #9aa1ad; /* tertiary / placeholder */
  --paper: #eff2f6; /* page ground */
  --card: #ffffff;
  --rule: #e0e5ed;
  --rule-strong: #c9d0dc;

  --plasma: #7a2ff2; /* growing — chamber is live */
  --plasma-ink: #4c119e;
  --plasma-soft: #f3ecfe;

  --ember: #d9480f; /* needs attention / removed / overdue */
  --ember-soft: #fdeee6;

  --amber: #b45309; /* in post-processing */
  --amber-soft: #fdf3e3;

  --jade: #0b8f66; /* good / sellable / done */
  --jade-soft: #e6f6f0;

  /* --- type ------------------------------------------------------------- */
  /* Nothing is fetched from anywhere. The plant runs on its own Wi-Fi and the
     app has to look right the first time it is opened there, with the server
     unreachable from the internet. These are the faces already on a Windows
     desk and an Android phone, so they render instantly and identically to
     every other tool the floor uses. */
  --display: 'Segoe UI Variable Display', 'Segoe UI Semibold', -apple-system,
    BlinkMacSystemFont, 'Helvetica Neue', Roboto, Arial, sans-serif;
  --body: -apple-system, BlinkMacSystemFont, 'Segoe UI Variable Text', 'Segoe UI',
    Roboto, 'Helvetica Neue', Arial, sans-serif;
  --mono: ui-monospace, 'Cascadia Mono', 'Segoe UI Mono', 'SF Mono', Menlo,
    Consolas, monospace;

  /* --- metrics ---------------------------------------------------------- */
  --rail-w: 232px;
  --topbar-h: 60px;
  --r-sm: 6px;
  --r-md: 10px;
  --r-lg: 14px;
  --shadow: 0 1px 2px rgb(22 24 29 / 6%), 0 8px 24px -16px rgb(22 24 29 / 28%);
  --shadow-lift: 0 2px 6px rgb(22 24 29 / 8%), 0 18px 40px -22px rgb(22 24 29 / 35%);
}

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

/* The shell layers each set their own display, which would otherwise beat the
   user-agent rule for [hidden] and leave the boot overlay stuck on screen. */
[hidden] {
  display: none !important;
}

html,
body {
  margin: 0;
  padding: 0;
  /* An opaque ground on the root: the page must never show the browser canvas
     through, at any scroll position. */
  background: var(--paper);
}

body {
  min-height: 100vh;
  font-family: var(--body);
  font-size: 14px;
  line-height: 1.5;
  color: var(--carbon);
  -webkit-font-smoothing: antialiased;
}

h1,
h2,
h3,
h4 {
  font-family: var(--display);
  font-weight: 600;
  letter-spacing: -0.01em;
  margin: 0;
}

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

button,
input,
select,
textarea {
  font: inherit;
  color: inherit;
}

/* Numbers are data. They always get the mono face and tabular figures so a
   column of carats reads as a column, not a ragged list. */
.num,
td.num,
th.num,
.stat__value,
.reactor__ct {
  font-family: var(--mono);
  font-variant-numeric: tabular-nums;
  font-feature-settings: 'tnum' 1;
}

:focus-visible {
  outline: 2px solid var(--plasma);
  outline-offset: 2px;
  border-radius: 3px;
}

/* ------------------------------------------------------------------ boot -- */

.boot {
  position: fixed;
  inset: 0;
  display: grid;
  place-content: center;
  justify-items: center;
  gap: 18px;
  background: var(--paper);
}

/* The mark, breathing, rather than a spinner ring. It is on screen for a
   second at most; what it has to say is "this is your plant's software and it
   is coming", which a nameless ring does not. */
.boot__mark {
  width: 76px;
  height: 76px;
  background: url('/brand/mark.svg') center / contain no-repeat;
  animation: breathe 1.6s ease-in-out infinite;
}

@keyframes breathe {
  0%, 100% { opacity: 0.55; transform: scale(0.96); }
  50% { opacity: 1; transform: scale(1); }
}

/* Somebody who has asked their machine not to animate things gets a still
   mark rather than a pulsing one. */
@media (prefers-reduced-motion: reduce) {
  .boot__mark { animation: none; opacity: 1; transform: none; }
}

.boot__text {
  margin: 0;
  font-family: var(--display);
  letter-spacing: 0.14em;
  text-transform: uppercase;
  font-size: 11px;
  color: var(--ash);
}

@keyframes spin {
  to {
    transform: rotate(360deg);
  }
}

body.is-booting #auth,
body.is-booting #app {
  display: none;
}

/* ----------------------------------------------------------------- auth --- */

.auth {
  min-height: 100vh;
  display: grid;
  grid-template-columns: 1fr;
  place-items: center;
  padding: 24px;
}

.signin {
  width: min(400px, 100%);
  background: var(--card);
  border: 1px solid var(--rule);
  border-radius: var(--r-lg);
  box-shadow: var(--shadow-lift);
  padding: 34px 32px 30px;
}

/* The whole lockup on the sign-in card: this is the first thing anybody sees
   of the plant, and a 30px chip was never going to say whose software it is.
   The wordmark inside the file is currentColor, so it takes the card's ink and
   works on a light card and a dark one without a second file. */
.signin__logo {
  width: min(280px, 78%);
  height: auto;
  display: block;
  margin: 0 auto 22px;
  color: var(--carbon);
}

.signin h1 {
  font-size: 22px;
  letter-spacing: -0.02em;
}

.signin__sub {
  margin: 6px 0 24px;
  color: var(--ash);
  font-size: 13px;
}

/* -------------------------------------------------- the signed-out card --- */

.signin__rule {
  border: 0;
  border-top: 1px solid var(--rule);
  margin: 6px 0 2px;
}

/* A password box with a way to see what you typed. On a factory floor, on a
   phone, possibly with a glove on, typing a string of dots you cannot check is
   how people end up writing the password on the wall by the door. */
.pw {
  position: relative;
  display: flex;
}

.pw input {
  flex: 1;
  padding-right: 62px;
}

.pw__eye {
  position: absolute;
  right: 6px;
  top: 50%;
  transform: translateY(-50%);
  border: 0;
  background: none;
  padding: 6px 8px;
  font-size: 12px;
  font-weight: 600;
  color: var(--ash);
  cursor: pointer;
}

.pw__eye:hover { color: var(--ink); }

/* What a password is worth, in words as well as a bar — a bar alone tells
   somebody they have failed without telling them what would pass. */
.pwmeter {
  display: grid;
  grid-template-columns: 1fr auto;
  align-items: center;
  gap: 9px;
  margin-top: 6px;
}

.pwmeter__bar {
  height: 4px;
  border-radius: 999px;
  background: var(--ash);
  width: 0;
  transition: width 0.18s ease;
}

.pwmeter__say { font-size: 11.5px; color: var(--ash); }

.pwmeter--bad .pwmeter__bar,
.pwmeter--weak .pwmeter__bar { background: var(--ember); }
.pwmeter--ok .pwmeter__bar { background: var(--amber, #c77d0a); }
.pwmeter--good .pwmeter__bar { background: var(--jade); }

/* Where somebody is in a three-step business. Registering fails most often not
   because a form was hard but because a person did not know there was anything
   after it — they confirm the e-mail, nothing happens, and they assume the
   software is broken rather than that a human has yet to approve them. */
.steps {
  display: flex;
  gap: 6px;
  list-style: none;
  margin: 0 0 20px;
  padding: 0;
  counter-reset: step;
}

.steps__s {
  flex: 1;
  font-size: 10.5px;
  line-height: 1.3;
  color: var(--ash);
  padding-top: 8px;
  border-top: 3px solid var(--rule);
  counter-increment: step;
}

.steps__s::before {
  content: counter(step) '. ';
  font-weight: 700;
}

.steps__s.is-on {
  color: var(--ink);
  border-top-color: var(--carbon);
  font-weight: 600;
}

.steps__s.is-done {
  color: var(--jade);
  border-top-color: var(--jade);
}

.steps__s.is-done::before { content: '\2713 '; }

/* A six-digit code, spaced out so it can be read back off the screen against
   the text message. */
.otpin {
  font-family: var(--mono);
  font-size: 22px;
  letter-spacing: 0.4em;
  text-align: center;
}

/* ----------------------------------------------------------------- shell -- */

.app {
  display: grid;
  grid-template-columns: var(--rail-w) 1fr;
  min-height: 100vh;
}

.rail {
  background: var(--carbon);
  color: #cfd4de;
  display: flex;
  flex-direction: column;
  position: sticky;
  top: 0;
  height: 100vh;
  overflow-y: auto;
}

.rail__brand {
  display: flex;
  align-items: center;
  gap: 11px;
  padding: 17px 18px;
  border-bottom: 1px solid rgb(255 255 255 / 8%);
}

.rail__mark {
  width: 30px;
  height: 30px;
  flex: none;
  background: url('/brand/mark.svg') center / contain no-repeat;
}

.rail__wordmark {
  display: grid;
  gap: 3px;
  min-width: 0;
}

/* The drawn wordmark, not typed letters: the brand's letterforms are its own
   and the rail is the place they are seen every day. White, because the rail
   is always the dark carbon panel whatever the rest of the theme does. */
.rail__wordmark .brandtype {
  width: 118px;
  height: auto;
  display: block;
  /* An <img> is its own document, so currentColor inside the file resolves to
     black there and cannot follow this panel. The wordmark is flat black, and
     inverting flat black is exactly white — no approximation, and still one
     file rather than a second copy of the letterforms to keep in step. */
  filter: invert(1);
}

.rail__wordmark i {
  font-style: normal;
  font-size: 9.5px;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  color: #7e8593;
}

.rail__nav {
  padding: 12px 10px;
  display: grid;
  /* Rows must hug their content — a stretched grid would space the links out
     over the whole rail height. */
  align-content: start;
  gap: 1px;
  flex: 1;
}

.rail__group {
  font-family: var(--display);
  font-size: 10px;
  letter-spacing: 0.16em;
  text-transform: uppercase;
  color: #6a707e;
  padding: 16px 10px 6px;
}

.navlink {
  display: flex;
  align-items: center;
  gap: 10px;
  padding: 8px 10px;
  border-radius: var(--r-sm);
  font-size: 13.5px;
  color: #b9bfcb;
  transition: background 0.15s, color 0.15s;
}

.navlink:hover {
  background: rgb(255 255 255 / 6%);
  color: #fff;
}

.navlink.is-on {
  background: rgb(122 47 242 / 18%);
  color: #fff;
  box-shadow: inset 2px 0 0 var(--plasma);
}

.navlink__icon {
  display: flex;
  width: 16px;
  height: 16px;
  flex: none;
  opacity: 0.75;
}

/* Every icon in the app is 16 units on the same grid, so nothing has to be
   nudged into line one at a time. */
.icon {
  display: block;
  width: 16px;
  height: 16px;
}

.navlink__count {
  margin-left: auto;
  font-family: var(--mono);
  font-size: 11px;
  color: #8b91a0;
}

.rail__foot {
  padding: 12px 14px 16px;
  border-top: 1px solid rgb(255 255 255 / 8%);
  font-size: 11.5px;
  color: #7e8593;
}

.frame {
  display: flex;
  flex-direction: column;
  min-width: 0;
}

.topbar {
  height: var(--topbar-h);
  display: flex;
  align-items: center;
  gap: 14px;
  padding: 0 20px;
  background: var(--card);
  border-bottom: 1px solid var(--rule);
  position: sticky;
  top: 0;
  z-index: 20;
}

.topbar__menu {
  display: none;
}

.topbar__where {
  font-family: var(--display);
  font-size: 15px;
  font-weight: 600;
  letter-spacing: -0.01em;
  min-width: 0;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.topbar__tools {
  margin-left: auto;
  display: flex;
  align-items: center;
  gap: 14px;
}

.plantpick {
  display: flex;
  align-items: center;
  gap: 8px;
  border: 1px solid var(--rule);
  border-radius: var(--r-sm);
  padding: 4px 8px 4px 10px;
  background: var(--paper);
}

.plantpick__label {
  font-family: var(--display);
  font-size: 10px;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  color: var(--ash);
}

.plantpick select {
  border: 0;
  background: none;
  font-size: 13px;
  font-weight: 500;
  padding: 3px 0;
  cursor: pointer;
}

.who {
  display: flex;
  align-items: center;
  gap: 9px;
}

.who__id {
  text-align: right;
  line-height: 1.25;
}

.who__name {
  font-size: 13px;
  font-weight: 600;
}

.who__role {
  font-family: var(--display);
  font-size: 10px;
  letter-spacing: 0.12em;
  text-transform: uppercase;
  color: var(--ash);
}

.view {
  padding: 22px 20px 56px;
  max-width: 1500px;
  width: 100%;
}

.view:focus {
  outline: none;
}

/* ------------------------------------------------------------ page head --- */

.page {
  display: flex;
  align-items: flex-end;
  gap: 16px;
  flex-wrap: wrap;
  margin-bottom: 18px;
}

.page__title {
  font-size: 24px;
  letter-spacing: -0.025em;
}

.page__sub {
  color: var(--ash);
  font-size: 13px;
  margin-top: 3px;
}

.page__actions {
  margin-left: auto;
  display: flex;
  gap: 8px;
  flex-wrap: wrap;
}

.eyebrow {
  font-family: var(--display);
  font-size: 10.5px;
  letter-spacing: 0.16em;
  text-transform: uppercase;
  color: var(--ash);
  margin-bottom: 9px;
}

/* ---------------------------------------------------------------- cards --- */

.card {
  background: var(--card);
  border: 1px solid var(--rule);
  border-radius: var(--r-md);
  box-shadow: var(--shadow);
}

.card__head {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 12px;
  padding: 13px 16px;
  border-bottom: 1px solid var(--rule);
}

.card__title {
  font-size: 14px;
  font-weight: 600;
}

.card__body {
  padding: 16px;
}

.card__actions {
  margin-left: auto;
  display: flex;
  flex-wrap: wrap;
  gap: 6px;
}

.grid {
  display: grid;
  gap: 14px;
}

.grid--stats {
  grid-template-columns: repeat(auto-fit, minmax(178px, 1fr));
}

.grid--2 {
  grid-template-columns: repeat(auto-fit, minmax(320px, 1fr));
}

.stack {
  display: grid;
  gap: 18px;
}

/* ---------------------------------------------------------------- stats --- */

.stat {
  background: var(--card);
  border: 1px solid var(--rule);
  border-radius: var(--r-md);
  padding: 14px 16px 15px;
  box-shadow: var(--shadow);
}

.stat__label {
  font-family: var(--display);
  font-size: 10.5px;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  color: var(--ash);
}

.stat__value {
  font-size: 27px;
  font-weight: 600;
  letter-spacing: -0.03em;
  margin-top: 5px;
  line-height: 1.05;
}

.stat__unit {
  font-family: var(--body);
  font-size: 12px;
  font-weight: 500;
  color: var(--ash);
  margin-left: 4px;
  letter-spacing: 0;
}

.stat__foot {
  margin-top: 6px;
  font-size: 12px;
  color: var(--ash);
}

.stat--accent {
  border-color: var(--plasma);
  box-shadow: 0 0 0 3px var(--plasma-soft), var(--shadow);
}

.stat--accent .stat__value {
  color: var(--plasma-ink);
}

/* =====================================================================
   SIGNATURE — the reactor strip.
   One card per chamber. The violet bar only animates while that chamber is
   actually growing, so the room's live state is readable from the doorway.
   ===================================================================== */

.reactors {
  display: grid;
  gap: 12px;
  grid-template-columns: repeat(auto-fill, minmax(232px, 1fr));
}

.reactor {
  position: relative;
  overflow: hidden;
  background: var(--card);
  border: 1px solid var(--rule);
  border-radius: var(--r-md);
  padding: 14px 15px 15px;
  box-shadow: var(--shadow);
  text-align: left;
  width: 100%;
  cursor: pointer;
  transition: transform 0.16s, box-shadow 0.16s, border-color 0.16s;
}

.reactor:hover {
  transform: translateY(-2px);
  box-shadow: var(--shadow-lift);
  border-color: var(--rule-strong);
}

/* The chamber window. Dark and inert until plasma is struck. */
.reactor__glow {
  position: absolute;
  inset: 0 0 auto;
  height: 3px;
  background: var(--rule-strong);
}

.reactor.is-live .reactor__glow {
  background: linear-gradient(
    90deg,
    transparent 0%,
    var(--plasma) 35%,
    #c9a6ff 50%,
    var(--plasma) 65%,
    transparent 100%
  );
  background-size: 220% 100%;
  animation: plasma-sweep 3.4s ease-in-out infinite;
}

.reactor.is-out .reactor__glow {
  background: var(--ember);
}

@keyframes plasma-sweep {
  0%,
  100% {
    background-position: 130% 0;
    opacity: 0.55;
  }
  50% {
    background-position: -30% 0;
    opacity: 1;
  }
}

.reactor__head {
  display: flex;
  align-items: baseline;
  gap: 8px;
}

.reactor__code {
  font-family: var(--display);
  font-size: 15px;
  font-weight: 700;
  letter-spacing: 0.04em;
}

.reactor__name {
  font-size: 11.5px;
  color: var(--ash);
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.reactor__ct {
  font-size: 25px;
  font-weight: 600;
  letter-spacing: -0.03em;
  margin: 10px 0 2px;
}

.reactor__ct small {
  font-family: var(--body);
  font-size: 11px;
  font-weight: 500;
  color: var(--ash);
  margin-left: 3px;
}

.reactor__meta {
  display: flex;
  flex-wrap: wrap;
  gap: 4px 10px;
  font-size: 11.5px;
  color: var(--ash);
  margin-top: 7px;
  padding-top: 9px;
  border-top: 1px dashed var(--rule);
}

.reactor__meta b {
  font-family: var(--mono);
  font-weight: 500;
  color: var(--carbon);
}

/* The chamber clock. Only a growing chamber has one, so its presence on the
   card is itself information. */
.runclock {
  display: flex;
  align-items: center;
  gap: 8px;
  margin-top: 8px;
  padding-top: 8px;
  border-top: 1px dashed var(--rule);
}

.runclock__t {
  font-family: var(--mono);
  font-variant-numeric: tabular-nums;
  font-size: 13.5px;
  font-weight: 600;
  letter-spacing: -0.02em;
  color: var(--plasma-ink);
}

.runclock__fix {
  margin-left: auto;
  border: 1px solid var(--rule);
  background: var(--card);
  border-radius: var(--r-sm);
  font-family: var(--display);
  font-size: 10px;
  letter-spacing: 0.12em;
  text-transform: uppercase;
  color: var(--ash);
  padding: 2px 7px;
  cursor: pointer;
}

.runclock__fix:hover {
  color: var(--carbon);
  border-color: var(--ash);
}

.reactor--idle {
  background: transparent;
  border-style: dashed;
  box-shadow: none;
  cursor: default;
}

.reactor--idle:hover {
  transform: none;
  box-shadow: none;
}

.reactor--idle .reactor__ct {
  color: var(--mist);
}

/* ---------------------------------------------------------------- table --- */

.tablewrap {
  overflow-x: auto;
}

table.data {
  width: 100%;
  border-collapse: collapse;
  font-size: 13px;
}

table.data th {
  font-family: var(--display);
  /* Bigger and darker than it was. A column heading is what tells you what a
     number MEANS, and at 10.5px in secondary grey it was the quietest thing on
     a screen full of figures — people were reading numbers and guessing the
     column. */
  font-size: 11.5px;
  letter-spacing: 0.11em;
  text-transform: uppercase;
  color: var(--carbon);
  text-align: left;
  font-weight: 700;
  padding: 9px 12px;
  border-bottom: 1.5px solid var(--rule-strong);
  white-space: nowrap;
  background: var(--card);
}

table.data td {
  padding: 10px 12px;
  border-bottom: 1px solid var(--rule);
  vertical-align: middle;
}

table.data tbody tr:last-child td {
  border-bottom: 0;
}

table.data tbody tr.is-clickable {
  cursor: pointer;
}

table.data tbody tr.is-clickable:hover {
  background: var(--paper);
}

/* Written to out-rank `table.data th`, which is the whole point of it.
   `th.num` alone is a weaker selector than `table.data th`, so the heading of
   every numeric column in the app was quietly left-aligned while its numbers
   were right-aligned — the heading sat at one end of the column and its
   figures at the other, and the eye had nothing to run down. */
table.data th.num,
table.data td.num,
th.num,
td.num {
  text-align: right;
}

.mono {
  font-family: var(--mono);
  font-variant-numeric: tabular-nums;
}

.code {
  font-family: var(--mono);
  font-size: 12.5px;
  font-weight: 500;
}

.muted {
  color: var(--ash);
}

.tiny {
  font-size: 11.5px;
}

/* --------------------------------------------------------------- badges --- */

.badge {
  display: inline-flex;
  align-items: center;
  gap: 5px;
  font-family: var(--display);
  font-size: 10.5px;
  font-weight: 600;
  letter-spacing: 0.1em;
  text-transform: uppercase;
  padding: 3px 8px;
  border-radius: 999px;
  border: 1px solid transparent;
  white-space: nowrap;
}

.badge::before {
  content: '';
  width: 5px;
  height: 5px;
  border-radius: 50%;
  background: currentColor;
}


/* --------------------------------------------------------------- buttons -- */

.btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: 7px;
  border: 1px solid var(--rule-strong);
  background: var(--card);
  color: var(--carbon);
  border-radius: var(--r-sm);
  padding: 8px 14px;
  font-size: 13px;
  font-weight: 600;
  cursor: pointer;
  transition: background 0.15s, border-color 0.15s, transform 0.1s;
}

.btn:hover:not(:disabled) {
  background: var(--paper);
  border-color: var(--ash);
}

.btn:active:not(:disabled) {
  transform: translateY(1px);
}

.btn:disabled {
  opacity: 0.5;
  cursor: not-allowed;
}

/* The one primary action on a screen. Violet is a state in this app — a
   chamber that is live — so it is never spent on a button. */
.btn--primary {
  background: var(--carbon);
  border-color: var(--carbon);
  color: #fff;
}

.btn--primary:hover:not(:disabled) {
  background: #24272f;
  border-color: #24272f;
}

.btn--danger {
  color: var(--ember);
  border-color: #f0c3ab;
  background: var(--ember-soft);
}

.btn--sm {
  padding: 5px 10px;
  font-size: 12px;
}

.btn--block {
  width: 100%;
}

.iconbtn {
  border: 1px solid var(--rule);
  background: var(--card);
  border-radius: var(--r-sm);
  width: 32px;
  height: 32px;
  display: grid;
  place-items: center;
  cursor: pointer;
}

/* ----------------------------------------------------------------- forms -- */

.field {
  display: grid;
  gap: 5px;
  min-width: 0;
}

.field > label,
.label {
  font-family: var(--display);
  font-size: 10.5px;
  letter-spacing: 0.13em;
  text-transform: uppercase;
  color: var(--ash);
}

.field input:not([type='checkbox']):not([type='radio']),
.field select,
.field textarea {
  border: 1px solid var(--rule-strong);
  background: var(--card);
  border-radius: var(--r-sm);
  padding: 9px 11px;
  font-size: 13.5px;
  width: 100%;
  transition: border-color 0.15s, box-shadow 0.15s;
}

/* Tick boxes size themselves and sit against their label. */
.field input[type='checkbox'],
.field input[type='radio'] {
  width: auto;
  flex: none;
  margin: 0;
  accent-color: var(--plasma);
}

.field input:not([type='checkbox']):not([type='radio']):focus,
.field select:focus,
.field textarea:focus {
  outline: none;
  border-color: var(--plasma);
  box-shadow: 0 0 0 3px var(--plasma-soft);
}

.field input[readonly] {
  background: var(--paper);
  color: var(--ash);
}

.field textarea {
  min-height: 74px;
  resize: vertical;
}

.field__hint {
  font-size: 11.5px;
  color: var(--ash);
}

.formgrid {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(190px, 1fr));
  gap: 13px;
}

.formgrid .span-2 {
  grid-column: span 2;
}

.formrow {
  display: flex;
  gap: 8px;
  align-items: flex-end;
  flex-wrap: wrap;
}

.filters {
  display: flex;
  gap: 8px;
  align-items: center;
  flex-wrap: wrap;
  margin-bottom: 14px;
}

.search {
  flex: 1 1 220px;
  min-width: 180px;
}

.chip {
  border: 1px solid var(--rule-strong);
  background: var(--card);
  border-radius: 999px;
  padding: 5px 12px;
  font-size: 12.5px;
  font-weight: 600;
  cursor: pointer;
}

.chip.is-on {
  background: var(--carbon);
  border-color: var(--carbon);
  color: #fff;
}

/* ---------------------------------------------------------------- modal --- */

.modal {
  position: fixed;
  inset: 0;
  z-index: 60;
  display: grid;
  place-items: center;
  padding: 18px;
  background: rgb(22 24 29 / 45%);
  animation: fade 0.14s ease;
}

.modal__panel {
  background: var(--card);
  border-radius: var(--r-lg);
  box-shadow: var(--shadow-lift);
  width: min(680px, 100%);
  max-height: min(88vh, 900px);
  display: flex;
  flex-direction: column;
  animation: rise 0.16s cubic-bezier(0.22, 1, 0.36, 1);
}

.modal__panel--wide {
  width: min(1040px, 100%);
}

.modal__head {
  display: flex;
  align-items: center;
  gap: 12px;
  padding: 15px 18px;
  border-bottom: 1px solid var(--rule);
}

.modal__title {
  font-size: 16px;
}

.modal__body {
  padding: 18px;
  overflow-y: auto;
}

.modal__foot {
  padding: 13px 18px;
  border-top: 1px solid var(--rule);
  display: flex;
  gap: 8px;
  justify-content: flex-end;
  background: var(--paper);
  border-radius: 0 0 var(--r-lg) var(--r-lg);
}

@keyframes fade {
  from {
    opacity: 0;
  }
}

@keyframes rise {
  from {
    opacity: 0;
    transform: translateY(8px);
  }
}

/* --------------------------------------------------------------- toasts --- */

.toasts {
  position: fixed;
  right: 18px;
  bottom: 18px;
  z-index: 90;
  display: grid;
  gap: 8px;
  justify-items: end;
}

.toast {
  background: var(--carbon);
  color: #fff;
  padding: 10px 15px;
  border-radius: var(--r-sm);
  font-size: 13px;
  box-shadow: var(--shadow-lift);
  max-width: 380px;
  animation: rise 0.16s ease;
}

.toast--bad {
  background: var(--ember);
}

.toast--good {
  background: var(--jade);
}

/* -------------------------------------------------------------- notices --- */

.empty {
  text-align: center;
  padding: 40px 20px;
  color: var(--ash);
}

.empty__title {
  font-family: var(--display);
  font-size: 15px;
  color: var(--carbon);
  margin-bottom: 4px;
}

.alert {
  border: 1px solid var(--rule);
  border-left: 3px solid var(--ash);
  background: var(--card);
  border-radius: var(--r-sm);
  padding: 11px 14px;
  font-size: 13px;
}

.alert--bad {
  border-left-color: var(--ember);
  background: var(--ember-soft);
  border-color: #f7cdb8;
  color: #8a2c05;
}

/* Something went right. The app had a bad and a warn and no good, so every
   "that worked" was being shown in the plain grey of a notice. */
.alert--good {
  border-left-color: var(--jade);
  background: var(--jade-soft);
}

.alert--warn {
  border-left-color: var(--amber);
  background: var(--amber-soft);
  border-color: #f0d8ae;
  color: #7a3a06;
}

/* --------------------------------------------------------------- ledger --- */

/* The piece history. A real timeline: order carries meaning here because it is
   literally the sequence of things that happened to one physical object. */
.ledger {
  display: grid;
  gap: 0;
}

.ledger__row {
  display: grid;
  grid-template-columns: 130px 1fr;
  gap: 14px;
  padding: 12px 0;
  border-bottom: 1px solid var(--rule);
}

.ledger__row:last-child {
  border-bottom: 0;
}

.ledger__when {
  font-family: var(--mono);
  font-size: 11.5px;
  color: var(--ash);
  padding-top: 2px;
}

.ledger__what {
  min-width: 0;
}

.ledger__type {
  font-family: var(--display);
  font-size: 11px;
  letter-spacing: 0.11em;
  text-transform: uppercase;
  font-weight: 600;
}

.ledger__detail {
  font-size: 12.5px;
  color: var(--ash);
  margin-top: 3px;
}

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

.delta--up {
  color: var(--jade);
}

.delta--down {
  color: var(--ember);
}

/* ------------------------------------------------------------------ bar --- */

.bar {
  height: 6px;
  border-radius: 999px;
  background: var(--paper);
  overflow: hidden;
}

.bar__fill {
  height: 100%;
  background: var(--plasma);
  border-radius: 999px;
}

.bar__fill--ember {
  background: var(--ember);
}

.bar__fill--jade {
  background: var(--jade);
}

/* ----------------------------------------------------------- responsive --- */

@media (max-width: 1000px) {
  .app {
    grid-template-columns: 1fr;
  }

  .rail {
    position: fixed;
    inset: 0 auto 0 0;
    width: var(--rail-w);
    z-index: 50;
    transform: translateX(-100%);
    transition: transform 0.2s ease;
    box-shadow: var(--shadow-lift);
  }

  .rail.is-open {
    transform: none;
  }

  .topbar__menu {
    display: grid;
  }

  .plantpick__label {
    display: none;
  }

  .who__id {
    display: none;
  }
}

@media (max-width: 620px) {
  .view {
    padding: 16px 13px 48px;
  }

  .page__title {
    font-size: 20px;
  }

  .formgrid .span-2 {
    grid-column: span 1;
  }

  .ledger__row {
    grid-template-columns: 1fr;
    gap: 3px;
  }

  .modal__foot {
    flex-direction: column-reverse;
  }

  .modal__foot .btn {
    width: 100%;
  }
}

/* Nothing here is load-bearing on motion — turn it all off on request. */
@media (prefers-reduced-motion: reduce) {
  *,
  *::before,
  *::after {
    animation-duration: 0.01ms !important;
    animation-iteration-count: 1 !important;
    transition-duration: 0.01ms !important;
  }

  .reactor.is-live .reactor__glow {
    background: var(--plasma);
    opacity: 1;
  }
}

@media print {
  .rail,
  .topbar,
  .page__actions,
  .filters {
    display: none !important;
  }

  .app {
    grid-template-columns: 1fr;
  }

  .card,
  .stat {
    box-shadow: none;
    break-inside: avoid;
  }
}

/* ------------------------------------------------------------ QR labels -- */

/* Laid out in real millimetres so the sheet comes off the printer at the size
   the label stock actually is. */
.labelsheet {
  display: grid;
  grid-template-columns: repeat(auto-fill, var(--label-w, 38mm));
  gap: 2mm;
  background: var(--card);
  border: 1px solid var(--rule);
  border-radius: var(--r-md);
  padding: 4mm;
}

.qrlabel {
  width: var(--label-w, 38mm);
  height: var(--label-h, 21mm);
  border: 1px dashed var(--rule-strong);
  border-radius: 1mm;
  padding: 1.2mm;
  display: flex;
  align-items: center;
  gap: 1.5mm;
  overflow: hidden;
  background: #fff;
  break-inside: avoid;
}

.qrlabel__qr {
  flex: none;
  display: block;
}

.qrlabel__text {
  min-width: 0;
  line-height: 1.15;
}

.qrlabel__code {
  font-family: var(--mono);
  font-size: 2.6mm;
  font-weight: 600;
  color: #000;
  white-space: nowrap;
}

.qrlabel__meta {
  font-family: var(--mono);
  font-size: 2mm;
  color: #444;
  margin-top: 0.6mm;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}

@media print {
  .no-print {
    display: none !important;
  }

  @page {
    margin: 8mm;
  }

  .view {
    padding: 0;
    max-width: none;
  }

  /* Cut guides are for the screen; on paper the label stock has its own edges. */
  .labelsheet {
    border: 0;
    padding: 0;
    gap: 0;
    background: none;
  }

  .qrlabel {
    border-color: transparent;
  }
}

/* ----------------------------------------------------------- floor board -- */

/* One column per department, in the order a batch actually travels. It scrolls
   sideways rather than wrapping, so the route stays readable as a route. */
.board {
  display: grid;
  grid-auto-flow: column;
  grid-auto-columns: minmax(190px, 1fr);
  gap: 10px;
  overflow-x: auto;
  padding-bottom: 8px;
  align-items: start;
}

.board__col {
  background: var(--card);
  border: 1px solid var(--rule);
  border-radius: var(--r-md);
  box-shadow: var(--shadow);
  min-width: 0;
}

.board__head {
  display: flex;
  align-items: center;
  gap: 8px;
  padding: 11px 12px;
  border-bottom: 1px solid var(--rule);
}

.board__count {
  margin-left: auto;
  font-size: 12px;
  color: var(--ash);
}

.board__body {
  padding: 9px;
  display: grid;
  gap: 8px;
  min-height: 64px;
}

.board__empty {
  color: var(--mist);
  text-align: center;
  font-size: 12px;
  padding: 12px 0;
}

.bcard {
  border: 1px solid var(--rule);
  border-left: 3px solid var(--rule-strong);
  border-radius: var(--r-sm);
  padding: 9px 10px 10px;
  background: var(--card);
  cursor: pointer;
  transition: transform 0.14s, box-shadow 0.14s, border-color 0.14s;
}

.bcard:hover {
  transform: translateY(-1px);
  box-shadow: var(--shadow);
}

/* The left edge carries the state colour — same language as the badges. */
.bcard--growing {
  border-left-color: var(--plasma);
  background: linear-gradient(90deg, var(--plasma-soft), var(--card) 60%);
}
.bcard--processing {
  border-left-color: var(--amber);
}
.bcard--out {
  border-left-color: var(--ember);
}
.bcard--ready {
  border-left-color: var(--jade);
  background: linear-gradient(90deg, var(--jade-soft), var(--card) 60%);
}

.bcard__top {
  display: flex;
  align-items: baseline;
  gap: 7px;
  /* A long imported batch code and the seed count both have to fit on a phone;
     the count drops to its own line rather than off the edge of the card. */
  flex-wrap: wrap;
}

.bcard__no {
  font-family: var(--mono);
  font-size: 13px;
  font-weight: 600;
}

.bcard__no:hover {
  text-decoration: underline;
}

.bcard__sys {
  margin-left: auto;
  font-family: var(--display);
  font-size: 10px;
  letter-spacing: 0.1em;
  color: var(--ash);
}

.bcard__ct {
  font-family: var(--mono);
  font-variant-numeric: tabular-nums;
  font-size: 18px;
  font-weight: 600;
  letter-spacing: -0.02em;
  margin: 5px 0 1px;
}

.bcard__ct small {
  font-family: var(--body);
  font-size: 10px;
  font-weight: 500;
  color: var(--ash);
  margin-left: 3px;
}

.bcard__meta {
  font-size: 11px;
  color: var(--ash);
}

.bcard__move {
  margin-top: 8px;
  width: 100%;
  border: 1px solid var(--rule);
  background: var(--paper);
  border-radius: var(--r-sm);
  font-family: var(--display);
  font-size: 10.5px;
  letter-spacing: 0.1em;
  text-transform: uppercase;
  color: var(--ash);
  padding: 4px;
  cursor: pointer;
  opacity: 0;
  transition: opacity 0.14s, color 0.14s, border-color 0.14s;
}

/* The action appears on the card you are actually pointing at, so a wall of
   buttons does not compete with the numbers. */
.bcard:hover .bcard__move,
.bcard:focus-within .bcard__move {
  opacity: 1;
}

.bcard__move:hover {
  color: var(--carbon);
  border-color: var(--ash);
}

@media (hover: none) {
  .bcard__move {
    opacity: 1;
  }
}

/* --------------------------------------------------------- permissions -- */

/* The permission grid is the one place an admin decides what a person can do,
   so it is laid out to be read in one pass rather than scrolled through. */
.permgrid {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(240px, 1fr));
  gap: 10px;
}

.permgroup {
  border: 1px solid var(--rule);
  border-radius: var(--r-sm);
  padding: 8px 11px 10px;
  margin: 0;
  min-width: 0;
}

.permgroup legend {
  font-family: var(--display);
  font-size: 10.5px;
  letter-spacing: 0.13em;
  text-transform: uppercase;
  color: var(--ash);
  padding: 0 5px;
  display: flex;
  align-items: center;
  gap: 8px;
}

.permgroup__all {
  border: 0;
  background: none;
  padding: 0;
  font: inherit;
  font-size: 9.5px;
  color: var(--plasma);
  cursor: pointer;
  text-transform: lowercase;
  letter-spacing: 0.06em;
}

.permgroup__all:hover {
  text-decoration: underline;
}

.permline {
  display: flex;
  align-items: flex-start;
  gap: 8px;
  font-size: 12.5px;
  padding: 3px 0;
  cursor: pointer;
}

.permline input {
  flex: none;
  margin-top: 2px;
}

.permline:hover {
  color: var(--plasma-ink);
}

/* ------------------------------------------------------------ date range -- */

.daterange {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 7px;
  border: 1px solid var(--rule);
  border-radius: var(--r-sm);
  padding: 4px 8px;
  background: var(--card);
}

.daterange__label {
  font-family: var(--display);
  font-size: 10px;
  letter-spacing: 0.13em;
  text-transform: uppercase;
  color: var(--ash);
}

.daterange input {
  min-width: 0;
  border: 1px solid var(--rule);
  border-radius: var(--r-sm);
  padding: 4px 7px;
  font-size: 12.5px;
  font-family: var(--mono);
}

.daterange input:focus {
  outline: none;
  border-color: var(--plasma);
  box-shadow: 0 0 0 3px var(--plasma-soft);
}

/* ------------------------------------------------------ scheduled jobs -- */

/* The left edge carries urgency, so a wall of jobs sorts itself out visually
   before you read a single word. */
.jobcard {
  border-left: 3px solid var(--rule-strong);
}

.jobcard--overdue {
  border-left-color: var(--ember);
  background: linear-gradient(90deg, var(--ember-soft), var(--card) 55%);
}

.jobcard--due {
  border-left-color: var(--ember);
}

.jobcard--soon {
  border-left-color: var(--amber);
}

.jobcard--ok {
  border-left-color: var(--jade);
}

/* The plant-floor reminder strip. */
.duestrip {
  display: grid;
  gap: 7px;
}

.duerow {
  display: flex;
  align-items: baseline;
  gap: 9px;
  padding: 7px 0;
  border-bottom: 1px solid var(--rule);
  font-size: 12.5px;
}

.duerow:last-child {
  border-bottom: 0;
}

.duerow__when {
  margin-left: auto;
  font-family: var(--mono);
  font-size: 11.5px;
  white-space: nowrap;
}

.duerow--overdue .duerow__when {
  color: var(--ember);
  font-weight: 600;
}

.duerow--soon .duerow__when {
  color: var(--amber);
}

/* ------------------------------------------------------- status colours -- */

/* Seven tones, defined once in public/js/status.js. Everything that shows a
   state uses these, so the same colour always means the same thing whether it
   is a batch, a system, a seed, a block or a maintenance job. */
.badge--idle {
  color: var(--ash);
  background: var(--paper);
  border-color: var(--rule);
}
.badge--live {
  color: var(--plasma-ink);
  background: var(--plasma-soft);
  border-color: #dfcbfb;
}
.badge--work {
  color: var(--amber);
  background: var(--amber-soft);
  border-color: #f0d8ae;
}
.badge--alert {
  color: var(--ember);
  background: var(--ember-soft);
  border-color: #f7cdb8;
}
.badge--good {
  color: var(--jade);
  background: var(--jade-soft);
  border-color: #b9e5d4;
}
.badge--done {
  color: #fff;
  background: var(--carbon);
}
.badge--dead {
  color: #fff;
  background: var(--ember);
}

/* The same seven as a left edge, for cards. */
.edge--idle { border-left-color: var(--rule-strong); }
.edge--live { border-left-color: var(--plasma); }
.edge--work { border-left-color: var(--amber); }
.edge--alert { border-left-color: var(--ember); }
.edge--good { border-left-color: var(--jade); }
.edge--done { border-left-color: var(--carbon); }
.edge--dead { border-left-color: var(--ember); }

/* --------------------------------------------------------- small screens -- */

/* Below 720px every data table becomes a stack of labelled cards. The labels
   come from `enhanceTables` in ui.js, which copies each column heading onto
   its cells, so no table markup has to be written twice. */
@media (max-width: 720px) {
  .tablewrap {
    overflow-x: visible;
  }

  table.data,
  table.data tbody,
  table.data tr,
  table.data td {
    display: block;
    width: auto;
  }

  table.data thead,
  table.data tfoot {
    display: none;
  }

  table.data tr {
    border: 1px solid var(--rule);
    border-radius: var(--r-sm);
    padding: 10px 12px;
    margin: 0 12px 10px;
    background: var(--card);
  }

  table.data tr:first-child {
    margin-top: 12px;
  }

  table.data td {
    border: 0;
    padding: 3px 0;
    display: flex;
    align-items: baseline;
    gap: 10px;
    text-align: left !important;
  }

  /* The heading sits inline with its value, so the eye reads label → value
     instead of hunting up to a header row that is no longer there. */
  table.data td[data-th]::before {
    content: attr(data-th);
    flex: none;
    min-width: 108px;
    font-family: var(--display);
    font-size: 10px;
    letter-spacing: 0.12em;
    text-transform: uppercase;
    color: var(--ash);
  }

  table.data td.cell--actions {
    justify-content: flex-end;
    padding-top: 8px;
    margin-top: 6px;
    border-top: 1px dashed var(--rule);
  }

  /* A card is the row, so the row-level click target still works. */
  table.data tbody tr.is-clickable:hover {
    border-color: var(--rule-strong);
  }

  /* Two columns of stat tiles is the most a phone can carry. */
  .grid--stats {
    grid-template-columns: repeat(2, 1fr);
  }

  .page__actions {
    width: 100%;
  }

  /* A grid track sizes itself to the widest child unless that child is allowed
     to shrink, so one wide table used to push the whole page sideways. */
  .stack > *,
  .grid > * {
    min-width: 0;
  }

  /* A long id, email or moly code has no space to break at, so let it break
     anywhere rather than widen the card it sits in. */
  table.data td,
  .card .tiny {
    min-width: 0;
    overflow-wrap: anywhere;
  }

  /* A date input keeps an intrinsic width no phone can honour, so on a narrow
     screen the range breaks into two lines instead of pushing the page sideways. */
  .daterange {
    width: 100%;
  }

  .daterange input {
    flex: 1 1 130px;
  }

  .reactors {
    grid-template-columns: 1fr;
  }

  .permgrid {
    grid-template-columns: 1fr;
  }
}

/* ------------------------------------------------------------- movement -- */

/* One short settle when a screen arrives. It says "this is a different place"
   without making anyone wait — and the reduced-motion rule above turns it off
   for anyone who asks. */
.view--in {
  animation: view-in 0.18s cubic-bezier(0.22, 1, 0.36, 1);
}

@keyframes view-in {
  from {
    opacity: 0;
    transform: translateY(5px);
  }
}

/* Marks the thing that just changed, so a save lands somewhere visible instead
   of the list silently redrawing. */
.just-changed {
  animation: landed 1.1s ease-out;
}

@keyframes landed {
  0% {
    background: var(--plasma-soft);
    box-shadow: inset 3px 0 0 var(--plasma);
  }
  100% {
    background: transparent;
    box-shadow: inset 3px 0 0 transparent;
  }
}

/* ------------------------------------------------- installed app, network -- */

/* One column along the bottom edge. Anything that needs to speak to the whole
   app goes in here, so two messages stack instead of covering each other. */
.dock {
  position: fixed;
  left: 0;
  right: 0;
  bottom: 0;
  z-index: 85;
  display: grid;
  gap: 10px;
  padding-bottom: env(safe-area-inset-bottom);
  pointer-events: none;
}

.dock > * {
  pointer-events: auto;
}

/* Asking to install, or saying an update is waiting. One question at a time. */
.pwabar {
  order: 1;
  margin: 0 12px;
  display: flex;
  align-items: center;
  gap: 12px;
  padding: 11px 12px 11px 15px;
  border: 1px solid var(--rule);
  border-radius: var(--r-md);
  background: var(--card);
  box-shadow: var(--shadow-lift);
  transform: translateY(14px);
  opacity: 0;
  transition:
    transform 0.22s ease,
    opacity 0.22s ease;
}

.pwabar.is-up {
  transform: none;
  opacity: 1;
}

.pwabar__text {
  flex: 1;
  font-size: 13px;
  min-width: 0;
}

.pwabar__actions {
  display: flex;
  align-items: center;
  gap: 6px;
  flex: none;
}

.pwabar__x {
  border: 0;
  background: none;
  color: var(--mist);
  font-size: 19px;
  line-height: 1;
  padding: 2px 6px;
  cursor: pointer;
  border-radius: var(--r-sm);
}

.pwabar__x:hover {
  color: var(--carbon);
  background: var(--paper);
}

/* The network being gone is a state, not a notification — it holds the bottom
   edge until it is true again. */
.netstrip {
  order: 2;
  padding: 9px 16px;
  text-align: center;
  font-family: var(--display);
  font-size: 12px;
  letter-spacing: 0.06em;
  text-transform: uppercase;
  color: #fff;
  background: var(--ember);
}

/* Toasts keep clear of whatever the dock is currently holding. */
.toasts {
  bottom: calc(18px + var(--dock-h, 0px));
}

/* Installed, there is no browser chrome to sit under — the app owns the notch
   and the home indicator. */
@media (display-mode: standalone) {
  .topbar {
    height: calc(var(--topbar-h) + env(safe-area-inset-top));
    padding-top: env(safe-area-inset-top);
  }

  .rail {
    padding-top: env(safe-area-inset-top);
  }

  .view {
    padding-bottom: calc(56px + env(safe-area-inset-bottom));
  }
}

/* ------------------------------------------------------------------ facts -- */

/* Label above value, as many across as fit. Used where a card is answering a
   list of short questions rather than showing a table. */
.factgrid {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(170px, 1fr));
  gap: 14px 18px;
}

.fact__k {
  display: block;
  font-family: var(--display);
  font-size: 10px;
  letter-spacing: 0.13em;
  text-transform: uppercase;
  color: var(--ash);
}

.fact__v {
  display: block;
  font-size: 14px;
  font-weight: 600;
  overflow-wrap: anywhere;
}

.fact__v + .tiny {
  color: var(--ash);
}

/* ---------------------------------------------------------- board clock -- */

/* The time a batch has spent in its column, ticking. Same face as the run
   clock on the plant floor, so a number that is time always looks the same. */
.bcard__clock {
  font-family: var(--mono);
  font-variant-numeric: tabular-nums;
  font-size: 13px;
  font-weight: 600;
  letter-spacing: -0.02em;
  /* Violet means growing, everywhere in this software. A batch that is only
     waiting gets the quiet colour, so the two are never read as the same
     thing at a glance. */
  color: var(--plasma-ink);
  margin: 2px 0 3px;
}
.bcard__clock--waiting {
  color: var(--ash);
  font-weight: 500;
}
.bcard__clock--none {
  color: var(--ash);
  font-weight: 500;
  font-style: italic;
  font-family: var(--body);
  font-size: 12px;
}

/* --------------------------------------------------------------- timeline -- */

/* Where a batch has been, top to bottom. The live entry glows the way a live
   reactor does; a finished one is a plain dot. */
.timeline {
  list-style: none;
  margin: 0;
  padding: 4px 0 0 18px;
  border-left: 2px solid var(--rule);
}

.timeline__item {
  position: relative;
  padding: 0 0 16px 16px;
}

.timeline__item::before {
  content: '';
  position: absolute;
  left: -7px;
  top: 5px;
  width: 10px;
  height: 10px;
  border-radius: 50%;
  background: var(--rule-strong);
  border: 2px solid var(--card);
}

.timeline__item--live::before {
  background: var(--plasma);
  box-shadow: 0 0 0 4px var(--plasma-soft);
}

.timeline__item--good::before {
  background: var(--jade);
}

.timeline__when {
  font-family: var(--mono);
  font-variant-numeric: tabular-nums;
  font-size: 11px;
  color: var(--ash);
}

.timeline__what {
  font-weight: 600;
  margin-top: 1px;
}

.timeline__meta {
  font-size: 12px;
  color: var(--ash);
  margin-top: 1px;
}

.timeline__span {
  font-family: var(--mono);
  font-variant-numeric: tabular-nums;
  font-size: 11px;
  color: var(--plasma-ink);
  margin-left: 6px;
}

/* ------------------------------------------------------------------- demo -- */

/* A plant that exists to be shown carries this on every screen of it. It is
   deliberately loud and deliberately not dismissible: the whole risk of having
   sample data inside the real software is somebody reading a made-up figure as
   production, and a band that can be closed is a band that will be. */

.demobar {
  background: repeating-linear-gradient(
    135deg,
    var(--amber) 0 14px,
    #a04a08 14px 28px
  );
  color: #fff;
  font-family: var(--display);
  font-size: 11.5px;
  font-weight: 700;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  text-align: center;
  padding: 5px 12px;
  position: sticky;
  top: 0;
  z-index: 60;
}

.demobar__what {
  letter-spacing: 0;
  text-transform: none;
  font-weight: 500;
  opacity: 0.92;
  margin-left: 8px;
}

@media (max-width: 620px) {
  .demobar__what {
    display: none;
  }
}

/* ------------------------------------------------------------------ charts -- */

.chart {
  width: 100%;
  height: auto;
  display: block;
  overflow: visible;
}

.chart__tick {
  font-family: var(--mono);
  font-size: 10px;
  fill: var(--ash);
}

.chart__value {
  font-family: var(--mono);
  font-size: 10px;
  font-weight: 600;
  fill: var(--carbon);
}

.chart__legend {
  display: flex;
  flex-wrap: wrap;
  gap: 6px 14px;
  font-size: 11.5px;
  color: var(--ash);
  margin: 8px 0 0;
}

.chart__swatch {
  display: inline-block;
  width: 10px;
  height: 10px;
  border-radius: 2px;
  margin-right: 5px;
  vertical-align: -1px;
}

/* The row of time controls above the charts. A phone gets one control per
   line rather than a select stretched wide enough to hold "M-1 · OLD-…-1 · R2"
   and a page that scrolls sideways because of it. */

.timepick {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 8px 10px;
  margin-top: 6px;
}

.timepick .plantpick {
  flex: 1 1 210px;
  min-width: 0;
  max-width: 100%;
}

.timepick .plantpick select,
.timepick .plantpick input {
  min-width: 0;
  max-width: 100%;
  flex: 1 1 auto;
  text-overflow: ellipsis;
}

.timepick .plantpick input {
  border: 0;
  background: none;
  font: inherit;
  font-size: 13px;
  font-weight: 500;
  padding: 3px 0;
  color: inherit;
}

.timepick .btn {
  flex: none;
}

/* A chart you can drag across. The selection is a plain box drawn over the
   picture — the chart itself is never redrawn while the finger is down, so a
   drag stays smooth on the tablet by the reactor. */

.chartwrap {
  position: relative;
  touch-action: pan-y;
  cursor: crosshair;
  user-select: none;
}

.chartwrap__sel {
  position: absolute;
  top: 0;
  bottom: 22px;
  background: var(--plasma);
  opacity: 0.14;
  border-left: 1px solid var(--plasma);
  border-right: 1px solid var(--plasma);
  pointer-events: none;
}

.zoombar {
  display: flex;
  align-items: center;
  flex-wrap: wrap;
  gap: 8px 12px;
  padding: 0 2px 12px;
}

.zoombar__hint {
  margin-left: auto;
}

/* What the software found, in words. One line each: whose system, what
   happened, when. Read top to bottom it is the story of the run. */

.findings {
  margin-top: 12px;
}

.findings__list {
  list-style: none;
  margin: 6px 0 0;
  padding: 0;
  display: grid;
  gap: 4px;
}

.finding {
  display: flex;
  align-items: baseline;
  flex-wrap: wrap;
  gap: 4px 8px;
  font-size: 12.5px;
  padding: 4px 0;
  border-bottom: 1px solid var(--rule);
}

.finding:last-child {
  border-bottom: 0;
}

.finding__dot {
  width: 7px;
  height: 7px;
  border-radius: 50%;
  flex: none;
  align-self: center;
}

.finding__who {
  font-size: 11.5px;
  font-weight: 600;
  color: var(--ash);
  min-width: 34px;
}

.finding__what {
  flex: 1 1 240px;
}

@media (max-width: 700px) {
  .zoombar__hint {
    margin-left: 0;
    flex-basis: 100%;
  }
}

/* ------------------------------------------------------------- piece picker -- */

/* Ticking seeds into a run. A sticky group heading, one row per piece, and a
   search box because sixty codes is too many to scan by eye. */
.pick {
  max-height: 280px;
  overflow: auto;
  border: 1px solid var(--rule);
  border-radius: var(--r-sm);
  background: var(--card);
}

.pick__group {
  position: sticky;
  top: 0;
  z-index: 1;
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 8px;
  padding: 7px 10px 5px;
  font-family: var(--display);
  font-size: 10px;
  letter-spacing: 0.12em;
  text-transform: uppercase;
  color: var(--ash);
  background: var(--paper);
  border-bottom: 1px solid var(--rule);
}

.pick__all {
  border: 1px solid var(--rule);
  background: var(--card);
  border-radius: var(--r-sm);
  font: inherit;
  letter-spacing: inherit;
  color: var(--carbon);
  padding: 1px 8px;
  cursor: pointer;
}

.pick__row {
  display: flex;
  align-items: center;
  gap: 10px;
  padding: 6px 10px;
  border-bottom: 1px solid var(--rule);
  font-size: 12.5px;
  cursor: pointer;
}

.pick__row:last-child {
  border-bottom: 0;
}

.pick__row:hover {
  background: var(--paper);
}

.pick__row input {
  flex: none;
}

.pick__search {
  width: 100%;
  border: 1px solid var(--rule);
  border-radius: var(--r-sm);
  padding: 7px 10px;
  font: inherit;
  font-size: 13px;
}

.pick__search:focus {
  outline: none;
  border-color: var(--plasma);
  box-shadow: 0 0 0 3px var(--plasma-soft);
}

/* Two or three choices side by side — new batch, or an old one going back in. */
.seg {
  display: flex;
  flex-wrap: wrap;
  gap: 8px;
}

.seg__opt {
  display: inline-flex;
  align-items: center;
  gap: 7px;
  border: 1px solid var(--rule);
  border-radius: 999px;
  padding: 6px 12px 6px 9px;
  font-size: 13px;
  cursor: pointer;
  background: var(--card);
}

.seg__opt:has(input:checked) {
  border-color: var(--carbon);
  background: var(--carbon);
  color: #fff;
}

.seg__opt:has(input:disabled) {
  opacity: 0.55;
  cursor: not-allowed;
}

/* Chips that filter a report down to one system. */
.chips {
  display: flex;
  flex-wrap: wrap;
  gap: 6px;
}

/* ------------------------------------------------------------ live floor --- */

.livegrid {
  display: grid;
  gap: 14px;
  grid-template-columns: repeat(auto-fill, minmax(300px, 1fr));
}

.livecard {
  background: var(--card);
  border: 1px solid var(--rule);
  border-left: 3px solid var(--rule-strong);
  border-radius: var(--r-lg);
  padding: 14px;
  display: flex;
  flex-direction: column;
  gap: 10px;
  box-shadow: var(--shadow);
}

.livecard.is-live { border-left-color: var(--plasma); }
.livecard.has-issue { border-left-color: var(--ember); }

.livecard__head {
  display: flex;
  justify-content: space-between;
  align-items: center;
  gap: 8px;
}

.livecard__head a { color: inherit; text-decoration: none; }
.livecard__run { font-size: 12.5px; }

/* The two routine slots first, then every photo the day actually earned —
   a second one of the same slot, the system stopping, starting again, or
   something wrong — and the tile that takes one more. */
.livecard__photos {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(118px, 1fr));
  gap: 8px;
}

/* One photo slot: a picture with its label, or the space where one should be. */
.pslot {
  position: relative;
  display: block;
  width: 100%;
  aspect-ratio: 4 / 3;
  border-radius: var(--r-md);
  overflow: hidden;
  background: #23262d;
  border: 1px solid var(--rule);
  padding: 0;
  cursor: pointer;
  font: inherit;
  color: #fff;
  text-align: left;
}

.pslot__img {
  width: 100%;
  height: 100%;
  object-fit: cover;
  display: block;
}

.pslot__tag {
  position: absolute;
  left: 0;
  right: 0;
  bottom: 0;
  padding: 4px 8px;
  font-size: 11px;
  font-weight: 600;
  background: rgb(0 0 0 / 55%);
  color: #fff;
}

.pslot--empty {
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: 4px;
  background: var(--paper);
  color: var(--ash);
  cursor: default;
}

.pslot--empty .pslot__tag {
  position: static;
  background: none;
  color: inherit;
  padding: 0;
}

.pslot__hint { font-size: 12px; font-weight: 700; }

.pslot--missing {
  border-color: var(--ember);
  color: var(--ember);
  background: var(--ember-soft);
}

.pslot--other { border-style: dashed; }

/* Something is wrong with this system: the photo says so before it is opened. */
.pslot--issue { border: 2px solid var(--ember); }
.pslot--issue .pslot__tag { background: var(--ember); }

/* One more photo, whenever there is a reason for one. */
.pslot--add {
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: 4px;
  background: var(--paper);
  color: var(--ash);
  border-style: dashed;
}

.pslot--add:hover { border-color: var(--carbon); color: var(--carbon); }
.pslot__plus svg { width: 22px; height: 22px; }

.pslot--add .pslot__tag {
  position: static;
  background: none;
  color: inherit;
  padding: 0;
  font-size: 11px;
}

.livecard__photoline { margin-top: -2px; }

/* A dialog that is about something going wrong wears it. */
.modal__panel--alert { border-top: 3px solid var(--ember); }

.livecard__reading {
  display: flex;
  flex-wrap: wrap;
  gap: 6px 12px;
  font-size: 12.5px;
  align-items: center;
}

.livecard__actions {
  display: flex;
  flex-wrap: wrap;
  gap: 8px;
  margin-top: auto;
}

.livecard__actions .icon { width: 14px; height: 14px; vertical-align: -2px; margin-right: 4px; }

.dayrow {
  display: grid;
  grid-template-columns: 96px 1fr;
  gap: 10px;
  align-items: start;
  padding: 8px 0;
  border-bottom: 1px solid var(--rule);
}

.dayrow:last-of-type { border-bottom: 0; }
.dayrow__day { font-weight: 600; font-size: 12.5px; padding-top: 4px; }

.dayrow__slots {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(140px, 1fr));
  gap: 8px;
}

@media (max-width: 560px) {
  .dayrow { grid-template-columns: 1fr; }
}

/* ----------------------------------------------------------- batch plans --- */

.planwrap {
  display: grid;
  grid-template-columns: 340px minmax(0, 1fr);
  gap: 16px;
  align-items: start;
}

.planwrap__main { min-width: 0; }
.planframe__wrap { position: relative; }

.planframe {
  width: 100%;
  height: min(78vh, 820px);
  border: 1px solid var(--rule);
  border-radius: var(--r-lg);
  background: #0b0d12;
  display: block;
}

.planframe__loading {
  position: absolute;
  inset: 0;
  display: flex;
  align-items: center;
  justify-content: center;
  color: #fff;
  background: #0b0d12;
  border-radius: var(--r-lg);
  font-size: 13px;
  pointer-events: none;
}

.planimg {
  width: 100%;
  border-radius: var(--r-md);
  border: 1px solid var(--rule);
  background: #0b0d12;
  display: block;
}

.poslist { max-height: 260px; overflow: auto; }

@media (max-width: 900px) {
  .planwrap { grid-template-columns: 1fr; }
  .planframe { height: 70vh; }
}

/* -------------------------------------------------------------- language --- */

.plantpick--lang { margin-left: 4px; }

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

/* ----------------------------------------------------------- paper sheets --- */

.sheet {
  background: var(--card);
  border: 1px solid var(--rule);
  border-radius: var(--r-lg);
  padding: 18px 20px 20px;
  box-shadow: var(--shadow);
}

.sheet__mast {
  display: flex;
  justify-content: space-between;
  align-items: flex-end;
  gap: 14px;
  flex-wrap: wrap;
  padding-bottom: 12px;
  border-bottom: 2px solid var(--carbon);
  margin-bottom: 14px;
}

.sheet__mast b { font-size: 15px; letter-spacing: .04em; display: block; }
.sheet__head { display: flex; gap: 18px; flex-wrap: wrap; font-size: 12px; color: var(--ash); }
.sheet__head b { color: var(--carbon); font-family: var(--mono); }
.sheet__body { display: flex; flex-direction: column; gap: 14px; }
.sheet__cols { display: grid; grid-template-columns: 1fr 1fr; gap: 14px; align-items: start; }
.sheet__cols--tight { gap: 8px; }
.sheet__row { display: flex; justify-content: space-between; align-items: center; gap: 12px; flex-wrap: wrap; }
.sheet__label { font-size: 11px; font-weight: 700; letter-spacing: .08em; color: var(--ash); margin-bottom: 6px; }
.sheet__stage {
  align-self: center;
  border: 1px solid var(--carbon);
  background: var(--paper);
  padding: 3px 18px;
  font-size: 12px;
  font-weight: 700;
  letter-spacing: .1em;
  text-transform: uppercase;
}

.sheet__kv { width: 100%; border-collapse: collapse; font-size: 12.5px; }
.sheet__kv th { text-align: left; font-size: 10.5px; letter-spacing: .06em; color: var(--carbon); background: var(--paper); border: 1px solid var(--rule-strong); padding: 6px 8px; width: 48%; }
.sheet__kv td { border: 1px solid var(--rule-strong); padding: 3px 6px; }
.sheet__kv td input, .sheet__kv td select { width: 100%; border: 0; padding: 4px 2px; font: inherit; background: transparent; }
.sheet__kv td input:focus, .sheet__kv td select:focus { outline: 2px solid var(--plasma); outline-offset: -2px; border-radius: 4px; }
.sheet__check { display: flex; gap: 8px; align-items: center; font-size: 12.5px; }
.sheet__table td input, .sheet__table th input { width: 100%; border: 0; background: transparent; font: inherit; padding: 2px 4px; }
.sheet__table td input:focus { outline: 2px solid var(--plasma); border-radius: 4px; }
.sheet__table th { text-align: left; }
.sheet__caption { text-align: center; font-size: 11.5px; font-weight: 600; letter-spacing: .04em; margin-top: 8px; }
.sheet__signed { margin-top: 12px; padding: 8px 12px; border-radius: var(--r-md); background: var(--jade-soft); color: var(--jade); font-size: 13px; }
.sheet__photos { margin-top: 18px; padding-top: 14px; border-top: 1px solid var(--rule); }

/* The 21-pocket plate: a circle, and pockets placed on it. A pocket sits in
   its grid slot until somebody drags it, after which it sits where the seed
   actually is. Positions are percentages, so the plate scales to any screen. */
.sheet__plates > div { display: flex; flex-direction: column; align-items: center; }
.sheet__platetools {
  display: flex;
  gap: 10px;
  align-items: center;
  flex-wrap: wrap;
  padding: 8px 0;
  border-top: 1px solid var(--rule);
  border-bottom: 1px solid var(--rule);
}

.plate {
  position: relative;
  width: min(100%, 320px);
  aspect-ratio: 1;
  border: 2.5px solid var(--carbon);
  border-radius: 50%;
  background: #fff;
  touch-action: none;
}

.plate.is-moving { cursor: grab; background: repeating-conic-gradient(var(--paper) 0 25%, #fff 0 50%) 50% / 14px 14px; }

.pocket {
  position: absolute;
  transform: translate(-50%, -50%);
  border: 1px solid var(--rule-strong);
  background: #fff;
  display: flex;
  align-items: center;
  justify-content: center;
  padding: 0;
  font: inherit;
}

.pocket.is-moved { border-color: var(--plasma); box-shadow: 0 0 0 1px var(--plasma-soft); }
.plate.is-moving .pocket { cursor: grab; }

.pocket input {
  width: 100%;
  height: 100%;
  border: 0;
  background: transparent;
  text-align: center;
  font: 600 12px var(--mono);
  padding: 0;
  -moz-appearance: textfield;
}

.pocket input::-webkit-outer-spin-button,
.pocket input::-webkit-inner-spin-button { -webkit-appearance: none; margin: 0; }
.pocket input:focus { outline: 2px solid var(--plasma); outline-offset: -2px; }

/* The "⋯" that opens the seed behind the pocket. */
.pocket__more {
  position: absolute;
  right: -1px;
  bottom: -1px;
  border: 0;
  background: transparent;
  color: var(--mist);
  font: 700 11px var(--body);
  line-height: 1;
  padding: 1px 3px;
  cursor: pointer;
}
.pocket__more:hover { color: var(--plasma); }

.pocket--mark { cursor: pointer; overflow: hidden; }
.pocket--mark.is-marked { background: var(--ember-soft); }
.pocket--mark:disabled { cursor: default; }
.pocket__glyph { font: 700 15px var(--mono); color: var(--ember); }
.pocket .sketch { width: 100%; height: 100%; display: block; }

/* The drawing surface: one seed, big, with the pen on it. */
.sketchpad { display: flex; flex-direction: column; gap: 10px; }
.sketchpad__tools { display: flex; gap: 8px; align-items: center; flex-wrap: wrap; }
.sketchpad__kind i { display: inline-block; width: 9px; height: 9px; border-radius: 2px; margin-right: 6px; vertical-align: -1px; }

.sketchpad__area {
  width: min(100%, 340px);
  aspect-ratio: 1;
  align-self: center;
  border: 1px solid var(--rule);
  border-radius: var(--r-md);
  background: repeating-conic-gradient(#fbfcfe 0 25%, #fff 0 50%) 50% / 18px 18px;
  touch-action: none;
}

.sketchpad__svg { width: 100%; height: 100%; display: block; cursor: crosshair; }
.sketchpad__seed { fill: #fff; stroke: var(--carbon); stroke-width: 1.4; }

.sketch { display: block; }

@media (max-width: 720px) {
  .sheet { padding: 14px; }
  .sheet__cols { grid-template-columns: 1fr; }
}

/* ------------------------------------------------------------- hourly log --- */

.loggrid th { position: sticky; top: 0; background: var(--card); z-index: 1; }
.loggrid tr.shift-b td { background: #f6f7fa; }
.loggrid tr.is-now td { background: var(--plasma-soft); }
.loggrid tr.is-future td { color: var(--mist); }
.loggrid td.is-out { color: var(--ember); font-weight: 700; }
.loggrid td.is-nt { color: var(--ash); font-weight: 700; letter-spacing: .06em; }
.loggrid td.is-missed { color: var(--ember); }
.loggrid td.tiny { max-width: 220px; }
.loggrid__foot { display: flex; gap: 16px 28px; flex-wrap: wrap; align-items: center; font-size: 12.5px; border-top: 1px solid var(--rule); }

/* --------------------------------------------------- a seed's condition --- */

.condcard {
  border: 1px solid var(--rule);
  border-radius: var(--r-md);
  padding: 12px 14px;
}

.condcard.is-drawn { border-color: var(--rule-strong); }
.condcard__head { display: flex; justify-content: space-between; align-items: center; gap: 10px; margin-bottom: 10px; }
.condcard__body { display: flex; gap: 14px; align-items: flex-start; flex-wrap: wrap; }
.condcard__body .sketch { border: 1px solid var(--rule); border-radius: var(--r-sm); background: #fff; flex: none; }
.condcard__facts { display: grid; gap: 6px; font-size: 12.5px; min-width: 160px; }
.condcard__facts .fact__k { display: block; }
.condcard__blank {
  width: 132px;
  height: 132px;
  flex: none;
  display: flex;
  align-items: center;
  justify-content: center;
  text-align: center;
  padding: 10px;
  border: 1px dashed var(--rule-strong);
  border-radius: var(--r-sm);
  color: var(--mist);
  font-size: 12px;
}
.condcard__note { width: 100%; }

/* ---------------------------------------------------------- photo wall --- */

/* auto-fill rather than a fixed count: a phone gets two across, a laptop six,
   and neither needs its own rule. */
.pgrid {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(132px, 1fr));
  gap: 10px;
}

.ptile {
  position: relative;
  border: 1px solid var(--rule);
  border-radius: var(--r-md);
  overflow: hidden;
  background: var(--card);
}

.ptile--on {
  border-color: var(--carbon);
  box-shadow: 0 0 0 2px var(--carbon);
}

.ptile__btn {
  display: block;
  width: 100%;
  padding: 0;
  border: 0;
  background: #0b0d12;
  cursor: zoom-in;
}

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

/* The tick sits on the picture, big enough for a thumb on a factory floor
   where the person is probably wearing a glove. */
.ptile__tick {
  position: absolute;
  top: 6px;
  left: 6px;
  z-index: 1;
  display: grid;
  place-items: center;
  width: 30px;
  height: 30px;
  border-radius: 8px;
  background: rgb(11 13 18 / 55%);
  cursor: pointer;
}

.ptile__tick input { width: 17px; height: 17px; accent-color: var(--carbon); cursor: pointer; }

.ptile__cap {
  display: flex;
  flex-wrap: wrap;
  gap: 5px;
  align-items: baseline;
  padding: 6px 8px;
}

/* The bar that appears when something is ticked. Sticky, because the wall is
   long and the buttons are no use at the top of it once you have scrolled. */
.selbar {
  position: sticky;
  /* Below the top bar, which is sticky itself — at 8px this sat on top of the
     photographs it was meant to act on. */
  top: calc(var(--topbar-h) + 8px);
  z-index: 5;
  display: flex;
  flex-wrap: wrap;
  gap: 8px;
  align-items: center;
  padding: 8px 12px;
  border: 1px solid var(--rule-strong);
  border-radius: var(--r-md);
  background: var(--card);
  box-shadow: var(--shadow-sm, 0 2px 8px rgb(11 13 18 / 8%));
}

/* Dimmed, but not transparent. opacity on the bar itself made it see-through,
   and a sticky bar you can read the photographs through is worse than no bar. */
.selbar--idle .selbar__n { color: var(--ash); }
.selbar__n { font-weight: 700; font-size: 13px; margin-right: 2px; }

/* Side by side, and on a phone one under the other — two chamber photographs
   squeezed into 180px each would compare nothing. */
.cmpgrid {
  display: grid;
  grid-template-columns: repeat(var(--cols, 2), minmax(0, 1fr));
  gap: 14px;
}

@media (max-width: 720px) {
  .cmpgrid { grid-template-columns: 1fr; }
}

.cmp { margin: 0; }

.cmp img {
  width: 100%;
  max-height: 52vh;
  object-fit: contain;
  background: #000;
  border-radius: 8px;
}

.cmp figcaption { padding-top: 7px; display: grid; gap: 3px; }

.cmp__m { width: 100%; border-collapse: collapse; margin-top: 5px; }
.cmp__m td { padding: 2px 0; }
.cmp__m td:first-child { color: var(--ash); }
.cmp__m td:nth-child(2) { text-align: right; font-family: var(--mono); }
.cmp__m td:last-child { text-align: right; padding-left: 8px; }

.swatch {
  display: inline-block;
  width: 11px;
  height: 11px;
  border-radius: 3px;
  border: 1px solid var(--rule-strong);
  vertical-align: -1px;
}

/* ------------------------------------------------------------- the camera --- */

.camera { display: flex; flex-direction: column; gap: 10px; }

.camera__stage {
  position: relative;
  width: 100%;
  aspect-ratio: 4 / 3;
  background: #0b0d12;
  border-radius: var(--r-md);
  overflow: hidden;
}

.camera__video { width: 100%; height: 100%; object-fit: cover; display: block; }

.camera__status {
  position: absolute;
  inset: 0;
  display: flex;
  align-items: center;
  justify-content: center;
  text-align: center;
  padding: 20px;
  color: #fff;
  font-size: 13.5px;
  background: rgb(11 13 18 / 82%);
}

/* Where to hold the label. */
/* The alignment frame for photographing a machine screen.
   The shape of the HMI page itself, so lining the screen up inside it is the
   whole of what the operator has to do — and having done it, the app knows
   exactly which part of the photograph is the screen and does not have to go
   looking for it. */
.camera__screenframe {
  position: absolute;
  left: 50%;
  top: 50%;
  width: 88%;
  max-height: 88%;
  aspect-ratio: 1280 / 800;
  transform: translate(-50%, -50%);
  border: 2px solid rgb(255 255 255 / 85%);
  border-radius: 6px;
  box-shadow: 0 0 0 100vmax rgb(11 13 18 / 45%);
  pointer-events: none;
}

/* A number that came off a photograph rather than off somebody's fingers.
   It stays marked until the field is touched, so a person signing off eleven
   readings can see at a glance which ones a machine proposed. */
/* Selector as specific as the one it has to beat: the base rule for these
   boxes is .field input:not([type='checkbox']):not([type='radio']), and a
   plain .field--read input loses to it and quietly does nothing at all. */
.field--read input:not([type='checkbox']):not([type='radio']) {
  border-color: var(--plasma);
  background: var(--plasma-soft);
}

.field--read label::after {
  content: ' • read';
  color: var(--plasma);
  font-weight: 600;
  letter-spacing: 0.02em;
}

/* A number carried forward from the last reading, rather than read off this
   photograph or typed by the person. Amber, because it is the one that most
   deserves a glance at the screen before it is signed off: it was true an hour
   ago and nobody has said it still is. The mark goes the moment it is
   touched. */
.field--carried input:not([type='checkbox']):not([type='radio']) {
  border-color: var(--amber, #b45309);
  background: var(--amber-soft, #fef6e7);
}

.field--carried label::after {
  content: ' • last';
  color: var(--amber, #b45309);
  font-weight: 600;
  letter-spacing: 0.02em;
}

.camera__reticle {
  position: absolute;
  left: 50%;
  top: 50%;
  width: min(62%, 260px);
  aspect-ratio: 1;
  transform: translate(-50%, -50%);
  border: 2px solid rgb(255 255 255 / 85%);
  border-radius: 12px;
  box-shadow: 0 0 0 100vmax rgb(11 13 18 / 35%);
  pointer-events: none;
}

/* Zoom sits on the picture rather than under it: on a phone held one-handed
   the thumb is already at the bottom of the screen, and a control below the
   preview would push the shutter off a short screen. */
/* What the camera is giving, small, out of the way of the picture. Amber when
   it is 1600 or less, which is the phone answering with much less than its
   sensor can do — the thing somebody needs to notice. */
.camera__size {
  position: absolute;
  top: 10px;
  right: 12px;
  padding: 3px 9px;
  border-radius: 999px;
  background: rgb(11 13 18 / 55%);
  color: #fff;
  font-size: 11px;
  font-variant-numeric: tabular-nums;
}

.camera__size--small { color: #ffd479; }

.camera__zoom {
  position: absolute;
  left: 12px;
  right: 12px;
  bottom: 10px;
  display: flex;
  align-items: center;
  gap: 10px;
  padding: 6px 12px;
  border-radius: 999px;
  background: rgb(11 13 18 / 55%);
  backdrop-filter: blur(6px);
}

.camera__zoom input { flex: 1; accent-color: #fff; }

.camera__zoomval {
  color: #fff;
  font-size: 12px;
  font-variant-numeric: tabular-nums;
  min-width: 30px;
}

.topbar__scan { display: inline-flex; align-items: center; gap: 6px; }
.topbar__scan .icon { width: 15px; height: 15px; }

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

/* On a 375 px phone the top bar has to fit the phone. Two full plant names and
   a full person's name do not, and the page was scrolling sideways on every
   screen because of it — so the pickers shrink to what they need to be
   recognised — the name is already out of the way at this width, and the
   face and the ⋯ stay, which is what anyone actually reaches for. */
@media (max-width: 620px) {
  .topbar__tools {
    gap: 8px;
    min-width: 0;
  }

  .topbar__tools .plantpick {
    padding: 4px 6px;
    min-width: 0;
  }

  .topbar__tools .plantpick select {
    max-width: 104px;
    min-width: 0;
    text-overflow: ellipsis;
  }

  .topbar__tools .plantpick--lang select {
    max-width: 76px;
  }

}

/* ------------------------------------------------------- the system, drawn --- */
/* The chamber as it stands on the floor, with the viewport in the middle.
   Only what is really running moves: the plasma when a run is live, the gas
   dashes when gas is flowing, the tower lamp for what the machine's own lamp
   shows. */

.rsvg { display: block; width: 100%; max-width: 300px; height: auto; margin: 0 auto; }

.rsvg__gas { stroke-dasharray: 5 7; opacity: .45; }
.rsvg__water { opacity: .5; }
.rsvg__plasma { opacity: 0; transition: opacity .4s; transform-box: fill-box; transform-origin: 50% 70%; }
.rsvg__bulb { fill: #2c3138; stroke: #1b1f25; stroke-width: .8; }

/* Running: the plasma sits on the plate and breathes, the gas moves, the
   green lamp is lit. */
.rsvg--growing .rsvg__plasma { opacity: 1; animation: rx-breathe 3.2s ease-in-out infinite; }
.rsvg--growing .rsvg__hot { animation: rx-hot 2.1s ease-in-out infinite; }
.rsvg--growing .rsvg__gas { opacity: 1; animation: rx-flow 1.6s linear infinite; }
.rsvg--growing .rsvg__gas--b { animation-delay: .35s; }
.rsvg--growing .rsvg__bulb--green { fill: #35d07f; filter: drop-shadow(0 0 4px #35d07f); }

/* Out of the system and being worked on: warm, but nothing is burning. */
.rsvg--out .rsvg__bulb--amber { fill: #f2a33c; filter: drop-shadow(0 0 3px #f2a33c); }

/* Repaired and waiting to be checked: the amber lamp blinks, as it does on
   the machine. */
.rsvg--ready .rsvg__bulb--amber { fill: #f2a33c; animation: rx-blink 1.6s steps(1, end) infinite; }

/* Out of service. */
.rsvg--down .rsvg__bulb--red { fill: #e5484d; filter: drop-shadow(0 0 4px #e5484d); }
.rsvg--down .rsvg__body, .rsvg--retired .rsvg__body { filter: saturate(.5) brightness(.94); }
.rsvg--retired { opacity: .55; }

@keyframes rx-breathe {
  0%, 100% { opacity: .82; transform: scale(.97); }
  50% { opacity: 1; transform: scale(1.03); }
}
@keyframes rx-hot {
  0%, 100% { opacity: .72; }
  50% { opacity: 1; }
}
@keyframes rx-flow { to { stroke-dashoffset: -24; } }
@keyframes rx-blink { 50% { fill: #4a3a1c; } }

/* The same lamp as a word, so colour is never the only signal. */
.rlamp { display: inline-flex; align-items: center; gap: 7px; font-size: 12px; font-weight: 600; }
.rlamp__stack { display: inline-flex; flex-direction: column; gap: 2px; }
.rlamp__stack i { width: 7px; height: 7px; border-radius: 50%; background: #cdd3dc; display: block; }
.rlamp--red .rlamp__stack i:nth-child(1) { background: #e5484d; box-shadow: 0 0 5px #e5484d; }
.rlamp--amber .rlamp__stack i:nth-child(2) { background: #f2a33c; box-shadow: 0 0 5px #f2a33c; }
.rlamp--green .rlamp__stack i:nth-child(3) { background: #0b8f66; box-shadow: 0 0 5px #35d07f; }
.rlamp--red .rlamp__text { color: var(--ember); }
.rlamp--amber .rlamp__text { color: var(--amber); }
.rlamp--green .rlamp__text { color: var(--jade); }
.rlamp--off .rlamp__text { color: var(--mist); }

/* A floor screen is often left open all day on a tablet; nobody needs the
   plasma pulsing at them if they have asked for less movement. */
@media (prefers-reduced-motion: reduce) {
  .rsvg--growing .rsvg__plasma,
  .rsvg--growing .rsvg__hot,
  .rsvg--growing .rsvg__gas,
  .rsvg--ready .rsvg__bulb--amber { animation: none; }
}

/* The drawing sits between the system's name and its carats, so the card is
   read top to bottom: which system, what it is doing, how much is in it. */
.reactor__view {
  margin: 10px -4px 8px;
  border-radius: var(--r-sm);
  background: linear-gradient(180deg, #f4f6fa 0%, #e9edf3 100%);
  padding: 6px 4px 2px;
}

.reactor.is-live .reactor__view { background: linear-gradient(180deg, #f6f2ff 0%, #ece4fb 100%); }
.reactor__lamp { margin-bottom: 8px; }
.reactor--idle .reactor__view { filter: saturate(.85); }

/* ---------------------------------------------------------- the manual --- */
/* The book: contents on the left where there is room, chapters on the right.
   It is meant to be read on a phone in one hand and printed for the wall. */

.book {
  display: grid;
  grid-template-columns: minmax(0, 260px) minmax(0, 1fr);
  gap: 28px;
  align-items: start;
}

@media (max-width: 900px) {
  .book { grid-template-columns: 1fr; gap: 16px; }
}

.book__toc {
  position: sticky;
  top: 76px;
  background: var(--card);
  border: 1px solid var(--rule);
  border-radius: var(--r-md);
  padding: 14px 16px 16px;
}

@media (max-width: 900px) {
  .book__toc { position: static; }
}

.book__toc ol { margin: 8px 0 12px; padding-left: 22px; }
.book__toc li { margin: 5px 0; font-size: 13px; }
.book__toc a { color: var(--carbon); text-decoration: none; }
.book__toc a:hover { text-decoration: underline; }

.book__body { display: flex; flex-direction: column; gap: 26px; }

.book__chapter {
  background: var(--card);
  border: 1px solid var(--rule);
  border-radius: var(--r-md);
  padding: 20px 22px 16px;
  scroll-margin-top: 76px;
}

.book__chhead { display: flex; align-items: baseline; gap: 12px; }

.book__num {
  font-size: 26px;
  font-weight: 800;
  color: var(--mist);
  line-height: 1;
  min-width: 34px;
}

.book__h2 { font-size: 20px; margin: 0; }
.book__h3 { font-size: 15px; margin: 20px 0 6px; }
.book__lead { font-size: 14.5px; color: var(--ash); margin: 6px 0 14px 46px; }
.book__p { font-size: 14px; line-height: 1.65; margin: 10px 0; }
.book__list, .book__steps { font-size: 14px; line-height: 1.6; padding-left: 22px; }
.book__list li, .book__steps li { margin: 6px 0; }
.book__steps li { padding-left: 4px; }

.book__note, .book__warn {
  font-size: 13.5px;
  line-height: 1.55;
  border-radius: var(--r-sm);
  padding: 10px 14px;
  margin: 14px 0;
  border-left: 3px solid var(--rule);
  background: var(--paper);
}

.book__warn { border-left-color: var(--ember); background: var(--ember-soft); }
.book__table { margin: 12px 0; font-size: 13.5px; }

.book__top {
  display: inline-block;
  margin-top: 14px;
  font-size: 12px;
  color: var(--ash);
}

.book__end { margin: 0; }

/* ---- the pictures in the book ---- */

.mfig { margin: 18px 0; }
.mfig__svg { display: block; width: 100%; height: auto; }
.mfig__cap { font-size: 12.5px; color: var(--ash); margin-top: 8px; line-height: 1.5; }

.mf-t { font-size: 13px; fill: var(--carbon); font-weight: 600; }
.mf-s { font-size: 11.5px; fill: var(--ash); }
.mf-h { font-size: 16px; fill: var(--carbon); font-weight: 800; }
.mf-on { fill: var(--carbon); }
.mf-eyebrow { font-size: 11px; fill: var(--mist); font-weight: 700; letter-spacing: .08em; }
.mf-box { fill: var(--card); stroke: var(--rule); stroke-width: 1.2; }
.mf-box--live { fill: var(--plasma-soft); stroke: var(--plasma); }
.mf-box--down { fill: var(--ember-soft); stroke: var(--ember); }
.mf-box--ready { fill: var(--amber-soft); stroke: var(--amber); }
.mf-box--ok { fill: var(--jade-soft); stroke: var(--jade); }
.mf-raise { filter: drop-shadow(0 2px 6px rgb(0 0 0 / 10%)); }
.mf-panel { fill: var(--paper); stroke: var(--rule); stroke-width: 1.2; }
.mf-none { fill: none; }
.mf-zebra { fill: var(--paper); }
.mf-pill { fill: var(--card); stroke: var(--rule); stroke-width: 1; }
.mf-pill--on { fill: var(--plasma-soft); stroke: var(--plasma); }
.mf-input { fill: var(--paper); stroke: var(--rule); stroke-width: 1; }
.mf-btn { fill: var(--carbon); }
.mf-btnt { font-size: 13px; fill: var(--card); font-weight: 700; }
.mf-badge { fill: var(--ember); }
.mf-badget { font-size: 12px; fill: #fff; font-weight: 800; }
.mf-arrow { stroke: var(--ash); stroke-width: 1.6; fill: none; }
.mf-headfill { fill: var(--ash); }
.mf-yes { fill: var(--jade-soft); stroke: var(--jade); stroke-width: 1; }
.mf-yest { font-size: 11px; fill: var(--jade); font-weight: 800; }
.mf-moly { fill: #dfe3ea; stroke: #b6bcc7; stroke-width: 1.5; }
.mf-molyin { fill: #eef1f5; stroke: #c8cdd6; stroke-width: 1; }
.mf-pocket { fill: var(--card); stroke: var(--rule); stroke-width: 1.2; }
.mf-pocket--on { fill: var(--plasma-soft); stroke: var(--plasma); stroke-width: 2; }
.mf-seed { fill: var(--paper); stroke: var(--ash); stroke-width: 1.5; }
.mf-incl { fill: var(--carbon); }
.mf-crack { stroke: var(--ember); stroke-width: 2; fill: none; stroke-linecap: round; }
.mf-cut { fill: var(--card); stroke: var(--ash); stroke-width: 1.2; }
.mf-paper { fill: #fffdf6; stroke: #d8d2c2; stroke-width: 1.2; }
.mf-ruled { stroke: #e3ded0; stroke-width: 1.4; }
.mf-cell { fill: var(--card); stroke: var(--rule); stroke-width: 1; }
.mf-cell--bad { fill: var(--ember-soft); stroke: var(--ember); }
.mf-cell--nt { fill: var(--amber-soft); stroke: var(--amber); }
.mf-cell--due { fill: var(--paper); stroke-dasharray: 3 3; }
.mf-qr { fill: #fff; stroke: var(--carbon); stroke-width: 1.5; }
.mf-qrmark { fill: var(--carbon); }
.mf-phone { fill: var(--carbon); }
.mf-phonescreen { fill: var(--card); }
.mf-reticle { fill: none; stroke: var(--plasma); stroke-width: 2.5; stroke-dasharray: 14 10; }

/* A few figures are laid out as real HTML, so they show the actual thing —
   the reactor drawing, the live-floor card — rather than a picture of it. */

.mfig__row { display: grid; grid-template-columns: repeat(3, 1fr); gap: 10px; margin-bottom: 12px; }
.mfig__stat { background: var(--paper); border: 1px solid var(--rule); border-radius: var(--r-sm); padding: 10px 12px; }
.mfig__statlabel { font-size: 10.5px; letter-spacing: .08em; color: var(--mist); font-weight: 700; }
.mfig__statvalue { font-size: 20px; font-weight: 800; }
.mfig__card { background: var(--card); border: 1px solid var(--rule); border-radius: var(--r-md); padding: 12px 14px; }
.mfig__cardhead { display: flex; align-items: center; gap: 8px; font-size: 14px; margin-bottom: 8px; }
.mfig__cardfoot { font-size: 12.5px; color: var(--ash); margin-top: 8px; display: flex; align-items: center; gap: 6px; flex-wrap: wrap; }
.mfig__badge { margin-left: auto; font-size: 11px; font-weight: 700; color: var(--plasma); background: var(--plasma-soft); border-radius: 999px; padding: 2px 10px; }
.mfig__dot { width: 9px; height: 9px; border-radius: 50%; background: var(--mist); display: inline-block; }
.mfig__dot--on { background: var(--jade); box-shadow: 0 0 6px var(--jade); }
.mfig__states { display: grid; grid-template-columns: repeat(auto-fit, minmax(130px, 1fr)); gap: 12px; }
.mfig__state { margin: 0; text-align: center; }
.mfig__state figcaption { font-size: 11.5px; color: var(--ash); margin-top: 4px; }
.mfig__slots { display: grid; grid-template-columns: repeat(auto-fill, minmax(112px, 1fr)); gap: 8px; }

.mfig__slot {
  aspect-ratio: 4 / 3;
  border-radius: var(--r-sm);
  border: 1px solid var(--rule);
  background: linear-gradient(135deg, #3a3f47, #23262d);
  position: relative;
  overflow: hidden;
}

.mfig__slot--bad { border: 2px solid var(--ember); }
.mfig__slot--add { background: var(--paper); border-style: dashed; }
.mfig__slottag { position: absolute; left: 0; right: 0; bottom: 0; padding: 3px 6px; font-size: 10.5px; font-weight: 600; background: rgb(0 0 0 / 55%); color: #fff; }
.mfig__slot--add .mfig__slottag { position: static; background: none; color: var(--ash); display: block; padding: 40% 6px 0; text-align: center; }
.mfig__slot--bad .mfig__slottag { background: var(--ember); }

/* Printed, the book is a booklet: no rail, no contents, no shadows, and a
   chapter never starts halfway down a page it cannot finish. */
@media print {
  .book { display: block; }
  .book__toc, .page__actions, .book__top { display: none; }
  .book__chapter { border: 0; padding: 0 0 18px; break-inside: avoid-page; page-break-inside: avoid; }
  .book__h2 { break-after: avoid-page; }
  .mfig { break-inside: avoid-page; page-break-inside: avoid; }
  .mfig__svg { max-height: 320px; }
}

/* ------------------------------------------------- the plate, drawn ------ */
/* The top view of the moly, drawn from the plan's own seed positions rather
   than from a saved picture — so it cannot fall behind the layout, and a gap
   left in the middle stays a gap. Everything inside is millimetres, so the
   stroke widths come from the plate size (`--hair`). */

.pplate {
  display: block;
  width: 100%;
  max-width: 520px;
  height: auto;
  margin: 0 auto;
}

.pplate__outer { fill: #c9ced7; stroke: #aab1bd; stroke-width: var(--hair); }
.pplate__inner { fill: #9aa2ae; stroke: #868f9c; stroke-width: var(--hair); }
.pplate__axis { stroke: #ffffff; stroke-opacity: .45; stroke-width: var(--hair); stroke-dasharray: 2 2; }
.pplate__seed { fill: #f4f1ff; stroke: var(--plasma); stroke-width: var(--hair); }
.pplate__no { fill: var(--plasma-ink); font-family: var(--mono); font-weight: 600; }

.pplatecap {
  font-family: var(--mono);
  font-size: 11.5px;
  color: var(--ash);
  text-align: center;
  margin-top: 10px;
}

/* A plain-text button that reads as a link — used where a second full-width
   button would shout louder than the thing it does deserves. */
.linkish {
  background: none;
  border: 0;
  padding: 4px;
  font: inherit;
  font-size: 13px;
  color: var(--ash);
  text-decoration: underline;
  cursor: pointer;
}

.linkish:hover { color: var(--carbon); }

/* ----------------------------------------------------- the day, reviewed --- */
/* One card per system, and the reasons under it. A flagged card is bordered,
   not shouted at: the reasons are the message, the colour only finds them. */

.daycard { margin: 0; }
.daycard--flag { border-color: var(--ember); }
.daycard--flag .card__head { background: var(--ember-soft); }

.daycard__why {
  margin: 12px 0 0;
  padding-left: 18px;
  font-size: 13px;
  line-height: 1.7;
  color: var(--ember);
}

/* What the picture measured, beside what this system usually measures. */
.lookrow {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
  gap: 10px;
  margin-top: 12px;
}

.look {
  border: 1px solid var(--rule);
  border-radius: var(--r-sm);
  padding: 8px 10px;
  background: var(--paper);
}

.look--off { border-color: var(--ember); background: var(--ember-soft); }

.look__bar {
  display: flex;
  align-items: center;
  gap: 10px;
  font-size: 12.5px;
  margin: 4px 0 2px;
}

.look__swatch {
  width: 16px;
  height: 16px;
  border-radius: 4px;
  border: 1px solid var(--rule-strong);
  display: inline-block;
  flex: none;
}

.page__actions input[type='date'] {
  font: inherit;
  padding: 7px 10px;
  border: 1px solid var(--rule);
  border-radius: var(--r-sm);
  background: var(--card);
  color: var(--carbon);
}

/* --------------------------------------------------- the work at a system --
   What somebody who has just scanned the door label came here to do. One row
   of buttons, wrapping on a phone, with the whole card marked so it reads as
   an instruction rather than one more panel. */
.card--work {
  border-color: var(--plasma-soft);
  box-shadow: 0 1px 0 var(--plasma-soft);
}
.worknow {
  display: flex;
  flex-wrap: wrap;
  gap: 10px;
}
.worknow .btn {
  flex: 0 1 auto;
}
@media (max-width: 560px) {
  .worknow .btn {
    flex: 1 1 46%;
    justify-content: center;
  }
}

/* ------------------------------------------------------------------ faces --
   A person, round, at whatever size the screen asks for. The letters version
   is not a placeholder to be replaced later — it is what most people will have
   for a while, so it has to look deliberate. */
.face {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  flex: none;
  border-radius: 999px;
  object-fit: cover;
  background: var(--paper);
  border: 1px solid var(--rule);
  vertical-align: middle;
}
.face--letters {
  color: #fff;
  border-color: transparent;
  font-weight: 600;
  letter-spacing: 0.02em;
  line-height: 1;
}

/* ------------------------------------------------------------------- chat --
   Rooms beside messages on a desk; one at a time on a phone, where the room
   list slides away as soon as a room is opened. */
.chat {
  display: grid;
  grid-template-columns: 300px 1fr;
  gap: 14px;
  height: calc(100vh - 132px);
  min-height: 420px;
}
.chat__rooms,
.chat__pane {
  display: flex;
  flex-direction: column;
  min-height: 0;
  background: var(--card);
  border: 1px solid var(--rule);
  border-radius: 12px;
  overflow: hidden;
}
.chat__head {
  display: flex;
  align-items: center;
  gap: 10px;
  padding: 12px 14px;
  border-bottom: 1px solid var(--rule);
  background: var(--paper);
}
.chat__head strong {
  font-size: 15px;
}
.chat__head .btn {
  margin-left: auto;
}
.chat__title {
  display: flex;
  flex-direction: column;
  align-items: flex-start;
  gap: 2px;
  background: none;
  border: 0;
  padding: 0;
  cursor: pointer;
  text-align: left;
  color: inherit;
  font: inherit;
}
.chat__back {
  display: none;
}
.chat__list {
  overflow-y: auto;
  flex: 1;
}
.roomrow {
  display: flex;
  align-items: center;
  gap: 10px;
  width: 100%;
  padding: 10px 12px;
  border: 0;
  border-bottom: 1px solid var(--rule);
  background: none;
  cursor: pointer;
  text-align: left;
  font: inherit;
  color: inherit;
}
.roomrow:hover {
  background: var(--paper);
}
.roomrow.is-on {
  background: var(--plasma-soft);
}
.roomrow__body {
  flex: 1;
  min-width: 0;
}
.roomrow__top {
  display: flex;
  justify-content: space-between;
  gap: 8px;
  align-items: baseline;
}
.roomrow__last {
  display: block;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}
.roomrow__count {
  flex: none;
  min-width: 20px;
  padding: 1px 6px;
  border-radius: 999px;
  background: var(--plasma);
  color: #fff;
  font-size: 11px;
  font-weight: 600;
  text-align: center;
}
.chat__msgs {
  flex: 1;
  overflow-y: auto;
  padding: 14px;
  display: flex;
  flex-direction: column;
  gap: 8px;
  background: var(--bg);
}
.msgday,
.msgnote {
  align-self: center;
  padding: 3px 10px;
  border-radius: 999px;
  background: var(--paper);
  border: 1px solid var(--rule);
  color: var(--ash);
}
.msg {
  display: flex;
  gap: 8px;
  align-items: flex-end;
  max-width: min(680px, 88%);
}
.msg--mine {
  align-self: flex-end;
  flex-direction: row-reverse;
}
/* No face on your own messages, so they sit against the edge. */
.msg--mine .msg__face {
  width: 0;
}
.msg__face {
  width: 32px;
  flex: none;
}
.msg__bub {
  background: var(--card);
  border: 1px solid var(--rule);
  border-radius: 12px;
  padding: 7px 10px;
  min-width: 0;
}
.msg--mine .msg__bub {
  background: var(--plasma-soft);
  border-color: #dfcbfb;
}
.msg__who {
  font-weight: 600;
  color: var(--plasma-ink);
  margin-bottom: 2px;
}
.msg__text {
  white-space: pre-wrap;
  word-break: break-word;
}
.msg__img {
  display: block;
  max-width: 240px;
  border-radius: 8px;
  margin: 2px 0 4px;
}
.msg__link {
  display: inline-block;
  margin-top: 4px;
  font-size: 12px;
}
.msg__foot {
  display: flex;
  gap: 8px;
  justify-content: flex-end;
  color: var(--ash);
  margin-top: 2px;
}
.linkbtn {
  background: none;
  border: 0;
  padding: 0;
  color: var(--ash);
  text-decoration: underline;
  cursor: pointer;
  font: inherit;
  font-size: 11px;
}
.chat__say {
  display: flex;
  gap: 8px;
  align-items: flex-end;
  padding: 10px 12px;
  border-top: 1px solid var(--rule);
  background: var(--paper);
}
.chat__say textarea {
  flex: 1;
  resize: none;
  max-height: 140px;
  padding: 8px 10px;
  border: 1px solid var(--rule);
  border-radius: 10px;
  font: inherit;
  background: var(--card);
  color: inherit;
}
.picklist {
  max-height: 260px;
  overflow-y: auto;
  border: 1px solid var(--rule);
  border-radius: 10px;
}
.pickrow {
  display: flex;
  align-items: center;
  gap: 10px;
  padding: 7px 10px;
  border-bottom: 1px solid var(--rule);
  cursor: pointer;
}
.pickrow:last-child {
  border-bottom: 0;
}

@media (max-width: 760px) {
  .chat {
    grid-template-columns: 1fr;
    height: calc(100vh - 118px);
  }
  /* One at a time: the list until a room is opened, the room after that. */
  .chat .chat__pane {
    display: none;
  }
  .chat--open .chat__rooms {
    display: none;
  }
  .chat--open .chat__pane {
    display: flex;
  }
  .chat--open .chat__back {
    display: inline-flex;
  }
  .msg {
    max-width: 94%;
  }
}

/* ------------------------------------------------- seed stock on the board --
   The seed-stock column is the one place people look for "how many seeds have
   we got", so the answer is in the column itself and not only two screens
   away. */
.board__stock {
  display: flex;
  align-items: baseline;
  gap: 6px;
  flex-wrap: wrap;
  margin: 0 0 8px;
  padding: 8px 10px;
  border: 1px solid var(--rule);
  border-radius: 8px;
  background: var(--paper);
  color: inherit;
  text-decoration: none;
}
.board__stock strong {
  font-size: 18px;
}
.board__stock:hover {
  border-color: var(--plasma);
}
.bcard__seeds {
  /* Right after the batch number, not pushed to the far edge: on a phone the
     card is narrower than the column and the far edge is off screen. */
  padding: 1px 7px;
  border-radius: 999px;
  background: var(--plasma-soft);
  color: var(--plasma-ink);
  white-space: nowrap;
}
.bcard__seeds--none {
  background: var(--paper);
  color: var(--ash);
}

/* ----------------------------------------------- a system that is stopped --
   The same clock as the growing one, in the colour that means something is
   wrong — so a glance at the floor tells running from stopped without
   reading a word. */
.runclock--down .runclock__t {
  color: var(--ember);
}
.runclock__since {
  font-size: 11px;
  color: var(--ash);
  margin-left: 6px;
}
