/* gid-panel — one theme, dark, no switcher.
   One person looks at this panel, from a phone, most often at night. Two themes
   meant two sets of colours to validate and maintain; one is left.

   THE COLOURS ARE COMPUTED, NOT PICKED BY EYE (dataviz skill, validate_palette.js):

   - The bot states (farming/stopped/delivering/alarm) stay distinguishable under
     colour blindness: the worst pair alarm↔stopped is ΔE 8.9 (deutan), and the
     critical pair farming↔alarm ΔE 9.9 — which is why the green is lighter
     (#4fdc9b) than in the design (#35c47a): with #35c47a that pair gave ΔE 2.9,
     meaning a deuteranope saw "farming" and "no contact" as the same. On top of
     that every state carries a WORD, so colour is not the only carrier anyway.
   - Every text colour has contrast >= 4.5:1 against each of the three backgrounds
     (#14161a, #1c1f26, #232733). Hence --tekst-slaby is lighter than
     in the design: #6c7481 gave 3.16:1 on a card, which is too little for
     11-pixel captions.
   - There are no charts here - History shows plain numbers. Should they ever come
     back, a mark on a dark background wants to be darker than the accent: #8f6bf5
     fits inside the lightness band for marks, #9d7bff (the button accent) does not.
   - The item quality colours are a convention from the game and stay as in the design.
     As a categorical set they do NOT stay distinguishable under colour blindness
     (unique↔crafted ΔE 3.4 protan) - beige and orange are close to each other by nature.
     That is why the quality ALWAYS stands as a word in the label next to the name,
     and the colour is decoration. Each of them passes contrast against the background (>= 5.7:1).

   Typeface: system. The design used Manrope and IBM Plex Mono from Google Fonts,
   but the panel has to run without the internet (and without a build step that would
   pull them in), and 24 woff2 files in the repo is not a price worth one typeface.
   Numbers get monospace and tabular-nums - that is what the design was really about. */

:root {
  --tlo: #14161a;
  --tlo-karta: #1c1f26;
  --tlo-karta-wyzej: #232733;
  --tlo-wglebienie: #101216;
  --obrys: #2a2f3a;
  --obrys-mocny: #3a4150;

  --tekst: #e9ebef;
  --tekst-drugorzedny: #a8b0bd;
  --tekst-slaby: #8b94a3;

  --akcent: #9d7bff;
  --akcent-tlo: rgba(157, 123, 255, .14);
  --akcent-obrys: rgba(157, 123, 255, .45);
  --akcent-jasny: #cbb6ff;
  --akcent-tekst: #141018;

  --stan-dziala: #4fdc9b;
  --stan-stoi: #e8a33d;
  --stan-zrzuca: #4bb8d8;
  --stan-offline: #7d8ba1;

  --alarm: #ff5d6c;
  --alarm-tlo: rgba(255, 93, 108, .12);
  --alarm-obrys: rgba(255, 93, 108, .42);
  --alarm-tekst: #ffd0d5;

  --jakosc-magiczny: #7aa2ff;
  --jakosc-zestaw: #3fc76b;
  --jakosc-rzadki: #e6d64f;
  --jakosc-unikat: #cbb27c;
  --jakosc-wykuty: #e58a3c;

  --mono: ui-monospace, "Cascadia Mono", Consolas, "DejaVu Sans Mono", monospace;
  --promien: 18px;
  --promien-maly: 12px;
}

* { box-sizing: border-box; }

body {
  margin: 0;
  background: var(--tlo);
  color: var(--tekst);
  font: 16px/1.5 -apple-system, "Segoe UI", system-ui, sans-serif;
  -webkit-font-smoothing: antialiased;
  /* Room for the view bar, which sits at the bottom permanently. */
  padding-bottom: calc(96px + env(safe-area-inset-bottom));
}

main { max-width: 76rem; margin: 0 auto; padding: 0 .9rem; }

h1, h2, h3 { margin: 0; letter-spacing: -.02em; }
p { margin: 0; }

.nums { font-family: var(--mono); font-variant-numeric: tabular-nums; }
.muted { color: var(--tekst-drugorzedny); }
.note { color: var(--tekst-slaby); font-size: .82rem; margin: .8rem 0 0; }
code {
  font-family: var(--mono);
  font-size: .9em;
  background: var(--tlo-wglebienie);
  padding: .1em .35em;
  border-radius: 4px;
}
a { color: var(--akcent); }

/* A caption visible only to screen readers - the select has a label, but on
   screen its own first option does the talking ("all characters"). */
.sr-only {
  position: absolute; width: 1px; height: 1px;
  overflow: hidden; clip: rect(0 0 0 0); white-space: nowrap;
}

/* --- screen header ------------------------------------------------------- */

.screen-head {
  display: flex; align-items: baseline; justify-content: space-between;
  gap: .75rem; padding: .9rem .15rem .75rem;
}
.screen-head h1 { font-size: 1.6rem; font-weight: 800; }
.screen-count { font-size: .78rem; color: var(--tekst-slaby); }

.sub {
  font-size: 1.05rem; font-weight: 800;
  margin: 1.6rem .15rem .6rem;
}

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

.tabbar {
  position: fixed; left: 0; right: 0; bottom: 0; z-index: 10;
  display: flex; align-items: center; gap: .35rem;
  /* The bar's content keeps the same width as the page content, but the background
     runs across the whole screen - otherwise on a desktop the bar looks like a little island. */
  padding: .5rem max(.75rem, (100% - 76rem) / 2) calc(.6rem + env(safe-area-inset-bottom));
  background: rgba(20, 22, 26, .94);
  backdrop-filter: blur(12px);
  border-top: 1px solid var(--obrys);
}
/* Five views have to fit into a single row on 360 px as well, and "Settings"
   and "On characters" are long words. Hence min-width: 0 (without it a flex item
   will not shrink below its own content and the bar runs off the screen) and type
   size that depends on width - instead of a breakpoint where the layout jumps. */
.tabbar .tab {
  flex: 1; min-width: 0; min-height: 52px;
  display: flex; align-items: center; justify-content: center;
  padding: 0 .2rem;
  border-radius: 14px;
  color: var(--tekst-drugorzedny);
  text-decoration: none;
  font-size: clamp(.72rem, 2.4vw, .95rem); font-weight: 700;
  white-space: nowrap; overflow: hidden;
}
.tabbar .tab:hover { background: var(--tlo-karta); color: var(--tekst); }
.tabbar .tab[aria-current="page"] { background: var(--akcent); color: var(--akcent-tekst); }

/* Freshness of the data from the panel. This is something different from a
   machine being offline: here it is about the browser's connection to the panel. */
.conn {
  flex: none; display: flex; align-items: center; gap: .4rem;
  height: 52px; padding: 0 .5rem;
  font-family: var(--mono); font-size: .68rem;
  color: var(--tekst-slaby);
}
.conn-dot { width: 7px; height: 7px; border-radius: 50%; background: var(--stan-offline); }
.conn[data-state="ok"] .conn-dot { background: var(--stan-dziala); }
.conn[data-state="error"] { color: var(--alarm); }
.conn[data-state="error"] .conn-dot { background: var(--alarm); animation: puls 1s infinite; }
@keyframes puls { 0%, 100% { opacity: 1; } 50% { opacity: .3; } }

/* On a narrow screen every pixel counts: only the dot is left. */
@media (max-width: 26rem) {
  .conn-text { display: none; }
}

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

.btn {
  font: inherit; font-size: .92rem; font-weight: 600;
  /* 46 px is the threshold below which a button is hard to hit with a thumb. */
  min-height: 46px; padding: 0 .9rem;
  border: 1px solid var(--obrys-mocny);
  border-radius: var(--promien-maly);
  background: var(--tlo-karta-wyzej);
  color: var(--tekst);
  cursor: pointer;
}
.btn:hover:not(:disabled) { background: #2b3140; }
.btn:disabled { opacity: .4; cursor: default; }
.btn-primary {
  background: var(--akcent-tlo);
  border-color: var(--akcent-obrys);
  color: var(--akcent-jasny);
}
.btn-primary:hover:not(:disabled) { background: rgba(157, 123, 255, .24); }
.btn-block { display: block; width: 100%; min-height: 52px; margin-top: .9rem; }
/* A class with display beats the hidden attribute from the browser stylesheet -
   without this the button stays on the screen despite hidden being set. */
.btn[hidden] { display: none; }
.btn-small { min-height: 38px; padding: 0 .7rem; font-size: .82rem; }

/* --- filters ------------------------------------------------------------- */

.filters { display: flex; flex-wrap: wrap; gap: .5rem; margin: 0 0 1rem; }
.filters select {
  flex: 1 1 12rem;
  font: inherit; font-size: .92rem; font-weight: 600;
  min-height: 46px; padding: 0 .8rem;
  background: var(--tlo-karta);
  border: 1px solid var(--obrys);
  border-radius: var(--promien-maly);
  color: var(--tekst);
}

/* Time range: one switch, not four separate buttons. */
.range {
  flex: 1 1 16rem;
  display: flex; gap: .3rem;
  background: var(--tlo-wglebienie);
  border: 1px solid var(--obrys);
  border-radius: 14px;
  padding: 4px;
}
.range .btn {
  flex: 1; min-height: 38px; padding: 0 .4rem;
  font-family: var(--mono); font-size: .85rem;
  background: none; border: 0; color: var(--tekst-drugorzedny);
}
.range .btn:hover:not(:disabled) { background: var(--tlo-karta); }
.range .btn[aria-pressed="true"] { background: var(--akcent); color: var(--akcent-tekst); }

/* --- alarms -------------------------------------------------------------- */

.alarms { display: flex; flex-direction: column; gap: .6rem; margin-top: 1.1rem; }
.alarm {
  display: flex; gap: .75rem; align-items: flex-start;
  background: var(--alarm-tlo);
  border: 1px solid var(--alarm-obrys);
  border-radius: 16px;
  padding: .85rem 1rem;
}
.alarm-dot {
  width: 8px; height: 8px; border-radius: 50%;
  background: var(--alarm); margin-top: .45rem; flex: none;
}
.alarm-body { flex: 1; min-width: 0; }
/* An alarm hides with one click and never comes back. */
.alarm-hide {
  flex: none;
  background: rgba(255, 93, 108, .16);
  border-color: var(--alarm-obrys);
  color: var(--alarm-tekst);
}
.alarm-hide:hover:not(:disabled) { background: rgba(255, 93, 108, .28); }
.alarm-what { font-size: 1.05rem; font-weight: 800; color: var(--alarm-tekst); }
.alarm-count { font-weight: 600; opacity: .75; }
.alarm-where {
  margin-top: .15rem;
  font-family: var(--mono); font-size: .78rem; color: var(--alarm);
}

/* --- machines and bots --------------------------------------------------- */

.machines { display: grid; gap: .9rem; grid-template-columns: 1fr; }

/* A footnote under the machines, not a tile: a machine that has never spoken up
   is to be noted, not to compete visually with the ones that are working. */
.machines-hidden {
  font-size: .78rem; color: var(--tekst-drugorzedny); text-align: center;
}

.machine {
  background: var(--tlo-karta);
  border: 1px solid var(--obrys);
  border-radius: 20px;
  overflow: hidden;
}
.machine-head { display: flex; align-items: center; gap: .6rem; padding: 1rem 1.1rem .75rem; }
.machine-dot { width: 9px; height: 9px; border-radius: 50%; flex: none; background: var(--stan-offline); }
.machine-name { flex: 1; min-width: 0; }
.machine-name h2 { font-size: 1.1rem; font-weight: 800; }
.machine-seen {
  margin-top: .1rem;
  font-family: var(--mono); font-size: .72rem; color: var(--tekst-slaby);
}
.machine-count {
  flex: none;
  font-family: var(--mono); font-size: 1.15rem; font-weight: 600;
  font-variant-numeric: tabular-nums; color: var(--stan-offline);
}
.machine[data-status="online"] .machine-dot { background: var(--stan-dziala); }
.machine[data-status="online"] .machine-count { color: var(--stan-dziala); }
.machine[data-status="online"][data-all-running="false"] .machine-count { color: var(--stan-stoi); }
.machine[data-status="offline"] .machine-dot { background: var(--alarm); }
.machine[data-status="offline"] .machine-seen { color: var(--alarm); }
.machine[data-status="disabled"] { opacity: .65; }
.machine-warn {
  padding: 0 1.1rem .6rem;
  font-size: .78rem; color: var(--stan-stoi); font-weight: 600;
}

.bot { border-top: 1px solid var(--obrys); padding: 1rem 1.1rem 1.1rem; }
.bot-head { display: flex; align-items: baseline; gap: .6rem; flex-wrap: wrap; }
.bot-name { font-size: 1.3rem; font-weight: 800; letter-spacing: -.02em; }
.bot-state { display: inline-flex; align-items: center; gap: .4rem; font-size: .95rem; font-weight: 600; }
.bot-state::before {
  content: ""; width: 8px; height: 8px; border-radius: 50%;
  background: currentColor; flex: none;
}
/* The state colour is an addition to the word standing next to it - never alone. */
.bot-state[data-kind="dziala"] { color: var(--stan-dziala); }
.bot-state[data-kind="stoi"] { color: var(--stan-stoi); }
.bot-state[data-kind="zrzuca"] { color: var(--stan-zrzuca); }
.bot-state[data-kind="przejsciowy"], .bot-state[data-kind="nieznany"] { color: var(--tekst-drugorzedny); }

.bot-since {
  margin-top: .35rem;
  font-family: var(--mono); font-size: .85rem; font-weight: 600;
  font-variant-numeric: tabular-nums;
  color: var(--tekst-drugorzedny);
}
/* A bot that has been stopped for hours has to catch the eye - that is the only
   reason this screen exists at all. */
.bot-since[data-alarm="true"] { font-size: 1.15rem; color: var(--stan-stoi); }

.bot[data-stale="true"] .bot-name,
.bot[data-stale="true"] .bot-since { color: var(--tekst-slaby); }
.bot[data-stale="true"] .bot-state { color: var(--stan-offline); }

.bot-stats { display: flex; flex-wrap: wrap; gap: .35rem 1.4rem; margin-top: .8rem; }
.bot-stat-label, .card-label {
  font-size: .68rem; letter-spacing: .08em; text-transform: uppercase;
  color: var(--tekst-slaby);
}
.bot-stat-value { font-family: var(--mono); font-size: 1.05rem; font-variant-numeric: tabular-nums; }

.bot-actions { display: grid; grid-template-columns: repeat(3, 1fr); gap: .5rem; margin-top: .9rem; }

/* Delivery for the whole machine - separately below the bots, so that it does not
   get confused with the buttons next to a single bot. */
.machine-foot { border-top: 1px solid var(--obrys); padding: .7rem 1.1rem; }

.cmd-status {
  margin-top: .55rem; min-height: 1rem;
  font-family: var(--mono); font-size: .74rem; color: var(--tekst-slaby);
}
.cmd-status[data-status="done"] { color: var(--stan-dziala); }
.cmd-status[data-status="failed"], .cmd-status[data-status="expired"] { color: var(--alarm); }
.cmd-status[data-status="queued"], .cmd-status[data-status="claimed"] { color: var(--stan-stoi); }
/* 'partial' is the aggregate status of a whole-machine command that succeeded
   for some of the bots - the per-bot dataset.status never uses it (see
   cmdStatusForBot in app.js), so this concerns only the view of the command itself. */
.cmd-status[data-status="partial"] { color: var(--stan-stoi); }

/* --- loot ---------------------------------------------------------------- */

/* Four tiles across on a full-width page - see the wide-screen block at the
   bottom of the file. Here, on a phone, it is one column and that is the case
   that has to stay comfortable. */
.loot-grid { display: grid; gap: .6rem; grid-template-columns: 1fr; align-items: start; }

/* The tile's height is driven by exactly four things: the name (one or two
   lines), the row of labels, the stat list and the footnote.
   THE STAT LIST IS NO LONGER CAPPED. It was, at five lines with a "+N more"
   note; the owner asked for the whole description, the same call he made about
   the ellipsis further down. So the tile is as tall as the item is long - four
   lines for a rune, twenty for a runeword - and a row of tiles is uneven.
   Everything else here is padding and leading, tightened but not squeezed: the
   item's own NAME stays at reading size, because that is what the eye comes here
   for. Do not buy the grid back with a cap or an ellipsis. */
.loot-card {
  background: var(--tlo-karta);
  border: 1px solid var(--obrys);
  border-left: 4px solid var(--tekst-slaby);
  border-radius: var(--promien-maly);
  padding: .55rem .65rem .5rem;
  /* A tile is a grid cell now; without this a long word inside it would push the
     whole column wider instead of wrapping. */
  min-width: 0;
}
/* Uniques and set items keep the stronger background - these are the finds this
   screen gets opened for. They no longer get extra padding on top of it: at four
   columns that made them a different SIZE of tile, and the row was as tall as the
   tallest one in it anyway. The background, the border and the bigger name still
   set them apart. */
.loot-card[data-wazny="true"] {
  background: var(--tlo-karta-wyzej);
  border-color: var(--obrys-mocny);
}
.loot-head { display: flex; align-items: baseline; gap: .2rem .45rem; flex-wrap: wrap; }
/* The name takes the rest of its line and the labels wrap underneath it - that is
   what turns the header into "a name, then a meta row" without a second element.
   min-width: 0 so a long name is allowed to wrap instead of widening the tile. */
.loot-name {
  flex: 1 1 auto; min-width: 0;
  font-size: 1.05rem; line-height: 1.2; font-weight: 800; letter-spacing: -.02em;
  text-wrap: balance; overflow-wrap: anywhere;
}
.loot-card[data-wazny="true"] .loot-name { font-size: 1.15rem; }
.loot-quality, .item-have { flex: none; }
.loot-quality {
  font-size: .64rem; font-weight: 700; letter-spacing: .08em; text-transform: uppercase;
  padding: .1rem .4rem; border-radius: 999px;
  border: 1px solid var(--obrys-mocny);
}
.loot-stats {
  display: flex; flex-direction: column; gap: .05rem;
  margin: .35rem 0 0; padding: 0; list-style: none;
}
.loot-card[data-wazny="true"] .loot-stats { gap: .1rem; margin-top: .4rem; }
/* A stat line WRAPS and is shown whole.
   It was briefly cut with an ellipsis, to keep every line exactly one line high
   and the tiles level with each other. The owner asked for that undone, and he
   is right about which of the two matters: an item you cannot read the stats of
   is not worth a tidy row of tiles. "+18% Enhanced Defense" cut to "+18% Enhan…"
   is the one thing you opened the screen for.
   The cost is accepted: tiles in a row can now differ in height by a line or two. */
.loot-stats li {
  font-family: var(--mono); font-size: .72rem; line-height: 1.35;
  color: var(--tekst-drugorzedny); font-variant-numeric: tabular-nums;
  min-width: 0; overflow-wrap: anywhere;
}
.loot-card[data-wazny="true"] .loot-stats li { font-size: .76rem; color: var(--tekst); }
.loot-foot {
  margin-top: .4rem; padding-top: .35rem; border-top: 1px solid var(--obrys);
  font-family: var(--mono); font-size: .7rem; line-height: 1.35;
  color: var(--tekst-slaby); overflow-wrap: anywhere;
}

/* The quality colour - always together with the worded caption in .loot-quality. */
.loot-card[data-quality="4"] { border-left-color: var(--jakosc-magiczny); }
.loot-card[data-quality="4"] .loot-name, .loot-card[data-quality="4"] .loot-quality { color: var(--jakosc-magiczny); }
.loot-card[data-quality="5"] { border-left-color: var(--jakosc-zestaw); }
.loot-card[data-quality="5"] .loot-name, .loot-card[data-quality="5"] .loot-quality { color: var(--jakosc-zestaw); }
.loot-card[data-quality="6"] { border-left-color: var(--jakosc-rzadki); }
.loot-card[data-quality="6"] .loot-name, .loot-card[data-quality="6"] .loot-quality { color: var(--jakosc-rzadki); }
.loot-card[data-quality="7"] { border-left-color: var(--jakosc-unikat); }
.loot-card[data-quality="7"] .loot-name, .loot-card[data-quality="7"] .loot-quality { color: var(--jakosc-unikat); }
.loot-card[data-quality="8"] { border-left-color: var(--jakosc-wykuty); }
.loot-card[data-quality="8"] .loot-name, .loot-card[data-quality="8"] .loot-quality { color: var(--jakosc-wykuty); }
.loot-card[data-wazny="true"] .loot-quality { border-color: currentColor; background: rgba(255, 255, 255, .06); }

/* How many pieces are sitting on the character ("×3"). A separate class, not
   .loot-quality: the quality colours tint that label and the number would
   masquerade as a quality name. */
.item-have {
  font-size: .68rem; font-weight: 700;
  padding: .1rem .4rem; border-radius: 999px;
  border: 1px solid var(--obrys-mocny);
  background: var(--tlo-wglebienie);
  color: var(--tekst);
}

/* --- On characters --------------------------------------------------------
   One question: which bot needs unloading. The tile keeps the layout of the
   machine tile (.machine): a header with the name and the count, content in the
   middle, a footer with an action - and the items inside are the same .loot-card
   as on the Loot screen. */

/* The alarm about unpaused deliveries stands ABOVE the list - it undermines the
   credibility of everything below it, so it has to be read first. */
.chars-alarm { margin-bottom: 1.2rem; }
.chars-alarm .sub { margin-top: 0; }
.chars-alarm .note { margin-bottom: .8rem; }

.chars-grid { display: grid; gap: .7rem; grid-template-columns: 1fr; align-items: start; }

.char-card {
  background: var(--tlo-karta);
  border: 1px solid var(--obrys);
  border-radius: 16px;
  overflow: hidden;
  min-width: 0;
}
.char-head {
  display: flex; align-items: baseline; gap: .5rem;
  padding: .75rem .85rem .4rem;
}
.char-name { flex: 1; min-width: 0; }
.char-name h2 { font-size: 1.1rem; font-weight: 800; overflow-wrap: anywhere; }
.char-where {
  margin-top: .1rem;
  font-family: var(--mono); font-size: .7rem; color: var(--tekst-slaby);
}
.char-count { flex: none; text-align: right; }
/* Cells, not pieces - and still the biggest thing in the header, because this is
   the number the screen exists for. */
.char-count-value {
  font-family: var(--mono); font-size: 1.2rem; font-weight: 700;
  font-variant-numeric: tabular-nums;
}
.char-count-sub {
  font-family: var(--mono); font-size: .72rem; color: var(--tekst-slaby);
  font-variant-numeric: tabular-nums;
}

/* THE COLOUR IS NEVER ALONE. The thresholds (150 / 250 cells, ItemSize) tint the
   number and outline the whole card, but the WORD - "filling up", "needs
   unloading" - stands right under it. The two colours are the ones the
   panel already uses for a stopped bot and for an alarm, so they carry the same
   meaning everywhere and have already been checked for contrast and for colour
   blindness. */
.char-fill { margin-top: .15rem; font-size: .72rem; font-weight: 700; color: var(--tekst-slaby); }
.char-card[data-fill="warn"] { border-color: var(--stan-stoi); }
.char-card[data-fill="warn"] .char-count-value,
.char-card[data-fill="warn"] .char-fill { color: var(--stan-stoi); }
.char-card[data-fill="full"] { border-color: var(--alarm); }
.char-card[data-fill="full"] .char-count-value,
.char-card[data-fill="full"] .char-fill { color: var(--alarm); }

/* A requirement of milestone S2, not decoration: without the date this list would
   masquerade as a reflection of the stash. That is why it always stands there,
   also when nobody has reset anything. */
.char-since {
  padding: 0 .85rem .6rem;
  font-family: var(--mono); font-size: .72rem; color: var(--tekst-drugorzedny);
}

/* One column inside a narrow character card, more once the card is wide - see the
   wide-screen block. align-items: start so a tile with two stats stays short
   instead of being stretched to the height of the tallest one in its row. */
.char-items {
  display: grid; gap: .5rem; grid-template-columns: 1fr; align-items: start;
  padding: .6rem .7rem;
  border-top: 1px solid var(--obrys);
  background: var(--tlo-wglebienie);
}
.char-none { font-size: .85rem; color: var(--tekst-drugorzedny); }

.char-actions { padding: .6rem .85rem .8rem; }

/* The reset question takes the place of the button. It has to look like an alarm,
   because this cannot be undone - and it has to have both answers out in the open. */
.char-confirm {
  margin: .6rem .85rem .8rem; padding: .8rem .9rem;
  background: var(--alarm-tlo);
  border: 1px solid var(--alarm-obrys);
  border-radius: var(--promien-maly);
}
.char-confirm-what { font-size: .95rem; font-weight: 800; color: var(--alarm-tekst); }
.char-confirm-why { margin-top: .3rem; font-size: .82rem; color: var(--tekst-drugorzedny); text-wrap: pretty; }
.char-confirm-error { margin-top: .45rem; font-size: .8rem; font-weight: 600; color: var(--alarm); }
.char-confirm-actions { display: flex; gap: .5rem; margin-top: .8rem; }

/* Striking a single item off the ledger, by hand. The buttons sit UNDER the tile:
   .loot-head lines its children up on a text baseline and already carries the
   drop-off tick, the name, the quality and the count - a button in there would
   push the name onto its own line at 360 px.
   The tile keeps its own padding, so all this needs is a gap above and the room
   to wrap when both "remove 1" and "remove all N" are out. Tiles are deliberately
   uneven in height (see .loot-stats) - one short row does not change that. */
.char-item-actions { display: flex; flex-wrap: wrap; gap: .35rem; margin-top: .45rem; }
/* A tile that arrived without loot ids has no button to show (the response caps
   how many it hands back) - and an empty row would still leave a gap under it. */
.char-item-actions:empty { display: none; }
/* The question takes the buttons' place and keeps the alarm look of .char-confirm;
   only the box's own margins go, because inside a tile they are padding twice. */
.char-item-confirm { margin: .45rem 0 0; padding: .55rem .6rem; }
.char-item-confirm .char-confirm-what { font-size: .85rem; }
.char-item-confirm .char-confirm-why { font-size: .78rem; }
.char-item-confirm .char-confirm-actions { margin-top: .55rem; }

/* --- selective drop-off (S5) ----------------------------------------------
   Tick items, choose the pickit rule that fetches them, send one command.
   The panel sits above the ledger and is drawn from its own state: the cards
   below redraw every fifteen seconds, and a <select> inside one of them would
   be rebuilt halfway through being used. */

/* The tick lives in the item tile's header, next to the name. `align-self`
   because .loot-head lines its children up on the baseline of the text, and a
   checkbox has no text to sit on. */
.item-pick { flex: none; align-self: center; }

.deliver { margin-bottom: 1.2rem; }
.deliver .sub { margin-top: 0; margin-bottom: .6rem; }

/* The bar is the same object as the one on the Settings screen (.send-bar) -
   sticky above the view bar - so it only needs its text to wrap on a phone:
   with a character name and two numbers in it, one line is not enough at 360 px. */
.deliver-bar .send-count { white-space: normal; }
/* .send-bar sets display: flex, and an author rule beats the browser's
   [hidden] { display: none } no matter the specificity - without this the bar
   stays on the screen with nothing ticked. The same trap as .btn[hidden]. */
.deliver-bar[hidden] { display: none; }

.deliver-panel {
  background: var(--tlo-karta);
  border: 1px solid var(--obrys);
  border-radius: var(--promien);
  padding: 1rem 1.1rem 1.1rem;
}
/* The question and the answer look different from the picker on purpose: by
   then the choosing is over and what is left is a decision. The accent is the
   panel's "this is about to happen" colour, the same as on the primary button. */
.deliver-panel-ask {
  background: var(--akcent-tlo);
  border-color: var(--akcent-obrys);
}
.deliver-panel-done { border-color: var(--obrys-mocny); }

.deliver-who { display: flex; align-items: baseline; gap: .6rem; flex-wrap: wrap; }
.deliver-who-what { flex: 1; min-width: 12rem; font-size: 1.05rem; font-weight: 800; }
.deliver-who-sum { font-size: .8rem; color: var(--tekst-drugorzedny); }

.deliver-files { display: flex; flex-wrap: wrap; gap: .6rem; margin-top: .9rem; }
.deliver-field { flex: 1 1 16rem; display: flex; flex-direction: column; gap: .3rem; }
.deliver-field-label { font-size: .78rem; font-weight: 700; color: var(--tekst-drugorzedny); }
.deliver-field select {
  font: inherit; font-size: .9rem; font-weight: 600;
  min-height: 46px; padding: 0 .7rem;
  background: var(--tlo-wglebienie);
  border: 1px solid var(--obrys-mocny);
  border-radius: var(--promien-maly);
  color: var(--tekst);
}

/* The seller's game box (A2) puts a text field and a password field into the
   same .deliver-field row the owner's screen fills with selects. Without this
   they come out in the browser's default black-on-white, on a dark panel.
   1rem AND NOT .9rem, unlike the select above: Safari on iOS zooms the whole
   page in when a focused field is under 16px, and the seller works from a phone.
   Same size and same reason as .login-box input. */
.deliver-field input {
  font: inherit; font-size: 1rem; font-weight: 600;
  min-height: 46px; padding: 0 .7rem;
  background: var(--tlo-wglebienie);
  border: 1px solid var(--obrys-mocny);
  border-radius: var(--promien-maly);
  color: var(--tekst);
  min-width: 0;
}

.deliver-note { margin-top: .6rem; font-size: .78rem; color: var(--tekst-slaby); text-wrap: pretty; }

/* Something the drop-off cannot be composed without. Not an error - a missing
   step, with the place to go and do it. */
.deliver-missing {
  margin-top: .7rem; padding: .6rem .75rem;
  background: var(--tlo-karta-wyzej);
  border: 1px solid var(--obrys-mocny);
  border-radius: var(--promien-maly);
  font-size: .84rem; color: var(--tekst-drugorzedny); text-wrap: pretty;
}
.deliver-missing strong { color: var(--tekst); }

/* One block per item code - that is the unit the rule question is asked in. */
.deliver-rule {
  margin-top: .8rem; padding: .8rem .85rem;
  background: var(--tlo-wglebienie);
  border: 1px solid var(--obrys);
  border-radius: var(--promien-maly);
}
.deliver-rule-head { display: flex; align-items: baseline; gap: .6rem; flex-wrap: wrap; }
.deliver-rule-what { flex: 1; min-width: 10rem; font-size: .95rem; font-weight: 700; }
.deliver-rule-code { font-size: .74rem; color: var(--tekst-slaby); }
.deliver-rule-why { margin-top: .35rem; font-size: .82rem; color: var(--tekst-drugorzedny); text-wrap: pretty; }
.deliver-rule-actions { margin-top: .6rem; }

.deliver-choices { display: flex; flex-direction: column; gap: .5rem; margin-top: .7rem; }
/* The whole row is the target, not just the radio: this is chosen with a thumb. */
.deliver-choice {
  display: flex; gap: .6rem; align-items: flex-start;
  padding: .6rem .7rem;
  background: var(--tlo-karta);
  border: 1px solid var(--obrys);
  border-radius: var(--promien-maly);
  cursor: pointer;
}
.deliver-choice input { width: 22px; height: 22px; flex: none; margin: 0; accent-color: var(--akcent); }
/* The chosen row is outlined AND its radio is filled in - the outline alone
   would be colour doing the talking on its own. */
.deliver-choice[data-chosen="true"] { border-color: var(--akcent-obrys); background: var(--akcent-tlo); }
.deliver-choice-body { flex: 1; min-width: 0; }
.deliver-choice-title { font-size: .9rem; font-weight: 700; overflow-wrap: anywhere; }

.deliver-flags { display: flex; flex-wrap: wrap; gap: .3rem; margin-top: .35rem; }
.deliver-flag {
  font-size: .68rem; font-weight: 700;
  padding: .15rem .45rem; border-radius: 999px;
  border: 1px solid var(--obrys-mocny);
  color: var(--tekst-drugorzedny);
}
.deliver-flag-warn { border-color: var(--stan-stoi); color: var(--stan-stoi); }

/* THE CONDITIONS ARE THE POINT OF THE SCREEN, so they are readable text and not
   a caption: the title of a rule lies ("Shako ANY" excludes ethereals), and
   this list is what tells you whether a sibling goes down with the ticked piece. */
.deliver-cond { list-style: none; margin: .45rem 0 0; padding: 0; display: grid; gap: .15rem; }
.deliver-cond li {
  font-family: var(--mono); font-size: .76rem; color: var(--tekst-drugorzedny);
  overflow-wrap: anywhere;
}
.deliver-cond-none { margin-top: .45rem; font-size: .78rem; color: var(--stan-stoi); }

/* Not styled as an alarm: nothing is broken. It is the one sentence that has to
   be read before the button, so it gets a frame and stays out of the grey. */
.deliver-warn {
  margin-top: .9rem; padding: .7rem .8rem;
  background: var(--tlo-karta-wyzej);
  border-left: 3px solid var(--stan-stoi);
  border-radius: var(--promien-maly);
  font-size: .82rem; color: var(--tekst-drugorzedny); text-wrap: pretty;
}

.deliver-error {
  margin-top: .7rem; padding: .6rem .75rem;
  background: var(--alarm-tlo);
  border: 1px solid var(--alarm-obrys);
  border-radius: var(--promien-maly);
  font-size: .84rem; font-weight: 600; color: var(--alarm-tekst); text-wrap: pretty;
}

.deliver-ask-what { font-size: 1.05rem; font-weight: 800; text-wrap: pretty; }
.deliver-ask-why { margin-top: .5rem; font-size: .82rem; color: var(--tekst-drugorzedny); text-wrap: pretty; }
.deliver-ask-rules, .deliver-facts {
  list-style: none; margin: .7rem 0 0; padding: 0; display: grid; gap: .3rem;
}
.deliver-ask-rules li, .deliver-facts li {
  font-size: .84rem; color: var(--tekst); overflow-wrap: anywhere;
}
.deliver-facts li { font-family: var(--mono); font-size: .78rem; }

.deliver-actions { display: flex; flex-wrap: wrap; gap: .5rem; margin-top: 1rem; }
/* A dark button with no reason next to it reads as broken - the reason is a
   sentence, never just the greying out. */
.deliver-blocked { margin-top: .5rem; font-size: .8rem; font-weight: 600; color: var(--stan-stoi); }

/* --- history ------------------------------------------------------------- */

/* The same split as on the Bots screen: a machine tile, bots inside. */
.history-grid { display: grid; gap: .9rem; grid-template-columns: 1fr; align-items: start; }

/* A thicker line than inside the card: bots on one machine have similar names
   (msPoppins, maPoppins, soPoppins), and their numbers stand one under the
   other - without a clearer cut it is easy to read the gold of the wrong bot. */
.hist-bot { border-top: 3px solid var(--obrys-mocny); padding: 1rem 1.1rem; }
.hist-bot-name { font-size: 1.2rem; font-weight: 800; }

.card-big { display: grid; grid-template-columns: 1fr 1fr; gap: .9rem; margin-top: .9rem; align-items: start; }
.card-sub { margin-top: .1rem; font-size: .74rem; color: var(--tekst-slaby); }
.card-big-value { font-family: var(--mono); font-size: 1.5rem; font-weight: 600; font-variant-numeric: tabular-nums; }
/* Gold, level, runes and two forms of restarts - five entries that have to fit
   on 390 px as well. Hence flex with a basis, and not rigid columns: with three
   columns on a grid "1 450 000" spilled out of its own cell,
   and with wider cells the rest of the numbers stood in the middle of the screen.
   Every entry has its own caption, so wrapping loses nothing. */
.card-row {
  display: flex; flex-wrap: wrap; gap: .75rem 1rem;
  margin-top: .9rem; padding-top: .9rem; border-top: 1px solid var(--obrys);
}
.card-row > div { flex: 1 1 5rem; }
.card-value { font-family: var(--mono); font-size: .95rem; font-variant-numeric: tabular-nums; }
.card-incidents { margin-top: .8rem; font-family: var(--mono); font-size: .74rem; color: var(--tekst-slaby); }
.card-incidents[data-any="true"] { color: var(--stan-stoi); }

.runs { list-style: none; margin: 0; padding: 0; background: var(--tlo-karta); border: 1px solid var(--obrys); border-radius: var(--promien); overflow: hidden; }
.runs li { padding: .8rem 1.1rem; }
.runs li + li { border-top: 1px solid var(--obrys); }
.run-head { display: flex; align-items: baseline; justify-content: space-between; gap: .75rem; }
.run-name { font-size: 1rem; font-weight: 700; }
.run-done { font-family: var(--mono); font-size: .92rem; font-variant-numeric: tabular-nums; color: var(--tekst-drugorzedny); }
.run-meta {
  margin-top: .25rem; display: flex; flex-wrap: wrap; gap: .2rem 1rem;
  font-family: var(--mono); font-size: .74rem; font-variant-numeric: tabular-nums;
  color: var(--tekst-slaby);
}
.run-failed[data-any="true"] { color: var(--stan-stoi); }
.run-why { margin-top: .2rem; font-size: .74rem; color: var(--tekst-slaby); }

/* --- Settings screen ------------------------------------------------------
   The machine tiles and the bots inside are the same as on the Bots screen
   (.machine, .bot) - only the checkboxes and what the panel last sent are added. */

.upload {
  background: var(--tlo-karta);
  border: 1px solid var(--obrys);
  border-radius: var(--promien);
  padding: 1rem 1.1rem;
  margin-bottom: .9rem;
}
.upload .filters { margin-bottom: .6rem; }
.upload-what { font-size: .85rem; color: var(--tekst-drugorzedny); text-wrap: pretty; }

.check { display: inline-flex; align-items: center; gap: .4rem; cursor: pointer; }
.check input { width: 20px; height: 20px; accent-color: var(--akcent); }

.drop {
  margin-top: .9rem; padding: 1.2rem 1rem;
  border: 1px dashed var(--obrys-mocny);
  border-radius: var(--promien-maly);
  background: var(--tlo-wglebienie);
  text-align: center;
}
.drop[data-over="true"] { border-color: var(--akcent); background: var(--akcent-tlo); }
.drop-main label { display: inline-flex; align-items: center; }
.drop-note { margin-top: .5rem; font-size: .8rem; color: var(--tekst-slaby); }
.drop-picked { margin-top: .6rem; font-size: .85rem; color: var(--akcent-jasny); }

.upload-known { margin-top: .8rem; font-size: .85rem; color: var(--tekst-drugorzedny); }
.upload-known label { display: flex; flex-direction: column; gap: .35rem; }
.upload-known select {
  font: inherit; font-size: .9rem; min-height: 46px; padding: 0 .8rem;
  background: var(--tlo-karta-wyzej); color: var(--tekst);
  border: 1px solid var(--obrys); border-radius: var(--promien-maly);
}
.upload-progress { width: 100%; height: 8px; margin-top: .8rem; }

/* The send bar sticks to the bottom of the screen, above the view bar: with nine
   bots the "send" button would fall off the phone screen. */
.send-bar {
  position: sticky; bottom: calc(74px + env(safe-area-inset-bottom)); z-index: 5;
  display: flex; align-items: center; gap: .75rem;
  margin-bottom: .9rem; padding: .6rem .8rem;
  background: var(--tlo-karta-wyzej);
  border: 1px solid var(--obrys-mocny);
  border-radius: var(--promien-maly);
}
.send-count { flex: 1; font-size: .88rem; color: var(--tekst-drugorzedny); }

.machine-all { flex: none; font-size: .8rem; color: var(--tekst-drugorzedny); }

.cfg-bot { padding: .85rem 1.1rem; }
.cfg-head { display: flex; align-items: center; gap: .6rem; }
/* The label covers the checkbox and the name, but NOT the delete button:
   a button inside a <label> would toggle the bot's selection as a side effect. */
.cfg-pick { display: flex; align-items: center; gap: .6rem; flex: 1; min-width: 0; cursor: pointer; }
.cfg-head input { width: 22px; height: 22px; accent-color: var(--akcent); flex: none; }
.cfg-del { flex: none; }
/* A profile the agent has stopped reporting while the machine is alive - usually
   an old name after a rename. This is the row that is meant to disappear. */
.cfg-gone {
  margin-top: .35rem; padding-left: 2rem;
  font-size: .78rem; font-weight: 600; color: var(--stan-stoi);
}
.cfg-sent {
  list-style: none; margin: .45rem 0 0; padding: 0 0 0 2rem;
  font-family: var(--mono); font-size: .74rem; color: var(--tekst-drugorzedny);
}
.cfg-never { color: var(--tekst-slaby); }
.cfg-bot .cmd-status { padding-left: 2rem; }

/* The question about deleting a profile takes up the whole bot row. It has to look
   like an alarm, because this cannot be undone - and it has to have both answers out in the open. */
.cfg-remove {
  margin-top: .6rem; padding: .8rem .9rem;
  background: var(--alarm-tlo);
  border: 1px solid var(--alarm-obrys);
  border-radius: var(--promien-maly);
}
.cfg-remove-what { font-size: .95rem; font-weight: 800; color: var(--alarm-tekst); }
.cfg-remove-why { margin-top: .3rem; font-size: .82rem; color: var(--tekst-drugorzedny); text-wrap: pretty; }
.cfg-remove-live { margin-top: .45rem; font-size: .78rem; color: var(--stan-stoi); text-wrap: pretty; }
.cfg-remove-error { margin-top: .45rem; font-size: .8rem; font-weight: 600; color: var(--alarm); }
.cfg-remove-actions { display: flex; gap: .5rem; margin-top: .8rem; }
.btn-danger {
  background: rgba(255, 93, 108, .16);
  border-color: var(--alarm-obrys);
  color: var(--alarm-tekst);
}
.btn-danger:hover:not(:disabled) { background: rgba(255, 93, 108, .28); }

/* --- empty screens ------------------------------------------------------- */

.empty {
  text-align: center; padding: 3rem 1.25rem;
  border: 1px dashed var(--obrys-mocny);
  border-radius: 20px;
  background: var(--tlo-wglebienie);
}
.empty-title { font-size: 1.05rem; font-weight: 700; }
.empty-why {
  margin: .4rem auto 0; max-width: 32ch;
  font-size: .88rem; color: var(--tekst-drugorzedny); text-wrap: pretty;
}
.empty-action { margin-top: 1.1rem; }

/* --- wider screen --------------------------------------------------------
   Three columns only once a card still has a sensible width.
   The view bar stays at the bottom on the desktop too: on this page you mostly
   click the buttons next to the bots, not the tabs. */

@media (min-width: 52rem) {
  main { padding: 0 1.25rem; }
  /* A machine tile and a history tile hold rows of numbers and three buttons
     side by side - they still want 23rem. Only the item and character tiles get
     packed. */
  .machines, .history-grid { grid-template-columns: repeat(auto-fit, minmax(23rem, 1fr)); }

  /* FOUR COLUMNS ON A FULL-WIDTH PAGE, AND NEVER MORE THAN FOUR. main is capped
     at 76rem and spends 1.25rem of padding on each side, so a row has 73.5rem to
     give away. With a 17rem minimum track, four of them need 70.1rem including
     the gaps and five would need 87.8rem - so the count settles on four however
     wide the monitor is, and no max-column rule is needed to hold it there.

     Downwards it falls by itself, with no second breakpoint to maintain:
       four   - window wider than about 73rem
       three  - about 55rem to 73rem
       two    - 52rem to about 55rem
       one    - below 52rem, where this block stops applying altogether and the
                single-column rule declared next to each grid takes over.
     A phone is always in the last row of that table: no multi-column rule can
     reach it, whatever the tile minimum turns out to be.

     .char-items measures itself against the CARD it sits in, not the window, so
     inside a packed .chars-grid it is one column - which is what it should be.

     auto-FILL, not auto-fit: a fit collapses the empty tracks, so a screen with
     one find on it would stretch that single tile across the page - the exact
     complaint this change is answering. */
  .loot-grid, .chars-grid, .char-items {
    grid-template-columns: repeat(auto-fill, minmax(17rem, 1fr));
  }
  .screen-head h1 { font-size: 1.9rem; }
}

/* --- login and roles (A0, A1) -------------------------------------------- */

/* The login page has its own, small document - before logging in we do not send
   a single dedicated-server name to the browser. Hence separate classes instead
   of the dashboard layout. */
.login-body {
  display: flex; align-items: center; justify-content: center;
  min-height: 100dvh; padding: 1rem;
}
.login-box {
  width: min(24rem, 100%);
  background: var(--tlo-karta);
  border: 1px solid var(--obrys);
  border-radius: var(--promien);
  padding: 1.6rem 1.4rem;
}
.login-box h1 { font-size: 1.5rem; font-weight: 800; margin-bottom: 1.1rem; }
.login-box form { display: flex; flex-direction: column; gap: .9rem; }
.login-box label {
  display: flex; flex-direction: column; gap: .35rem;
  font-size: .82rem; color: var(--tekst-drugorzedny);
}
.login-box input {
  min-height: 46px; padding: 0 .7rem;
  background: var(--tlo-wglebienie);
  border: 1px solid var(--obrys-mocny);
  border-radius: var(--promien-maly);
  color: var(--tekst); font-size: 1rem;
}
.login-box button {
  min-height: 50px; margin-top: .3rem;
  background: var(--akcent); color: var(--akcent-tekst);
  border: 0; border-radius: var(--promien-maly);
  font-size: 1rem; font-weight: 800; cursor: pointer;
}
/* The error has to be readable, not just seen as red. */
.login-error {
  margin-bottom: .9rem; padding: .7rem .8rem;
  background: var(--alarm-tlo);
  border: 1px solid var(--alarm-obrys);
  border-radius: var(--promien-maly);
  color: var(--alarm-tekst); font-weight: 700;
}
.login-note { margin-top: 1rem; font-size: .78rem; color: var(--tekst-slaby); }

.logout { display: flex; align-items: center; gap: .6rem; }
.who { font-size: .8rem; color: var(--tekst-slaby); }

/* A sentence explaining what the screen is. On the vendor screen this is not
   decoration: without it the list looks like a readout of the stash, while it is a ledger. */
.screen-what {
  margin: 0 .15rem 1rem; padding: .7rem .85rem;
  background: var(--tlo-karta);
  border: 1px solid var(--obrys);
  border-radius: var(--promien-maly);
  font-size: .84rem; color: var(--tekst-drugorzedny);
}

/* The word carries the information, the colour only reinforces it. */
.char-where.zyje { color: var(--stan-dziala); }
.char-where.nie-zyje { color: var(--alarm); }
