/* The screens themselves: forms, posts, the composer, the queue, the sheet.
   The shell around them — the rail, the map, the board — is in shell.css, and
   every colour in both files comes from tokens.css. */

* { box-sizing: border-box; }

/* The UA rule for [hidden] is display:none at the lowest specificity, so any
   author 'display' below would quietly beat it and show a hidden element. */
[hidden] { display: none !important; }

body {
  margin: 0;
  background: var(--bg);
  color: var(--ink);
  font: 16px/1.65 Georgia, 'Times New Roman', serif;
}

/* Every screen gets the whole window. Where a *line* of prose still needs a
   measure — the rules pages, a character's biography — that is the job of the
   column it sits in, not of the page it sits on. --measure stays for those. */
/* The side padding is written twice so the notch can raise it — see .topbar in
   shell.css, which does the same for the same reason. */
.view {
  max-width: none;
  margin: 0 auto;
  padding: 1.5rem 1.75rem 6rem;
  padding-left: max(1.75rem, env(safe-area-inset-left));
  padding-right: max(1.75rem, env(safe-area-inset-right));
}
.view > h1 { font-size: 1.5rem; margin: 0 0 0.25rem; }

.muted { color: var(--ink-dim); }
.row-between { display: flex; justify-content: space-between; align-items: flex-start; gap: 1rem; }

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

.card {
  background: var(--panel);
  border: 1px solid var(--line);
  border-radius: var(--radius);
  padding: 1.25rem;
  margin: 1rem 0;
  display: grid;
  gap: 0.75rem;
}
.card.narrow { max-width: 24rem; }
.card h1 { margin: 0; font-size: 1.25rem; }

label { display: grid; gap: 0.25rem; font-size: 0.85rem; color: var(--ink-dim); }

input, select, textarea, button {
  font: inherit;
  color: var(--ink);
  background: var(--panel-2);
  border: 1px solid var(--line-control);
  border-radius: var(--radius);
  padding: 0.5rem 0.6rem;
}
input:focus-visible, select:focus-visible, textarea:focus-visible, button:focus-visible {
  outline: 2px solid var(--accent);
  outline-offset: 1px;
}

/* Sliders drawn by hand: the browsers' own disagree, and the rule above would
   put a box round the track. Each engine needs its own pseudo-elements, and
   they cannot share a selector — one unknown one drops the whole rule. */
input[type='range'] {
  -webkit-appearance: none;
  appearance: none;
  height: 1.25rem;
  padding: 0;
  border: none;
  background: transparent;
  cursor: pointer;
}
input[type='range']::-webkit-slider-runnable-track {
  height: 4px;
  background: var(--line-bright);
  border-radius: 2px;
}
input[type='range']::-moz-range-track {
  height: 4px;
  background: var(--line-bright);
  border-radius: 2px;
}
input[type='range']::-webkit-slider-thumb {
  -webkit-appearance: none;
  appearance: none;
  width: 12px;
  height: 12px;
  margin-top: -4px; /* centres it on the 4px track */
  background: var(--amber);
  border: none;
  border-radius: 50%;
}
input[type='range']::-moz-range-thumb {
  width: 12px;
  height: 12px;
  background: var(--amber);
  border: none;
  border-radius: 50%;
}

button { cursor: pointer; }
button.primary { background: var(--accent); color: var(--on-accent); border-color: var(--accent); font-weight: 700; }
button.ghost { background: transparent; color: var(--ink-dim); }
button.link { background: none; border: none; color: var(--accent); padding: 0; text-decoration: underline; }
button.small { padding: 0.3rem 0.5rem; font-size: 0.85rem; }
button[disabled] { opacity: 0.5; cursor: not-allowed; }

.notice { color: var(--danger); margin: 0.5rem 0; font-size: 0.9rem; }
.notice.muted { color: var(--ink-dim); }

/* ------------------------------------------------------- the character card */

/* The one page a player makes once and then owns. Everything here exists to
   give their own pictures and prose room, rather than to fit more fields in. */
.karta { display: grid; gap: 1.25rem; }

/* The forest is the wall; this is what hangs on it. Without a surface under
   it, a table of statistics sits directly on photographed trees and both lose:
   the numbers are hard to read and the picture is wasted holding them up. */
.karta-body {
  background: var(--fora-panel);
  border: 1px solid var(--line);
  padding: 0 2rem 2.5rem;
}

/* The name and the avatar stay off the panel, against the trees, the way a
   category banner does on the board. */
.karta-head {
  padding: 0 0.25rem 0.25rem;
  text-shadow: 0 1px 12px rgba(0, 0, 0, 0.8);
}

/* Two boxes the page redraws when you switch between the accepted card and the
   proposed one. They hold nothing of their own — display: contents keeps the
   banner a grid child of .karta and the facts a flex child of the masthead,
   exactly as when they were written straight in. */
.karta-banner-slot, .karta-facts { display: contents; }

.karta-banner {
  margin: 0;
  overflow: hidden;
  border: 1px solid var(--line);
  /* Wide and short: a header, not an illustration competing with the avatar. */
  aspect-ratio: 5 / 1;
  background: var(--panel);
}
.karta-banner img { width: 100%; height: 100%; object-fit: cover; display: block; }

/* Face, name, facts and the seven — everything you read to know whose card
   this is, before any tab is involved.

   The quartermaster's-notebook grid from the Zaplecze document: two repeating
   gradients at 26px, faint enough to read through, and a hard rule under the
   whole thing. It also solves the legibility problem the forest created — the
   facts sit on a surface now instead of on photographed trees. */
.karta-head {
  position: relative;
  display: flex;
  align-items: flex-start;
  flex-wrap: wrap;
  gap: 1.5rem 2.5rem;
  padding: 1.75rem 1.5rem 1.5rem;
  background-color: var(--bg-panel);
  background-image: var(--grid-lines);
  border: 1px solid var(--line);
  border-bottom: 2px solid var(--line-bright);
}
/* Reading the proposal. The page says so in its own frame, because the one
   mistake worth designing against here is accepting the wrong version — or
   thinking the board already shows what you are looking at. */
.karta.is-proposal .karta-head,
.karta.is-proposal .karta-body { border-color: var(--amber); }
.karta.is-proposal .karta-body { border-style: dashed; }

.karta-head h1 { margin: 0 0 0.6rem; font-family: var(--font-title); letter-spacing: 0.01em; }

/* The stamped labels on the document's masthead: mono, tracked, outlined. */
.stamp-row { display: flex; flex-wrap: wrap; gap: 0.55rem; margin-bottom: 0.6rem; }
.stamp-row .stamp {
  font-family: var(--font-mono);
  font-size: var(--label);
  letter-spacing: 0.14em;
  text-transform: uppercase;
  border: 1px solid var(--line-bright);
  color: var(--ink-dim);
  padding: 0.3rem 0.7rem;
}
.karta-ident { flex: 1 1 14rem; min-width: 0; }

/* The facts get the rest of the masthead now that the seven have gone back to
   their own tab: room to grow to two columns where there is width for it,
   rather than shrinking to whatever was left over. */
.karta-head .card-facts { flex: 1 1 22rem; min-width: 0; margin: 0; }

.karta-tabs {
  display: flex;
  gap: 0.25rem;
  margin: 0 -2rem 1.75rem;
  padding: 0 2rem;
  border-bottom: 1px solid var(--line-bright);
}
.karta-tab {
  background: transparent;
  border: 1px solid transparent;
  border-bottom: none;
  margin-bottom: -1px;
  padding: 0.55rem 1.1rem;
  font-family: var(--font-title);
  font-size: 0.8rem;
  letter-spacing: 0.16em;
  text-transform: uppercase;
  color: var(--ink-dim);
}
.karta-tab:hover:not(:disabled) { color: var(--accent); }
.karta-tab:disabled { opacity: 0.4; cursor: not-allowed; }
.karta-tab.on {
  color: var(--text);
  border-color: var(--line-bright);
  /* Sits on the panel's own colour so the active tab and the sheet under it
     read as one piece of paper with a notch cut in it. */
  border-bottom: 1px solid var(--fora-panel);
  background: var(--fora-panel);
}

.karta-panel { min-height: 12rem; }

/* Who somebody knows, with their faces. The same auto-fitting grid the kit
   uses, because they are the same kind of object: one card, one thing. */
.relations {
  display: grid;
  gap: 0.75rem;
  grid-template-columns: repeat(auto-fit, minmax(min(19rem, 100%), 1fr));
  margin-bottom: 1.5rem;
}
.relation {
  display: flex;
  gap: 0.9rem;
  padding: 0.85rem 1rem;
  background: var(--panel);
  border: 1px solid var(--line);
  border-left: 3px solid var(--rust);
}
.relation-face {
  flex: none;
  width: 54px;
  height: 70px;
  overflow: hidden;
  background: var(--bg-panel);
}
.relation-face .face { width: 100%; height: 100%; object-fit: cover; display: block; aspect-ratio: auto; }
.relation-face .portrait-initials { font-size: 0.85rem; letter-spacing: 0; }
.relation-body { display: grid; gap: 0.3rem; align-content: start; min-width: 0; }
.relation-name {
  font-family: var(--font-title);
  font-size: 0.98rem;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: var(--text);
  text-decoration: none;
}
.relation-name:hover { color: var(--amber-bright); }
.relation-note { margin: 0; font-size: 0.9rem; color: var(--text-soft); line-height: 1.5; }

/* Picking them, in the editor. */
.relation-add { display: flex; gap: 0.5rem; flex-wrap: wrap; align-items: center; }
.relation-add input { flex: 1 1 14rem; min-width: 0; }
.relation-picks { display: grid; gap: 0.3rem; margin-top: 0.5rem; }
.relation-pick {
  display: flex;
  align-items: center;
  gap: 0.75rem;
  padding: 0.35rem 0.6rem;
  background: var(--panel-2);
  border-left: 2px solid var(--rust);
  font-size: 0.85rem;
}
.relation-pick input { flex: 1; min-width: 0; }
.relation-pick strong { flex: none; }

/* One card per thing carried, on the document's own auto-fitting grid. */
/* auto-fill, not auto-fit: auto-fit collapses the empty tracks, so a group
   holding one thing stretched that one card across the whole screen and its
   photograph came out the height of the window. The cards keep their size and
   the row is simply not full. */
.kit { display: grid; gap: 0.75rem; grid-template-columns: repeat(auto-fill, minmax(min(17rem, 100%), 1fr)); }
.kit-item {
  display: flex;
  flex-direction: column;
  gap: 0.5rem;
  padding: 0.9rem 1rem 1rem;
  background: var(--panel);
  border: 1px solid var(--line);
  /* A specific object and a pile of something are different kinds of thing. */
  border-left: 3px solid var(--teal);
}
.kit-item.is-stack { border-left-color: var(--green); }
.kit-head { display: flex; align-items: baseline; justify-content: space-between; gap: 0.8rem; }
.kit-head h4 {
  margin: 0;
  font-family: var(--font-title);
  font-weight: 500;
  font-size: 1.02rem;
  letter-spacing: 0.05em;
  text-transform: uppercase;
}
.kit-count {
  flex: none;
  font-family: var(--font-mono);
  font-size: 0.72rem;
  letter-spacing: 0.1em;
  color: var(--green-ink);
  border: 1px solid var(--green);
  padding: 0.15rem 0.45rem;
}
.kit-note { margin: 0; font-size: 0.9rem; color: var(--text-soft); }
/* The same chip beside the heading, where it counts the kit rather than a stack. */
h3 > .kit-count { margin-left: 0.6rem; }

/* The GM's X, in the corner of the card rather than in the row: it is about
   this one thing, and it must not push the name about when it appears. */
.kit-item { position: relative; }
.kit-remove {
  position: absolute;
  top: 0.25rem;
  right: 0.35rem;
  z-index: 1;
  padding: 0 0.3rem;
  line-height: 1.2;
  font-size: 1.1rem;
  background: var(--panel);
  color: var(--text-soft);
  border: 1px solid var(--line-control);
  cursor: pointer;
}
.kit-remove:hover, .kit-remove:focus-visible { color: var(--danger); border-color: currentColor; }

/* The item's own picture, where it has one. Wide and short rather than square:
   these are photographs of objects on a table, not avatars. */
.kit-face {
  margin: -0.9rem -1rem 0;
  aspect-ratio: 3 / 2;
  /* The ratio decides the shape; this decides how big it is allowed to get, so
     a card that ends up wide for any reason still gets a photograph and not a
     poster. */
  max-height: 11rem;
  overflow: hidden;
  background: var(--panel-sunk, var(--panel));
  border-bottom: 1px solid var(--line);
}
.kit-face img { width: 100%; height: 100%; object-fit: cover; display: block; }

/* The switch, sitting on the krótkofalówka rather than above the list. */
.kit-item .karta-radio { margin: 0.1rem 0 0; }
.kit-foot {
  margin-top: auto;
  padding-top: 0.6rem;
  border-top: 1px dashed var(--line-bright);
  font-family: var(--font-mono);
  font-size: 0.74rem;
  color: var(--ink-dim);
}
.kit-foot b {
  display: block;
  font-size: var(--label-sm);
  font-weight: 500;
  letter-spacing: 0.15em;
  text-transform: uppercase;
  margin-bottom: 0.2rem;
}

/* Ekwipunek na liście, for whoever would rather read forty things than look at
   forty photographs. A tick in Ustawienia, remembered per screen like the text
   size beside it — see applyLook in shell.js. Every card keeps its markup and
   every control keeps working; only the shape changes, which is why this is
   CSS and not a second way of drawing the kit. */
[data-kit='lista'] .kit { grid-template-columns: 1fr; gap: 0; }
[data-kit='lista'] .kit-item {
  flex-direction: row;
  align-items: baseline;
  flex-wrap: wrap;
  gap: 0 0.7rem;
  padding: 0.4rem 2rem 0.4rem 0.7rem;
  border-top: none;
  border-right: none;
  border-bottom: 1px solid var(--line);
}
[data-kit='lista'] .kit-head { flex: 1 1 12rem; justify-content: flex-start; gap: 0.6rem; }
[data-kit='lista'] .kit-head h4 { font-size: 0.92rem; }
/* The photograph is the whole reason the card is a card. On a list it becomes
   a thumbnail rather than disappearing: it is still how you tell one
   krótkofalówka from another. */
[data-kit='lista'] .kit-face {
  margin: 0;
  flex: none;
  width: 3rem;
  aspect-ratio: 1;
  max-height: none;
  border: 1px solid var(--line);
  align-self: center;
}
[data-kit='lista'] .kit-note { flex: 0 1 auto; font-size: 0.8rem; }
/* The kategoria, on one line and unlabelled: a column of „KATEGORIA” forty
   times over is the label saying more than the thing. */
[data-kit='lista'] .kit-foot {
  margin: 0;
  padding: 0;
  border-top: none;
  flex: none;
  display: flex;
  gap: 0.35rem;
}
[data-kit='lista'] .kit-foot b { display: none; }
[data-kit='lista'] .kit-remove { top: 50%; transform: translateY(-50%); border: none; background: none; }
/* „zmień” opens under the row rather than beside the name. */
[data-kit='lista'] .studio-editor { flex: 1 0 100%; }
/* A line somebody put on the board themselves, rather than a deed the game
   recorded. Quieter than the deeds around it: it is a note, not a fact the
   server can vouch for. */
.event.is-said .event-what { font-style: italic; }
.event-remove { margin-left: 0.6rem; font-size: 0.7rem; }

/* The two ways into a card, side by side — and quite different things: one
   goes to the staff, the other applies as you press it. */
.karta-ident-actions { display: flex; flex-wrap: wrap; gap: 0.9rem; align-items: baseline; }
.karta-avatar-edit {
  margin-top: 0.6rem;
  padding: 0.75rem 0.9rem;
  max-width: 22rem;
  background: var(--panel);
  border: 1px solid var(--line);
}
.karta-avatar-edit[hidden] { display: none; }

/* What is wrong with somebody, beside their name on their own card. Same chip
   as beside a post — one idea, drawn once. */
.karta-stan { display: flex; flex-wrap: wrap; gap: 4px; margin: 0.4rem 0 0.2rem; }
/* Whether anybody is writing her, on the one page about her (049). A sentence
   rather than a chip: it carries a date, and "wraca 14 sierpnia" is the point. */
.karta-absence { margin: 0.35rem 0 0; font-size: 0.85rem; color: var(--teal-ink); }
.karta-absence.is-zamrozona { opacity: 0.78; }

/* The pages of a thread. Quiet: it is furniture, and the writing is the page.
   Sits at both ends of the posts, so changing page never means scrolling back
   up past ten of them. */
.pager {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 0.3rem;
  padding: 0.6rem 0;
  font-family: var(--font-ui);
  font-size: 0.8rem;
}
.pager-page,
.pager-step {
  min-width: 1.9rem;
  padding: 0.2rem 0.45rem;
  text-align: center;
  border: 1px solid var(--line-control);
  color: var(--text-soft);
}
a.pager-page:hover,
a.pager-step:hover { color: var(--amber-bright); border-color: var(--amber); text-decoration: none; }
.pager-page.is-current {
  color: var(--amber-bright);
  border-color: var(--amber);
  font-weight: 600;
}
/* Still occupies its place at the ends of the run, so the row does not shuffle
   sideways as you page through it. */
.pager-step.is-off { opacity: 0.3; }
.pager-gap { padding: 0 0.15rem; color: var(--ink-dim); }
.pager-count { margin-left: auto; color: var(--ink-dim); font-size: 0.74rem; }

/* Animals, beside the kit rather than in it.
 *
 * An animal is an item underneath (migration 032) and deliberately does not
 * look like one: a face and a name, wider than a kit card and never more than
 * a couple of them, because a character has a dog, not nine. Amber down the
 * side is the same signal the floor of a room uses — this is a thing with its
 * own presence, not a number. */
.zwierzeta { display: grid; gap: 0.75rem; margin-bottom: 1.25rem; }
.zwierze {
  display: flex;
  gap: 1rem;
  padding: 0.85rem 1rem;
  background: var(--panel);
  border: 1px solid var(--line);
  border-left: 3px solid var(--amber);
}
.zwierze-face {
  flex: 0 0 5.5rem;
  height: 5.5rem;
  overflow: hidden;
  background: var(--panel-2);
  box-shadow: inset 0 0 0 1px var(--line);
}
.zwierze-face img { width: 100%; height: 100%; object-fit: cover; display: block; }
.zwierze-face.is-empty {
  display: grid;
  place-items: center;
  text-align: center;
  padding: 0.4rem;
  font-size: var(--label-sm);
  letter-spacing: 0.1em;
  text-transform: uppercase;
  color: var(--ink-dim);
}
.zwierze-body { flex: 1 1 auto; min-width: 0; display: flex; flex-direction: column; gap: 0.3rem; }
.zwierze-head { display: flex; align-items: baseline; justify-content: space-between; gap: 0.8rem; }
.zwierze-head h4 {
  margin: 0;
  font-family: var(--font-title);
  font-weight: 500;
  font-size: 1.02rem;
  letter-spacing: 0.05em;
  text-transform: uppercase;
}
.zwierze-kind {
  font-family: var(--font-mono);
  font-size: var(--label);
  letter-spacing: 0.12em;
  text-transform: uppercase;
  color: var(--ink-dim);
}
.zwierze-note { margin: 0; font-size: 0.9rem; color: var(--text-soft); }
.zwierze-rename { display: grid; gap: 0.2rem; }
.zwierze-rename span {
  font-size: var(--label-sm);
  letter-spacing: 0.15em;
  text-transform: uppercase;
  color: var(--ink-dim);
}

/* One row per stat: what it is, what level it is at, and the points earned
   since that have not turned into the next one yet. */
.stats {
  list-style: none;
  margin: 0;
  padding: 0;
  display: grid;
  gap: 0.15rem;
  max-width: 32rem;
}
/* Two to a row: six of them in a column made a tall list of mostly empty line,
   and they are read against each other rather than in order. */
.stats { max-width: none; gap: 0.4rem; grid-template-columns: repeat(2, minmax(0, 1fr)); }
.stats .stat {
  display: grid;
  grid-template-columns: minmax(0, 1fr) max-content 4.5rem;
  align-items: center;
  gap: 1rem;
  padding: 0.85rem 1.1rem;
  background: var(--panel);
  border: 1px solid var(--line);
  border-left: 3px solid var(--line-bright);
}
.stats .stat.is-max { border-left-color: var(--amber); }
/* The studio's number box takes a fourth column only when it is drawn. */
.stats .stat:has(.stat-setter) { grid-template-columns: minmax(0, 1fr) max-content 4.5rem max-content; }
.stat-setter { display: flex; gap: 0.4rem; }
.stat-set { width: 3.6rem; }
/* Every point after day one, under the six. */
.stat-log { list-style: none; margin: 1rem 0 0; padding: 0; font-size: 0.9rem; }
.stat-log li { padding: 0.35rem 0; border-top: 1px solid var(--line); }
.stat-main { display: grid; gap: 0.15rem; min-width: 0; }
.stat-name { font-family: var(--font-title); font-size: 0.95rem; letter-spacing: 0.1em; text-transform: uppercase; color: var(--text); }
.stat-about { font-size: 0.78rem; color: var(--ink-dim); line-height: 1.4; }
.stat-level { font-family: var(--font-title); line-height: 1; white-space: nowrap; }
.stat-level strong { font-size: 1.9rem; color: var(--text); font-weight: 500; }
.stat-of { font-size: 0.95rem; color: var(--ink-dim); }
.stats .is-max .stat-level strong { color: var(--amber-bright); }

/* Szczęście, across the bottom and in its own colour. It is not one of the six:
   nobody spends points on it, the GM opens it, and it decides ties. Teal says
   that before the sentence under its name repeats it. */
.stats .stat.is-luck {
  grid-column: 1 / -1;
  border-left-color: var(--teal);
}
.stats .is-luck .stat-name { color: var(--teal-ink); }
.stats .is-luck .stat-level strong { color: var(--teal-ink); }

/* One column where two would squeeze; Szczęście is already full width. */
@media (max-width: 46rem) {
  .stats { grid-template-columns: minmax(0, 1fr); }
}
/* How close this stat is to its next level: the count, and the same thing as a
   line so it can be read without doing arithmetic. Twenty-five points is one
   level (rules.md §1), so the bar is that fraction and nothing cleverer. */
.stat-loose {
  display: grid;
  gap: 0.3rem;
  justify-items: end;
  font-family: var(--font-mono);
  font-size: 0.72rem;
  color: var(--ink-dim);
  text-align: right;
}
.stat-bar {
  width: 100%;
  height: 3px;
  background: var(--line);
  overflow: hidden;
}
.stat-bar-fill {
  display: block;
  height: 100%;
  background: var(--amber);
}
/* Nothing left to fill. The word replaces the bar rather than joining it. */
.stat-loose.is-full { color: var(--amber-bright); align-content: center; }
.stats .is-luck .stat-bar-fill { background: var(--teal); }
.stats .is-luck .stat-loose.is-full { color: var(--teal-ink); }

/* Podstawy has two things in it — a short list of facts and a long piece of
   prose — so on a wide screen they sit beside each other instead of the facts
   pushing the writing down the page. */
@media (min-width: 62rem) {
  .karta-panel > div:has(> .card-facts) {
    display: grid;
    grid-template-columns: 18rem minmax(0, 1fr);
    gap: 2.5rem;
    align-items: start;
  }
  .karta-panel > div:has(> .card-facts) > .card-facts { margin-bottom: 0; }
}

/* A line of prose keeps a readable measure even when the page does not: at a
   1600px window an uncapped paragraph runs past 200 characters, which is not
   width being used, it is width being survived. */
/* No measure: the card uses the width of the panel it sits in. */
.card-prose { max-width: none; }
.karta-panel > div > h3 {
  font-family: var(--font-title);
  font-size: 0.95rem;
  letter-spacing: 0.16em;
  text-transform: uppercase;
  color: var(--ink-dim);
  margin: 2rem 0 0.6rem;
}

/* The small facts, as a two-column list rather than a paragraph. */
.card-facts { display: grid; grid-template-columns: max-content 1fr; gap: 0.35rem 1.5rem; margin: 0 0 1.75rem; }
.card-facts dt {
  font-size: var(--label);
  letter-spacing: 0.14em;
  text-transform: uppercase;
  color: var(--ink-dim);
  align-self: baseline;
}
.card-facts dd { margin: 0; }

/* Player-written prose. The face and size come from shell.css, with every
   other prose surface; what is here is the shape of a long biography — room to
   breathe between sections, and pictures that sit in it rather than beside it. */
.card-prose { hyphens: auto; line-height: 1.7; }
.card-prose p { margin: 0 0 1.1rem; }
.card-prose p:last-child { margin-bottom: 0; }

/* A section of the card.

   These sit beside .card-prose rather than inside it — the heading and the
   answer under it are siblings — so they are selected from .card-sections, or
   the generic muted h3 in .karta-panel catches them and they come out small,
   grey and unruled. That is what was happening.

   Two lines, doing two jobs: a faint one above that separates this section from
   the one before it, and a firm one under the words with the amber head on it,
   the way the rules pages and the Zaplecze document do a heading. */
.card-sections .page-head {
  position: relative;
  font-family: var(--font-title);
  font-size: 1.15rem;
  font-weight: 600;
  letter-spacing: 0.18em;
  text-transform: uppercase;
  text-align: left;
  color: var(--text);
  margin: 3rem 0 1.25rem;
  padding: 1.8rem 0 0.6rem;
  border-top: 1px solid var(--line);
  border-bottom: 1px solid var(--line-bright);
}
.card-sections .page-head::after {
  content: '';
  position: absolute;
  left: 0;
  bottom: -1px;
  width: 4.5rem;
  height: 2px;
  background: var(--amber);
}
/* The first one in a tab, and the first after the epigraph, have nothing to be
   divided from. */
.card-sections > .card-section:first-child > .page-head:first-child,
.card-sections > .card-section:has(> .card-epigraph) + .card-section > .page-head:first-child {
  margin-top: 0;
  padding-top: 0;
  border-top: none;
}

/* Zobacz propozycję: what the proposal changes, framed thin and red so the
   staff and the player see the difference without reading both versions. */
.karta .is-changed { outline: 1px solid var(--danger); outline-offset: 4px; }

/* The quote at the head of a history. Set apart from the prose rather than
   dressed up: it is somebody's line, not a pull quote in a magazine. */
.card-prose blockquote {
  margin: 2rem 0;
  padding: 0.2rem 0 0.2rem 1.4rem;
  border-left: 2px solid var(--amber);
  font-style: italic;
  text-align: left;
  color: var(--text-soft);
}
.card-prose blockquote p { margin: 0; }
.card-prose img {
  max-width: 100%;
  max-height: 26rem;
  width: auto;
  height: auto;
  margin: 1.2rem auto;
  display: block;
  border: 1px solid var(--line);
}
/* Several pictures in one line become a row of them rather than a column. */
.card-prose p:has(img + img) {
  display: flex;
  flex-wrap: wrap;
  gap: 0.6rem;
  justify-content: center;
}
.card-prose p:has(img + img) img { margin: 0; max-height: 14rem; }
/* [baner]: the width of the text. [portret]: the avatar's shape, cropped to it
   from the middle like the avatar, as a column of its own on the left with the
   whole section's prose in a column beside it — wherever in the text the tag
   was put. One per section: a second would sit on top of the first. */
.card-prose img.baner { width: 100%; object-fit: cover; }
.card-prose:has(img.portret) {
  position: relative;
  min-height: 320px;
  padding-left: calc(200px + 1.5rem);
}
.card-prose img.portret {
  position: absolute;
  top: 0;
  left: 0;
  width: 200px;
  height: 320px;
  max-height: none;
  object-fit: cover;
  margin: 0;
}
@media (max-width: 46rem) {
  .card-prose:has(img.portret) { min-height: 0; padding-left: 0; }
  .card-prose img.portret { position: static; margin: 1.2rem auto; }
}
.card-prose::after { content: ''; display: block; clear: both; }
.card-prose .galeria img { margin: 0; max-height: none; }

/* Waiting on the staff, or refused with a reason. */
.card-draft {
  padding: 0.8rem 1rem;
  background: var(--panel);
  border: 1px solid var(--line);
  border-left: 3px solid var(--amber);
  display: grid;
  gap: 0.4rem;
}
.card-draft.is-refused { border-left-color: var(--danger); }
.card-draft-label {
  font-size: var(--label);
  letter-spacing: 0.14em;
  text-transform: uppercase;
  color: var(--amber-bright);
}
.card-draft.is-refused .card-draft-label { color: var(--danger); }
/* Your own unsent draft. Quiet on purpose: it is a note to yourself, not a
   state anybody else is waiting on. */
.card-draft.is-mine { border-left-color: var(--line-bright); }
.card-draft.is-mine .card-draft-label { color: var(--ink-dim); }
.card-draft p { margin: 0; }
.card-draft-actions { display: flex; gap: 0.5rem; }

.card-form { display: grid; gap: 0.9rem; }
.card-form label { display: grid; gap: 0.25rem; }
.card-form textarea { font-family: var(--font-mono); font-size: 13px; line-height: 1.6; resize: vertical; }
.card-form-facts { display: grid; gap: 0.75rem; grid-template-columns: repeat(auto-fit, minmax(min(13rem, 100%), 1fr)); }
.card-form-actions { display: flex; gap: 0.6rem; align-items: center; }
/* "Szkic zapisany", beside the buttons — the same reassurance the composer
   gives, in the same words. */
.card-form-status { font-size: 0.75rem; color: var(--ink-dim); }

/* Bringing a card over from the old board. Folded shut: it matters once, on
   the day somebody moves across, and never again after that. */
.card-import { border: 1px solid var(--line); border-radius: 4px; padding: 0.7rem 0.9rem; }
.card-import summary { cursor: pointer; font-family: var(--font-title); font-size: var(--label-lg); letter-spacing: 0.14em; text-transform: uppercase; }
.card-import[open] summary { margin-bottom: 0.6rem; }
.card-import textarea { width: 100%; margin: 0.5rem 0; }
.card-import-actions { display: flex; gap: 0.8rem; align-items: center; flex-wrap: wrap; }
/* A checkbox and its words, on one line — .card-form label stacks them. */
.card-import-over { display: flex !important; gap: 0.4rem; align-items: center; font-size: 0.8rem; color: var(--ink-dim); }
.card-import-report { display: grid; gap: 0.5rem; margin-top: 0.7rem; font-size: 0.82rem; }
.card-import-report p { margin: 0; }
.card-import-done { color: var(--amber-bright); }
/* What did not fit anywhere, kept as typed so it can be copied out by hand. */
.card-import-left { display: grid; gap: 0.15rem; padding-left: 0.7rem; border-left: 2px solid var(--line-bright); }
.card-import-left strong { font-size: var(--label-lg); letter-spacing: 0.08em; text-transform: uppercase; color: var(--ink-dim); }
.card-import-left span { white-space: pre-wrap; }

/* One box per question. The question itself sits under its own label, because
   a card is filled in once and the person filling it in has never seen it. */
/* One tab's worth of the form, folded away while another tab is on show — see
   showTab in sheet.js. Spelled out rather than left to the browser's own
   [hidden] rule, because that one loses to any author `display` a pane or its
   wrapper might grow later, and a pane that fails to hide overlaps the tab you
   actually asked for. */
.card-write { display: grid; gap: 0.9rem; }
.card-tab[hidden], .card-write[hidden] { display: none; }

.card-fields { display: grid; gap: 1.4rem; }
.card-field { display: grid; gap: 0.3rem; }
.card-field-label {
  font-family: var(--font-title);
  font-size: var(--label-lg);
  letter-spacing: 0.14em;
  text-transform: uppercase;
  color: var(--text);
}
.card-field-hint { color: var(--ink-dim); font-size: 0.78rem; line-height: 1.45; }
.card-field textarea { font-family: var(--font-prose); font-size: 0.95rem; line-height: 1.6; resize: vertical; }

/* Wygląd, Historia — the two halves of Szczegóły, in the form as on the card. */
.card-fields-group {
  margin: 1.6rem 0 0;
  padding-bottom: 0.5rem;
  border-bottom: 1px solid var(--line-bright);
  font-family: var(--font-title);
  font-size: 1rem;
  letter-spacing: 0.18em;
  text-transform: uppercase;
  color: var(--amber-bright);
}
.card-fields-group:first-child { margin-top: 0; }

/* The groups have no heading where the card is *read* — only in the form, above.
   See sectionsFor in sheet.js for why. */

/* The line that opens a history. Centred, quiet, and not labelled: it is an
   epigraph, and putting "Cytat" over it would say the quiet part out loud. */
.card-epigraph {
  margin: 0 auto 2.5rem;
  max-width: 54ch;
  border: none;
  padding: 0;
  font-family: var(--font-prose);
  font-size: 0.95rem;
  font-style: italic;
  letter-spacing: 0.06em;
  text-align: center;
  text-transform: uppercase;
  color: var(--text-soft);
}
.card-epigraph p { margin: 0; }

/* --------------------------------------------------- what you owe a reply */

/* One block per character, because that is how a person holds this: not "which
   threads exist" but "what is waiting on Maya, and what on Sophie". */
.my-threads { display: grid; gap: 1.6rem; margin-top: 1.2rem; }

/* Each character is a card laid on the forest, not type floating over it. */
.my-character {
  padding: 1.1rem 1.2rem 1.2rem;
  background: color-mix(in srgb, var(--fora-panel) 78%, transparent);
  border: 1px solid var(--line);
  backdrop-filter: blur(6px);
  -webkit-backdrop-filter: blur(6px);
  box-shadow: 0 10px 30px rgba(0, 0, 0, 0.25);
}

/* How much is standing on you, across every character, above the cards. */
.my-summary {
  display: flex; align-items: baseline; gap: 0.7rem;
  margin-top: 1.2rem; padding: 0.8rem 1.2rem;
  background: color-mix(in srgb, var(--fora-panel) 70%, transparent);
  border: 1px solid var(--line);
  border-left: 4px solid var(--line-bright);
  backdrop-filter: blur(6px);
  -webkit-backdrop-filter: blur(6px);
}
.my-summary.is-owed { border-left-color: var(--amber); }
.my-summary-n { font-family: var(--font-title); font-size: 2rem; line-height: 1; color: var(--ink-dim); }
.my-summary.is-owed .my-summary-n { color: var(--amber-bright); }
.my-summary-text { font-family: var(--font-title); letter-spacing: 0.12em; text-transform: uppercase; font-size: 0.85rem; }

/* The banner is a wall poster: it wants the panel under it to start where it
   starts, not inside the reading measure. */
.my-threads-page .banner { margin-bottom: 0; }

/* The same rule the rules pages use under a section heading: a hairline across
   the column with an amber segment sitting on its left end. Written twice
   rather than shared, because the portal's copy is scoped inside .page-body and
   pulling it out from under a broader "h2, h3 { border: none }" would cost more
   than the six lines it saves. */
.my-character-head {
  position: relative;
  display: flex;
  align-items: center;
  gap: 0.9rem;
  padding-bottom: 0.7rem;
  border-bottom: 1px solid var(--line-bright);
  margin-bottom: 0.9rem;
}
.my-character-head::after {
  content: '';
  position: absolute;
  left: 0;
  bottom: -1px;
  width: 3.5rem;
  height: 2px;
  background: var(--amber);
}
.my-face { width: 46px; height: 60px; overflow: hidden; flex: none; background: var(--panel); }
.my-face .face { width: 100%; height: 100%; object-fit: cover; display: block; aspect-ratio: auto; }
.my-face .portrait-initials { font-size: 0.8rem; letter-spacing: 0; }

.my-character-head > div { display: grid; gap: 0.15rem; }
.my-name {
  font-family: var(--font-title);
  font-size: 1.05rem;
  font-weight: 600;
  letter-spacing: 0.16em;
  text-transform: uppercase;
  color: var(--text);
  text-decoration: none;
}
.my-name:hover { color: var(--amber-bright); }
.my-count { font-size: 0.78rem; color: var(--ink-dim); }

.my-list { list-style: none; margin: 0; padding: 0; display: grid; gap: 0.4rem; }

.my-row {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 1.5rem;
  padding: 0.7rem 0.9rem;
  background: var(--panel);
  border: 1px solid var(--line);
  /* The bar says whose move it is before you have read a word of the row. */
  border-left: 3px solid transparent;
  color: inherit;
  text-decoration: none;
}
.my-row:hover { border-color: var(--accent); }
/* Your move: an amber bar, a warm wash off it, and a filled label. Waiting
   rows step back so the ones that need you are what the eye lands on. */
.my-row.is-yours {
  border-left: 4px solid var(--amber);
  background: linear-gradient(90deg, color-mix(in srgb, var(--amber) 16%, var(--panel)), var(--panel) 60%);
}
.my-row:not(.is-yours):not(.is-shut) { opacity: 0.78; }
.my-row:not(.is-yours):hover { opacity: 1; }

.my-row-main { display: grid; gap: 3px; min-width: 0; }

/* A draft is a row with its delete beside it rather than inside the link. */
.my-draft { display: flex; align-items: center; gap: 0.6rem; }
.my-draft .my-row { flex: 1; min-width: 0; }

/* Set exactly as a thread title is set on the board — .board .temat-title. It
   is the same object seen from a different angle, and a bold sans line here
   made this page look like a different site's list of the same threads. */
.my-title {
  font-family: var(--font-title);
  font-size: 17px;
  letter-spacing: 2px;
  text-transform: uppercase;
}
.my-meta { font-size: 12px; color: var(--fora-desc); }
.my-people { font-size: 12px; color: var(--ink-dim); }
.my-people-label { text-transform: uppercase; letter-spacing: 0.1em; font-size: 10px; }
.my-people-now { color: var(--amber-bright); font-weight: 700; }
/* The whole row is a link; underlining every line of it on hover was noise. */
.my-row:hover, .my-row:hover * { text-decoration: none; }

.my-turn { display: grid; gap: 0.1rem; text-align: right; flex: none; }
.my-turn-label {
  font-size: var(--label);
  letter-spacing: 0.12em;
  text-transform: uppercase;
  color: var(--ink-dim);
}
.my-row.is-yours .my-turn-label {
  justify-self: end;
  padding: 0.15rem 0.55rem;
  color: var(--bg, #111);
  background: var(--amber-bright);
  font-weight: 700;
}
.my-turn-when { font-size: 0.85rem; }
.my-turn-who { font-size: 0.72rem; color: var(--ink-dim); }

@media (max-width: 34rem) {
  .my-row { flex-direction: column; align-items: flex-start; gap: 0.5rem; }
  .my-turn { text-align: left; }
}

/* ----------------------------------------------------------------- threads */

/* The zakładka toggle. Quiet until it is on, because a thread you have not
   bookmarked is every thread, and a page full of lit stars says nothing. */
.scena-mark {
  /* Sized to sit level with the record shelf beside it — same padding, same
     border, same corner, so the row reads as two of one thing rather than as
     two things that happen to be near each other. */
  display: inline-flex;
  align-items: center;
  gap: 0.4rem;
  padding: 0.45rem 0.8rem;
  font-family: var(--font-title);
  font-size: var(--label-sm);
  letter-spacing: 0.1em;
  text-transform: uppercase;
  color: var(--text-muted);
  background: var(--bg-surface);
  border: 1px solid var(--line-control);
  border-radius: var(--radius);
  cursor: pointer;
  white-space: nowrap;
}
.scena-mark:hover { color: var(--amber-bright); border-color: var(--amber); }
.scena-mark.is-on { color: var(--amber-bright); border-color: var(--amber); }

/* Zakładki has no portrait to sit beside, so its heading closes the gap the
   .my-face would have filled. */
.my-character.is-bookmarks .my-character-head { gap: 0; }
.my-row.is-shut .my-title { color: var(--ink-dim); }

/* ------------------------------------------------------------------- posts */

.thread { display: grid; gap: 1rem; margin: 0 0 1.5rem; }

/* The speaker's block on top, exactly as tall as the portrait in it, and the
   prose underneath. The two slots and the tags are a way of looking at the
   inventory the server keeps — not a second copy of it. */
/* The thin bar is sticky, so anything scrolled to by its own id has to stop
   clear of it — otherwise a post's own head arrives underneath the trail. */
.post { background: var(--panel); border: 1px solid var(--line); scroll-margin-top: 5rem; }
/* „Start epizodu": an amber strip across the top of the post where a
   character's episode begins — the opening bracket to the closing end card. */
.post-opening {
  margin: 0;
  padding: 0.45rem 1rem;
  text-align: center;
  font-family: var(--font-title);
  font-size: 0.72rem;
  letter-spacing: 0.3em;
  text-transform: uppercase;
  color: var(--amber);
  border-top: 2px solid var(--amber);
  border-bottom: 1px solid var(--line);
}
/* Zamknij gry: the end card at the bottom of a thread's episode. Centred and
   quiet, between two amber rules, so it reads as the board speaking rather
   than as one more post. */
.post.is-closing {
  text-align: center;
  padding: 2.2rem 1.5rem 2.4rem;
  background: var(--bg-surface, var(--panel));
  border: none;
  border-top: 2px solid var(--amber);
  border-bottom: 2px solid var(--amber);
}
.closing-label {
  margin: 0 0 0.9rem;
  font-family: var(--font-title);
  font-size: 0.78rem;
  letter-spacing: 0.32em;
  text-transform: uppercase;
  color: var(--amber);
}
.closing-label::before, .closing-label::after { content: '—'; margin: 0 0.8em; opacity: 0.6; }
.post.is-closing .closing-body {
  max-width: 34rem;
  margin: 0 auto;
  text-align: center;
  font-style: italic;
  color: var(--text-soft);
}
.post.is-closing .closing-body a { font-style: normal; font-weight: 600; letter-spacing: 0.04em; }

.post-top {
  display: grid;
  grid-template-columns: 220px minmax(0, 1fr);
  gap: 1.4rem;
  /* As tall as the portrait beside it, plus room for the date under the name —
     and a floor rather than a fixed height, because a GM post carrying four
     NPC cards under it needs the room and a post carrying none must not
     change at all. */
  min-height: 372px;
}
.post-face { display: block; }
.post-face .face { width: 220px; height: 352px; object-fit: cover; display: block; }

.post-ident {
  display: flex;
  flex-direction: column;
  gap: 0.75rem;
  min-width: 0;
  padding: 1.15rem 1.3rem 1.15rem 0;
}
.post-said { display: flex; align-items: baseline; justify-content: space-between; gap: 1rem; }
.post-when { font-size: 0.8rem; color: var(--ink-dim); }
.post-who {
  font-family: var(--font-title);
  font-size: 1.25rem;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: var(--text);
}
.post-who:hover { color: var(--amber-bright); text-decoration: none; }

/* --------------------------------------------------- a name nobody is using

   A character whose player is away reads in a different colour wherever her
   name is drawn — the byline, the spis, her own masthead (049). One rule for
   all three, so the board cannot end up saying it two ways.

   Teal is the colour this design already uses for "belongs to the game rather
   than to a person standing here" — the NPC and persona chips, an NPC's
   transmission on the radio. Somebody on a przerwa is on loan to that state and
   comes back out of it; zamrożona is the same fact with no date, so it is the
   same colour dimmed rather than a third one nobody would learn. Never rust:
   being away is not an error, and half the board goes on holiday in August. */
.is-away.is-przerwa { color: var(--teal-ink); }
.is-away.is-zamrozona { color: var(--teal-faint); }
/* The link colours above are more specific than a bare class, so the hover has
   to be claimed back or a byline goes amber and loses the fact on hover. */
.post-who.is-away:hover { color: var(--teal-ink); }

/* Slots across the block, and what they carry underneath them.

   They stood in a 13rem column with the tags beside them, which gave the two
   boxes less width than the words in them want — „Pistolet Glock 19” wrapped,
   and the tags next to it were a scrolling sliver. Both things want width more
   than they want to be side by side, so they take turns at it: the weapons
   along the top, everything carried under them. Which is what the phone has
   done with this block all along, a rule lower down. */
.post-kit {
  flex: 1 1 auto;
  min-height: 0;
  display: grid;
  gap: 0.75rem;
  align-content: start;
}
/* One under the other, each as wide as the block — which is the point of the
   rule above. Side by side they were 253px on a 1800px screen against 208px in
   the old column, which is not enough more to be worth the name wrapping. */
.post-slots { display: grid; gap: 0.5rem; align-content: start; }
.slot {
  display: grid;
  gap: 2px;
  padding: 0.6rem 0.75rem;
  background: var(--bg-panel);
  border-left: 2px solid var(--amber);
  font-size: 0.9rem;
}
.slot.empty { border-left-color: var(--line-bright); color: var(--ink-dim); }
/* 0.56rem was under 9px on a default root — small enough that "BROŃ BIAŁA"
   read as texture rather than as a word. A label can be quiet without being
   unreadable, so the tracking keeps it quiet and the size makes it a word. */
.slot-label {
  font-size: var(--label);
  letter-spacing: 0.16em;
  text-transform: uppercase;
  color: var(--ink-dim);
}
.slot-item { font-family: var(--font-title); font-size: 1rem; letter-spacing: 0.04em; }
.slot-note { font-size: 0.7rem; color: var(--ink-dim); }

/* The same two boxes on the card and on Ekwipunek, where they stand on their
   own rather than under a name in a block — so they lie side by side instead
   of stacking into a column half the page wide. */
.karta-bron .post-slots, .kit-hands {
  grid-template-columns: repeat(auto-fit, minmax(min(12rem, 100%), 1fr));
  margin-bottom: 1rem;
}
.karta-bron { margin-bottom: 1.2rem; }
.karta-bron h3 { margin: 0 0 0.5rem; }
/* The picker under the name it is about, not beside it: a select the width of
   the box reads as part of the slot rather than as a control that wandered in. */
.slot select { margin-top: 0.35rem; width: 100%; font-size: 0.85rem; }

/* The GM dressing somebody, folded away on their own card. Quiet on purpose:
   it is the rarest thing on the page and sits under everything it changes. */
.karta-daj { margin-top: 1.2rem; border-top: 1px solid var(--line); padding-top: 0.6rem; }
.karta-daj > summary { cursor: pointer; font-size: 0.85rem; color: var(--accent); text-transform: uppercase; letter-spacing: 0.04em; }
.karta-daj p { font-size: 0.8rem; }

/* --------------------------------------------- the cast inside a GM post */

/* Sideways, because a scene can have five people in it and five stacked
   blocks would bury the writing under its own scaffolding. */
.post-voices {
  display: flex;
  gap: 0.6rem;
  overflow-x: auto;
  padding-bottom: 0.35rem;
  border-top: 1px solid var(--line);
  padding-top: 0.7rem;
}
.voice-card {
  flex: 0 0 15rem;
  display: grid;
  gap: 0.4rem;
  align-content: start;
  padding: 0.6rem;
  background: var(--bg-panel);
  border: 1px solid var(--line);
}
.voice-head { display: flex; gap: 0.5rem; align-items: center; color: inherit; text-decoration: none; min-width: 0; }
.voice-head:hover .voice-name { color: var(--amber-bright); }
.voice-face { width: 42px; height: 42px; object-fit: cover; flex: none; border: 1px solid var(--line); }
.voice-face.portrait-blank { display: grid; place-items: center; font-size: 0.8rem; }
.voice-name {
  font-family: var(--font-title);
  font-size: 0.9rem;
  letter-spacing: 0.06em;
  text-transform: uppercase;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
/* The kit scrolls inside the card rather than making the card grow: the
   strip is a glance, and one NPC with twenty things must not set the
   height of everybody else standing beside them. */
.voice-tags { display: grid; gap: 0.4rem; max-height: 7rem; overflow-y: auto; }
.voice-card .slot { padding: 0.35rem 0.5rem; font-size: 0.8rem; }
.voice-card .slot-item { font-size: 0.85rem; }
.voice-card .tag { font-size: 0.7rem; padding: 2px 6px; }

/* Under the slots, so it wraps across the block instead of scrolling inside
   a column too narrow to read one item's name in. */
.post-tags { display: grid; gap: 0.5rem; align-content: start; }
.tag-row { display: flex; flex-wrap: wrap; gap: 4px; }
.tag {
  font-size: 0.78rem;
  padding: 3px 9px;
  border: 1px solid var(--line-bright);
  color: var(--text-soft);
}
.tag.health { border-color: var(--rust); color: var(--danger); }
.tag.health.ok { border-color: var(--green); color: var(--green-ink); }
.tag.empty { border-style: dashed; color: var(--ink-dim); }

.post-main { min-width: 0; padding: 1.15rem 1.3rem; }
/* The strip along the bottom: where in the game's own time this post sits. */
.post-foot {
  margin-top: auto;
  display: flex;
  gap: 0.6rem;
  align-items: center;
  flex-wrap: wrap;
  padding-top: 0.5rem;
  border-top: 1px solid var(--line);
  font-size: 0.72rem;
}
.stamp {
  letter-spacing: 0.14em;
  text-transform: uppercase;
  color: var(--ink-dim);
}
/* A post's footer sets its stamps as one line, so they need something between
   them. A .stamp-row draws each one in its own box and does not: the dot ended
   up *inside* the second box, which is why a karta read "· 22 LAT". */
.stamp + .stamp::before { content: '· '; }
.stamp-row .stamp + .stamp::before { content: none; }
.post-tools { margin-left: auto; display: flex; gap: 0.75rem; align-items: baseline; font-size: 0.78rem; }
.post-tools .link.danger { color: var(--danger); }
/* Long lines stay hard to read however wide the column gets. */
.post-body { max-width: 68ch; }
/* A retrospection: the face sits in the prose and the lines wrap around it. */
.post.is-retro .post-face { float: left; margin: 0 1.3rem 0.75rem 0; }
.post.is-retro .post-body { max-width: calc(68ch + 220px + 1.3rem); }
.post.is-retro .post-foot { clear: both; margin-top: 1rem; }
.post-actions { margin-top: 0.5rem; font-size: 0.85rem; }

.post-body p { margin: 0 0 0.9rem; }
.post-body p:last-child { margin-bottom: 0; }
.post-body blockquote {
  margin: 0 0 0.9rem;
  padding: 0.5rem 0.9rem;
  border-left: 3px solid var(--accent);
  background: var(--panel-2);
}
.post-body cite { display: block; font-size: 0.8em; color: var(--ink-dim); font-style: normal; }
/* Width was always capped; height was not, so one tall picture could push the
   next post a screen and a half down the page. Capped at 70vh — tall enough
   that a portrait still reads as a picture rather than a thumbnail, short
   enough that the post it belongs to is still on screen beside it. `width:
   auto` with `object-fit: contain` so a constrained image shrinks rather than
   stretches: nothing is ever cropped. */
.post-body img {
  max-width: 100%;
  max-height: 70vh;
  width: auto;
  height: auto;
  object-fit: contain;
  border-radius: var(--radius);
}
.post-body .akcja { border-bottom: 1px dashed var(--accent); cursor: help; }
.post-body .spoiler { border: 1px solid var(--line); border-radius: var(--radius); padding: 0.5rem 0.75rem; }
.post-body .spoiler summary { cursor: pointer; color: var(--ink-dim); }
.post-body .center { text-align: center; }

/* ---------------------------------------------------------------- composer */

.composer {
  position: sticky;
  bottom: 0;
  background: var(--panel);
  border: 1px solid var(--line);
  border-radius: var(--radius);
  padding: 0.75rem;
  display: grid;
  gap: 0.5rem;
}
.composer-head { display: flex; gap: 0.75rem; align-items: center; flex-wrap: wrap; font-size: 0.8rem; color: var(--ink-dim); }
.composer-head .draft-status { margin-left: auto; }

.composer-toolbar { display: flex; gap: 0.25rem; flex-wrap: wrap; }
.tool { padding: 0.3rem 0.55rem; font-size: 0.9rem; line-height: 1; min-height: 2.2rem; }

.composer-ask { display: flex; gap: 0.35rem; }
.composer-ask input { flex: 1; }

/* Side by side where there is room; stacked on a phone, where the preview
   would otherwise steal half the writing surface. */
.composer-panes { display: grid; gap: 0.5rem; }
@media (min-width: 60rem) { .composer-panes { grid-template-columns: 1fr 1fr; } }

.composer-text { resize: vertical; min-height: 12rem; font-family: inherit; line-height: 1.6; }
.composer-preview {
  min-height: 6rem;
  padding: 0.5rem 0.75rem;
  background: var(--panel-2);
  border: 1px dashed var(--line);
  border-radius: var(--radius);
  overflow-wrap: anywhere;
}
.composer-preview p { margin: 0 0 0.9rem; }
.composer-preview blockquote { margin: 0 0 0.9rem; padding: 0.4rem 0.8rem; border-left: 3px solid var(--accent); }
.composer-preview img { max-width: 100%; height: auto; }

.composer-actions { display: flex; gap: 0.5rem; align-items: center; }

/* --------------------------------------------------------------- rolls */

.roll {
  display: flex;
  gap: 0.6rem;
  align-items: baseline;
  flex-wrap: wrap;
  margin-top: 0.75rem;
  padding: 0.5rem 0.75rem;
  border-left: 3px solid var(--line);
  background: var(--panel-2);
  border-radius: var(--radius);
  font-size: 0.9rem;
}
.roll.pending { border-left-color: var(--ink-dim); font-style: italic; }
.roll.passed  { border-left-color: var(--accent); }
.roll.failed  { border-left-color: var(--danger); }
/* A resolved roll wears the downtime roll's box: a full border and a wash of
   its result's colour off the left edge (see .summary-roll for the tones). */
.roll[class*=' r'] {
  border: 1px solid var(--line-bright); border-left: 4px solid var(--tone);
  border-radius: 0;
  background: linear-gradient(90deg, color-mix(in srgb, var(--tone) 22%, transparent), transparent 65%);
}
.roll.r6, .roll.r0 { background: linear-gradient(90deg, color-mix(in srgb, var(--tone) 34%, transparent), transparent 70%); }
.roll .roll-outcome { color: color-mix(in srgb, var(--tone, var(--ink)) 55%, var(--ink)); }
.roll-stat { font-weight: 700; }
.roll-question { color: var(--ink-dim); }
.roll-working { font-family: ui-monospace, Consolas, monospace; color: var(--ink-dim); }

.roll-picker { display: grid; gap: 0.4rem; }
.roll-row, .act-row { display: flex; gap: 0.5rem; align-items: center; }
.roll-row > span, .act-row > span { min-width: 4.5rem; font-size: 0.8rem; color: var(--ink-dim); }
.roll-fields { display: flex; gap: 0.4rem; flex-wrap: wrap; }
.roll-fields input { flex: 1; min-width: 12rem; }
.tool.on, button.on { border-color: var(--accent); color: var(--accent); }

/* What you spent and where you go next — the plain controls under the box.
   Nothing here needs a text selection, which is the whole point: on a phone
   the native selection handles win every fight. */
/* Przekaźnik or krótkofalówka — see the picker in composer.js. It appears the
   moment a [radio] is typed and only in a room with a mast, so it has to read
   as an answer the post is waiting for rather than as one more setting: a
   framed strip directly under the box, with the two reaches spelled out. */
.composer-device {
  display: flex;
  flex-wrap: wrap;
  align-items: baseline;
  gap: 0.4rem 1rem;
  margin-top: 0.5rem;
  padding: 0.5rem 0.7rem;
  border: 1px solid var(--line-bright);
  border-left: 3px solid var(--amber);
  border-radius: var(--radius);
  background: color-mix(in srgb, var(--amber) 6%, transparent);
}
.composer-device-head {
  font-size: var(--label-lg);
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: var(--ink-dim);
}
.composer-device-pick {
  display: flex;
  align-items: baseline;
  gap: 0.35rem;
  cursor: pointer;
}
.composer-device-pick small { color: var(--ink-dim); }
.composer-device-name { font-weight: 600; }

/* Everything a post can declare: a row of buttons, one panel at a time, and
   under the box the list of what this post is carrying. See composer.js. */
.composer-tools { display: grid; gap: 0.5rem; border-top: 1px solid var(--line); padding-top: 0.5rem; }
.composer-tool-buttons { display: flex; flex-wrap: wrap; gap: 0.4rem; }
.composer-tool-panel { display: grid; gap: 0.5rem; }
.composer-attached { display: grid; gap: 0.25rem; }
.composer-attached > :empty { display: none; }

.post-actions { display: grid; gap: 0.4rem; }
.act-row { flex-wrap: wrap; }
.act-item { flex: 1 1 11rem; min-width: 0; }
.act-verb, .act-whom { flex: none; }
.act-qty { width: 4.5rem; flex: none; }

.act-pending { list-style: none; margin: 0; padding: 0; display: grid; gap: 0.25rem; }
.act-chip {
  display: flex;
  gap: 0.5rem;
  align-items: baseline;
  justify-content: space-between;
  padding: 0.25rem 0.5rem;
  background: var(--panel-2);
  border-left: 2px solid var(--accent);
  font-size: 0.85rem;
}
.act-chip button { line-height: 1; }

/* An action tied to a passage rather than only to the post. The mark is small
   on purpose: anchoring is the enhancement, not the achievement. */
.act-anchored {
  margin-left: auto;
  font-size: var(--label);
  text-transform: uppercase;
  letter-spacing: 0.06em;
  color: var(--ink-dim);
}

/* --------------------------------------------------------------- queue */

.queue { display: grid; gap: 1rem; margin-top: 1rem; }
.queue-item {
  background: var(--panel);
  border: 1px solid var(--line);
  border-radius: var(--radius);
  padding: 1rem;
  display: grid;
  gap: 0.5rem;
}
.queue-item header { display: flex; gap: 0.75rem; align-items: baseline; flex-wrap: wrap; }
.queue-item p { margin: 0; }
.queue-post {
  margin: 0;
  padding: 0.6rem 0.8rem;
  background: var(--panel-2);
  border-radius: var(--radius);
  white-space: pre-wrap;
  overflow-wrap: anywhere;
  font: inherit;
  max-height: 18rem;
  overflow-y: auto;
}
.queue-actions { display: flex; gap: 0.4rem; flex-wrap: wrap; }

/* ------------------------------------------------------------- actions */

.action-summary {
  list-style: none;
  margin: 0.75rem 0 0;
  padding: 0.5rem 0 0;
  border-top: 1px solid var(--line);
  display: grid;
  gap: 0.25rem;
  font-size: 0.85rem;
}
.action { display: flex; gap: 0.5rem; align-items: baseline; }
.action-kind {
  color: var(--ink-dim);
  font-size: var(--label-lg);
  text-transform: uppercase;
  letter-spacing: 0.04em;
  min-width: 7rem;
}
.action-undone { color: var(--ink-dim); font-style: italic; }
.action-place { color: var(--accent); }

/* The arrival wears exactly what the exit wears — it is the same fact — and
   sits above the words instead of under them. */
.action-summary.arrival { margin: 0 0 0.9rem; padding: 0 0 0.5rem; border-top: none; border-bottom: 1px solid var(--line); }
/* Two of them can stack — the thread they came from, and the walk itself — and
   they are one block of "before the words", so only the last one closes it. */
.action-summary.arrival:has(+ .action-summary.arrival) { margin-bottom: 0; padding-bottom: 0; border-bottom: none; }

/* Actions, the line for the noticeboard and an unannounced departure are three
   blocks that can all land under one post, and they read as one list: one rule
   between the words and all of them, not three. */
.action-summary + .action-summary { margin-top: 0.25rem; padding-top: 0; border-top: none; }

/* A line somebody chose to put on the board, said in the board's own colour so
   it does not read as another thing the system worked out. */
.action-said .action-text { color: var(--accent); }

/* Landing on a post after following a link: say which one, then get out of
   the way. */
.post.jumped { animation: jumped 2.6s ease-out; }
/* The fade is the 'get out of the way' half. Asked for less motion, the ring
   simply stays: the point is which post, and that half is not decoration. */
@media (prefers-reduced-motion: reduce) {
  .post.jumped { animation: none; box-shadow: inset 0 0 0 2px var(--accent); }
}
@keyframes jumped {
  from { box-shadow: inset 0 0 0 2px var(--accent); }
  to { box-shadow: inset 0 0 0 2px transparent; }
}
.action-room { color: var(--ink-dim); }
.action-arrow { color: var(--ink-dim); }

.queue-conditions { font-size: 0.9rem; }
/* Advisory, and marked as advisory: the GM is meant to overrule it freely. */
button.suggested { border-color: var(--accent); box-shadow: inset 0 0 0 1px var(--accent); }

/* ---------------------------------------------------- the character sheet */

.sheet-head { display: flex; gap: 1.25rem; align-items: flex-start; margin-bottom: 1.5rem; }
.sheet-head h1 { margin: 0 0 0.25rem; }
.avatar { width: 100px; height: 160px; object-fit: cover; border-radius: var(--radius); border: 1px solid var(--line); }

.view section { margin: 1.75rem 0; }
.view section h2 { font-size: 1.1rem; margin: 0 0 0.5rem; border-bottom: 1px solid var(--line); padding-bottom: 0.25rem; }
.view section h3 { font-size: 0.85rem; margin: 0.75rem 0 0.25rem; font-weight: normal; }

.stats { border-collapse: collapse; width: 100%; font-size: 0.9rem; }
.stats th, .stats td { text-align: left; padding: 0.3rem 0.5rem; border-bottom: 1px solid var(--line); }
.stats thead th { color: var(--ink-dim); font-size: var(--label-lg); text-transform: uppercase; letter-spacing: 0.04em; }
.stats tbody th { font-weight: normal; }

.items, .statuses, .supplies, .studio-list, .map-locations {
  list-style: none; margin: 0; padding: 0; display: grid; gap: 0.2rem; font-size: 0.9rem;
}
.statuses.past { margin-top: 0.5rem; }

.studio-list { gap: 0.35rem; margin-bottom: 0.5rem; }
.studio-list ul { list-style: none; margin: 0.15rem 0 0 1rem; padding: 0; font-size: 0.85rem; }

/* The forum tree is three ranks deep and was printing all three as one kind of
   line — every name the same size, every fact trailing off the end of it, each
   level a little smaller than the last until the threads were unreadable. So
   the ranks are drawn: a dział is a block with a rule over it, everything under
   it hangs off a vertical line, and the facts sit on their own line in mono
   where they read as labels rather than as more of the name. */
.studio-tree { gap: 1.1rem; }
.studio-tree > li {
  padding-top: 0.9rem;
  border-top: 1px solid var(--line);
}
.studio-tree > li > strong {
  font-family: var(--font-title);
  font-size: 1rem;
  letter-spacing: 0.1em;
  text-transform: uppercase;
  color: var(--text);
}
.studio-tree ul {
  margin: 0.5rem 0 0 0.35rem;
  padding-left: 1rem;
  border-left: 1px solid var(--line);
  display: grid;
  gap: 0.7rem;
  /* One step down, and no further: the old rule compounded at every level. */
  font-size: 0.92rem;
}
.studio-tree ul ul { font-size: 1em; gap: 0.35rem; }
.studio-tree ul strong { color: var(--text); }

/* Everything that is not the name: #id, kind, "ukryte". Under it, quiet. */
.studio-tree li > span.muted {
  display: block;
  margin-top: 0.1rem;
  font-family: var(--font-mono);
  font-size: 0.7rem;
  letter-spacing: 0.06em;
}
.studio-tree .studio-editor { margin: 0.35rem 0 0; }

/* ------------------------------------------------------------- archive */

.search-form { display: flex; gap: 0.5rem; margin: 1rem 0; }
.search-form input { flex: 1; }

.results { display: grid; gap: 0.5rem; }
.result {
  display: grid; gap: 0.2rem;
  padding: 0.6rem 0.8rem;
  background: var(--panel);
  border: 1px solid var(--line);
  border-radius: var(--radius);
  text-decoration: none; color: inherit;
}
.result:hover { border-color: var(--accent); }
.result-where { font-size: 0.75rem; color: var(--ink-dim); }
.result-snippet { font-size: 0.9rem; }

/* ----------------------------------------------------------------- map */

/* A placeholder backdrop until there is a real map image to drop in — the
   pins and their positions are already real. */
/* The Mapa screen: the same drawing, read at full width. No aspect-ratio of
   its own — the picture inside decides how tall the box is, so new artwork of
   any shape drops in without a number here having to be kept in step. The
   hatching shows through only while there is no artwork to show. */
.map {
  position: relative;
  background:
    repeating-linear-gradient(45deg, transparent 0 14px, rgba(200,161,90,.05) 14px 28px),
    var(--panel-2);
  border: 1px solid var(--line);
  border-radius: var(--radius);
  margin: 1rem 0;
  min-height: 12rem;
  /* A window, so the drawing can be bigger than it. */
  overflow: hidden;
  /* The drawing's own proportions, measured off the loaded picture by panOn.
     Until it arrives the box is whatever min-height says, so the page never
     waits on an image to have a layout. */
  aspect-ratio: var(--map-aspect, auto);
}
/* Out of flow, so growing the picture cannot grow the box it is read through.
   At rest it is the full width of that box and the two are the same size,
   which is exactly what it was before there was a zoom. */
.map .map-frame {
  position: absolute;
  left: 0;
  top: 0;
  width: calc(100% * var(--map-zoom, 1));
  transform: translate(var(--pan-x, 0px), var(--pan-y, 0px));
}
.map .map-art { width: 100%; height: auto; display: block; }
.map-detail { margin-top: 1rem; }

/* Closer, and back out again. Sat on the map rather than under it: it is the
   map you are looking at when you want them, and on a phone the bottom-right
   corner is where a thumb already is. */
.map-zoom {
  position: absolute;
  right: 0.6rem;
  bottom: 0.6rem;
  z-index: 5;
  display: flex;
  flex-direction: column;
  gap: 0.35rem;
}
.map-zoom-btn {
  width: 2.4rem;
  height: 2.4rem;
  padding: 0;
  font-size: 1.15rem;
  line-height: 1;
  color: var(--text);
  background: rgba(12, 14, 13, 0.72);
  border: 1px solid var(--line-bright);
}
.map-zoom-btn:hover { color: var(--amber-bright); border-color: var(--amber); }
html[data-theme='light'] .map-zoom-btn {
  background: rgba(255, 255, 255, 0.84);
  color: var(--text);
}

/* ------------------------------------------------- notifications & turns */

.inbox { display: grid; gap: 0.4rem; margin: 1rem 0; }
.inbox-item {
  display: grid; gap: 0.15rem;
  padding: 0.6rem 0.8rem;
  background: var(--panel);
  border: 1px solid var(--line);
  border-left: 3px solid var(--accent);
  border-radius: var(--radius);
  text-decoration: none; color: inherit;
}
.inbox-item.read { border-left-color: var(--line); opacity: 0.65; }
.inbox-kind { font-size: var(--label); text-transform: uppercase; letter-spacing: 0.05em; color: var(--ink-dim); }
.inbox-title { font-weight: 700; font-size: 0.95rem; }
.inbox-body { font-size: 0.85rem; color: var(--ink-dim); white-space: pre-line; }
.inbox-when { font-size: 0.7rem; color: var(--ink-dim); }

label.checkbox { display: flex; gap: 0.5rem; align-items: center; }
label.checkbox input { width: auto; }

.turns { display: flex; gap: 0.5rem; align-items: center; flex-wrap: wrap; margin: 0.75rem 0; font-size: 0.85rem; }
.turn { padding: 0.15rem 0.5rem; border: 1px solid var(--line); border-radius: 999px; color: var(--ink-dim); }
/* Marked, not enforced: it says whose turn it is and refuses nothing. */
.turn.current { border-color: var(--accent); color: var(--accent); font-weight: 700; }

/* ------------------------------------------------- the character card form */

.stat-grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(min(7rem, 100%), 1fr)); gap: 0.5rem; }
.stat-cell { gap: 0.2rem; }
.stat-cell input { width: 100%; }

/* Ustawienia. One form, three headings — the person, what they will not read,
   then how to reach them — and a save button at the bottom that means all of it.

   Full width, in columns: the page is a column of one-line inputs and two long
   textareas, and at 34rem the textareas were the size of a comment box for the
   one thing here somebody actually writes a paragraph into. The headings span,
   so the columns never split a section in half. */
.settings { gap: 1rem 1.75rem; grid-template-columns: repeat(auto-fit, minmax(min(22rem, 100%), 1fr)); align-items: start; }
.settings > h1,
.settings > h2,
.settings > p,
.settings > .settings-wide,
.settings > button[type="submit"] { grid-column: 1 / -1; }
.settings h2 {
  margin: 0.75rem 0 0;
  padding-top: 0.9rem;
  border-top: 1px solid var(--line);
  font-family: var(--font-title);
  font-size: 0.85rem;
  letter-spacing: 0.16em;
  text-transform: uppercase;
  color: var(--ink-dim);
}
.settings label small { line-height: 1.45; margin-bottom: 0.15rem; }
.settings textarea { font-family: var(--font-prose); line-height: 1.55; resize: vertical; }
.settings button[type="submit"] { justify-self: start; }

/* ------------------------------------------------------------ nieobecności */

/* One card, one block per character. Deliberately not the two-column grid the
   settings form above uses: a hiatus form is one thing being filled in about
   one person, and splitting its four boxes across columns would read as two
   unrelated halves. */
.settings-absences { display: grid; gap: 1rem; }
.settings-absences > h2 {
  margin: 0;
  font-family: var(--font-title);
  font-size: 0.85rem;
  letter-spacing: 0.16em;
  text-transform: uppercase;
  color: var(--ink-dim);
}
.settings-absences textarea { font-family: var(--font-prose); line-height: 1.55; resize: vertical; }

.absence {
  display: grid;
  gap: 0.6rem;
  padding: 0.9rem 1rem;
  background: var(--panel-2);
  border: 1px solid var(--line);
}
.absence.is-muted { opacity: 0.7; }
.absence-head { display: grid; gap: 0.2rem; }
.absence-name {
  font-family: var(--font-title);
  font-size: 1.05rem;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: var(--text);
  text-decoration: none;
}
.absence-name:hover { color: var(--accent); }
.absence-state { margin: 0; font-size: 0.85rem; }
.absence-state.is-away { color: var(--teal-ink); }
.absence-state.is-abandoning { color: var(--danger); }

.absence-form { display: grid; gap: 0.55rem; }
.absence-form label { display: grid; gap: 0.15rem; }
.absence-form label small { line-height: 1.45; }
.absence-form button { justify-self: start; }
.absence-form > p.muted { margin: 0; font-size: 0.82rem; line-height: 1.5; }
/* The two dates side by side: they are one answer with two ends, and stacked
   they read as two separate questions. */
.absence-dates { display: grid; gap: 0.5rem 1rem; grid-template-columns: repeat(auto-fit, minmax(min(12rem, 100%), 1fr)); }
.absence-actions { display: flex; gap: 0.4rem; }

/* Folded shut. Letting a character go is not a thing to have under the cursor
   while filling in a fortnight's holiday. */
.absence-abandon { border-top: 1px solid var(--line); padding-top: 0.6rem; }
.absence-abandon > summary { cursor: pointer; font-size: 0.85rem; color: var(--text-muted); }
.absence-abandon > summary:hover { color: var(--danger); }
.absence-abandon > .absence-form { margin-top: 0.6rem; }

.invite-code {
  display: inline-block;
  font-family: ui-monospace, Consolas, monospace;
  font-size: 1.1rem; letter-spacing: 0.08em;
  padding: 0.4rem 0.7rem;
  background: var(--panel-2); border: 1px dashed var(--accent);
  border-radius: var(--radius); color: var(--accent);
}

.carried { margin: 0.25rem 0 0.5rem; }
label small { font-weight: normal; }

/* ------------------------------------------------------------- the rules */



/* ------------------------------------------------------- the character list */

/* Spis postaci. One card per person on the board, on the same auto-fitting
   grid the kit and the relations use — they are the same kind of object, one
   card for one thing, and a third grid geometry would only be a third thing to
   keep in step. */
.dir-page { display: grid; gap: 0.9rem; }
.dir-head { display: grid; gap: 0.9rem; }
.dir-head .banner { margin-bottom: 0; }

.dir-tools { display: flex; gap: 0.6rem; flex-wrap: wrap; align-items: center; }
.dir-search { flex: 1 1 20rem; min-width: 0; }
.dir-sort { flex: none; }

/* The filter button and its panel of ticks: kind, state, card. */
.dir-filters { margin: 0.25rem 0; }
.dir-filter-button {
  display: inline-block; list-style: none; cursor: pointer;
  font-family: var(--font-ui); font-size: var(--label); letter-spacing: 0.09em; text-transform: uppercase;
  padding: 0.35rem 0.75rem; border: 1px solid var(--line-control); color: var(--text-muted);
}
.dir-filter-button::-webkit-details-marker { display: none; }
.dir-filters[open] .dir-filter-button, .dir-filter-button:hover { color: var(--text); border-color: var(--amber); }
.dir-filter-panel {
  display: flex; flex-wrap: wrap; gap: 0.75rem 2rem; margin-top: 0.5rem; padding: 0.75rem 1rem;
  border: 1px solid var(--line); background: var(--bg-surface);
}
.dir-filter-panel fieldset { border: 0; margin: 0; padding: 0; display: grid; gap: 0.25rem; }
.dir-filter-panel legend { font-size: var(--label); letter-spacing: 0.09em; text-transform: uppercase; color: var(--text-muted); margin-bottom: 0.25rem; }
.dir-filter-panel .row-actions { flex-basis: 100%; }
.dir-filter {
  font-family: var(--font-ui);
  font-size: var(--label);
  letter-spacing: 0.09em;
  text-transform: uppercase;
  padding: 0.35rem 0.75rem;
  background: none;
  border: 1px solid var(--line-control);
  color: var(--text-muted);
  cursor: pointer;
}
.dir-filter:hover { color: var(--text); border-color: var(--text-soft); }
.dir-filter.active { color: var(--on-accent); background: var(--amber); border-color: var(--amber); }

.dir-count {
  margin: 0;
  font-family: var(--font-mono);
  font-size: 0.75rem;
  color: var(--text-muted);
}

.dir-list { display: grid; gap: 0.75rem; grid-template-columns: repeat(auto-fit, minmax(min(21rem, 100%), 1fr)); }

/* The whole card is the link: there is one thing you want from a row here, and
   it is that person's card. */
.dir-card {
  display: flex;
  gap: 0.9rem;
  padding: 0.85rem 1rem;
  background: var(--panel);
  border: 1px solid var(--line);
  border-left: 3px solid var(--line-bright);
  text-decoration: none;
  color: inherit;
}
.dir-card:hover { border-left-color: var(--amber); background: var(--panel-2); }
.dir-card:hover .dir-name { color: var(--amber-bright); }

.dir-face {
  flex: none;
  width: 74px;
  height: 118px;
  overflow: hidden;
  background: var(--bg-panel);
}
.dir-face .face { width: 100%; height: 100%; object-fit: cover; display: block; aspect-ratio: auto; }

.dir-body { display: grid; gap: 0.3rem; align-content: start; min-width: 0; }
.dir-name {
  margin: 0;
  font-family: var(--font-title);
  font-size: 1.05rem;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: var(--text);
}
.dir-full { margin: 0; font-size: 0.82rem; color: var(--text-muted); }
.dir-role { margin: 0; font-size: 0.9rem; color: var(--text-soft); }

/* ------------------------------------------------------- spis wizerunków --

   Read down the left column, looking for one face, so the rows are narrow and
   stacked rather than a grid of cards: the answer is "that name is there" or
   "it is not", and a second column would only slow the scan. */
.faces .face-search { margin: 0 0 0.9rem; }
.faces .face-search input { width: 100%; max-width: 22rem; }

.face-list { display: grid; gap: 0.35rem; }

.face-row {
  display: flex;
  gap: 0.8rem;
  align-items: center;
  padding: 0.4rem 0.6rem;
  border: 1px solid var(--line);
  border-left: 3px solid var(--line-bright);
  background: var(--panel);
  text-decoration: none;
  color: inherit;
}
.face-row:hover { border-left-color: var(--amber); background: var(--panel-2); }
.face-row:hover .face-name { color: var(--amber-bright); }

.face-thumb {
  flex: none;
  width: 34px;
  height: 54px;
  overflow: hidden;
  background: var(--bg-panel);
}
.face-thumb .face { width: 100%; height: 100%; object-fit: cover; display: block; aspect-ratio: auto; }
.face-thumb .portrait-initials { font-size: 0.65rem; letter-spacing: 0; }

.face-body { flex: 1; display: flex; gap: 0.6rem; align-items: baseline; flex-wrap: wrap; min-width: 0; }
.face-name {
  font-family: var(--font-title);
  font-size: 0.95rem;
  letter-spacing: 0.06em;
  text-transform: uppercase;
  color: var(--text);
}
.face-as { font-size: 0.88rem; color: var(--text-soft); }
.face-who { font-size: 0.78rem; color: var(--text-muted); margin-left: auto; }

.dir-chips { display: flex; gap: 0.3rem; flex-wrap: wrap; margin-top: 0.15rem; }
.dir-chip {
  font-size: var(--label-sm);
  letter-spacing: 0.09em;
  text-transform: uppercase;
  border: 1px solid var(--line-bright);
  color: var(--text-soft);
  padding: 2px 6px;
  white-space: nowrap;
}
/* A player's own character is the ordinary case and stays unmarked; the ones
   the game holds are the ones worth telling apart at a glance. */
.dir-chip.type.drugoplanowa_mg,
.dir-chip.type.persona_mg { border-color: var(--teal); color: var(--teal-ink); }
.dir-chip.type.w_tle { border-color: var(--line); color: var(--text-muted); }
.dir-chip.pending { border-color: var(--rust); color: var(--danger); }
/* Away, and until when. Same teal as the name above it — see "a name nobody is
   using". Not uppercase-cropped: „przerwa do 2026-08-14" needs its date. */
.dir-chip.away { border-color: var(--teal); color: var(--teal-ink); }
.dir-chip.away.zamrozona { opacity: 0.72; }

.dir-foot {
  margin: 0.2rem 0 0;
  font-family: var(--font-mono);
  font-size: 0.7rem;
  color: var(--text-muted);
}

/* ---------------------------------------------------------- the kit screen */

/* Ekwipunek as a page: the same item cards the card's tab draws, in three
   labelled groups instead of two. The third one is the floor. */
.kit-page { display: grid; gap: 1.25rem; }
.kit-page-head { display: grid; gap: 0.5rem; }
.kit-page-head .banner { margin-bottom: 0; }
.kit-page-sub {
  margin: 0;
  font-family: var(--font-mono);
  font-size: var(--label-lg);
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: var(--text-muted);
}

.kit-group {
  padding: 1rem 1.1rem 1.2rem;
  background: var(--panel);
  border: 1px solid var(--line);
  border-left: 3px solid var(--line-bright);
  display: grid;
  gap: 0.75rem;
}
/* The floor is not yours. Amber says that before the note under it does. */
.kit-group.is-ground { border-left-color: var(--amber); }
.kit-group.is-ground.is-empty { border-left-color: var(--line); }

.kit-group-head { display: flex; align-items: baseline; justify-content: space-between; gap: 1rem; }
.kit-group-head h2 {
  margin: 0;
  font-family: var(--font-title);
  font-size: 1rem;
  letter-spacing: 0.16em;
  text-transform: uppercase;
  color: var(--text);
}
.kit-group-count {
  font-family: var(--font-mono);
  font-size: var(--label);
  letter-spacing: 0.1em;
  text-transform: uppercase;
  color: var(--text-muted);
}
.kit-group-sub {
  margin: 0;
  font-family: var(--font-title);
  font-size: var(--label);
  font-weight: 500;
  letter-spacing: 0.18em;
  text-transform: uppercase;
  color: var(--ink-dim);
}

/* ------------------------------------------------------------- the studio */

/* The title and the one switch that changes what the whole screen is for. */
.studio-head {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: 1rem;
  flex-wrap: wrap;
}
.studio-edit {
  display: inline-flex;
  align-items: center;
  gap: 0.45rem;
  font-size: var(--label);
  letter-spacing: 0.1em;
  text-transform: uppercase;
  color: var(--ink-dim);
  cursor: pointer;
  user-select: none;
}
.studio-edit:hover { color: var(--text); }
.studio-edit-box { accent-color: var(--accent); cursor: pointer; }
.studio.is-editing .studio-edit { color: var(--accent); }

/* Reading, which is what most visits are. Everything that makes or destroys
   is marked where it is built — `studio-make` on the creator forms in form(),
   `studio-unmake` on anything with a delete behind it — so the switch is one
   rule rather than a flag threaded through ten tabs.

   This hides controls; it does not withhold anything. The server refuses a
   player either way, and it refuses them identically with the switch on. */
.studio:not(.is-editing) .studio-make,
.studio:not(.is-editing) .studio-unmake { display: none; }

.studio-tabs {
  display: flex; flex-wrap: wrap; gap: 0.25rem;
  margin: 0.75rem 0 1rem;
  border-bottom: 1px solid var(--line);
}
.studio-tab-link {
  padding: 0.4rem 0.8rem;
  font-size: 0.85rem;
  color: var(--ink-dim);
  text-decoration: none;
  border: 1px solid transparent;
  border-bottom: none;
}
.studio-tab-link:hover { color: var(--text); }
/* The open tab joins the page: the line under it is the one gap in the rule. */
.studio-tab-link.is-current {
  color: var(--accent);
  border-color: var(--line);
  background: var(--panel);
  margin-bottom: -1px;
  border-bottom: 1px solid var(--panel);
}

.studio-tab { display: grid; gap: 1.25rem; }
/* Two columns where there is room: the picture of what exists on the left,
   the form that adds to it on the right. One column on a narrow screen. */
.studio-forms { display: grid; gap: 1rem; grid-template-columns: repeat(auto-fit, minmax(min(20rem, 100%), 1fr)); align-items: start; }
.studio-clock { font-size: 1.05rem; }
.studio-link { color: var(--accent); text-decoration: none; font-weight: 700; }
.studio-link:hover { text-decoration: underline; }

.studio-row { display: flex; gap: 0.6rem; align-items: flex-start; padding: 0.4rem 0; border-bottom: 1px solid var(--line); }
.studio-row > div { flex: 1; min-width: 0; }
.studio-row details { margin-top: 0.3rem; }
.studio-row summary { font-size: 0.8rem; cursor: pointer; }

.studio-thumb { width: 44px; height: 44px; object-fit: cover; border: 1px solid var(--line); background: var(--panel-2); flex: none; }
.studio-thumb.empty { display: block; }
.npc-faces { display: flex; gap: 0.3rem; flex: none; }

.row-actions { display: flex; gap: 0.4rem; align-items: center; margin-top: 0.4rem; flex-wrap: wrap; }
/* MG and admin, beside the buttons that need one of them. */
.studio-role { display: inline-flex; align-items: center; gap: 0.25rem; font-size: 0.85rem; }
.row-actions input[type="number"] { width: 5rem; }

/* A player's triggers, on their row in Gracze. Rust, because it is the one
   line on that row that is about somebody being hurt. */
.studio-triggers {
  margin: 0.3rem 0 0;
  padding-left: 0.6rem;
  border-left: 2px solid var(--rust);
  font-size: 0.85rem;
  color: var(--text-soft);
  white-space: pre-line;
}
.studio-triggers b { color: var(--danger); font-weight: 400; }

.studio-table-wrap { overflow-x: auto; }
.studio-table { border-collapse: collapse; width: 100%; font-size: 0.85rem; }
.studio-table th, .studio-table td { text-align: left; padding: 0.3rem 0.5rem; border-bottom: 1px solid var(--line); vertical-align: top; }
.studio-table thead th { color: var(--ink-dim); font-size: var(--label-lg); text-transform: uppercase; letter-spacing: 0.04em; }

/* ------------------------------------------------------- sending a picture */

.upload-field { display: grid; gap: 0.3rem; margin: 0.5rem 0; }
.upload-row { display: flex; gap: 0.4rem; align-items: center; flex-wrap: wrap; }
.upload-row input[type="text"] { flex: 1; min-width: 10rem; }
.upload-row input[type="file"] { font-size: 0.8rem; }
.upload-preview { max-width: 200px; max-height: 200px; border: 1px solid var(--line); background: var(--panel-2); }

/* ----------------------------------------------------- the GM's toolset */

.gm-tools {
  margin-bottom: 0.75rem;
  border: 1px solid var(--line);
  background: var(--panel);
}
.gm-tools > summary {
  padding: 0.5rem 0.8rem;
  cursor: pointer;
  font-size: 0.85rem;
  color: var(--accent);
  letter-spacing: 0.04em;
  text-transform: uppercase;
}
/* Two or three short panels, so the gap between them is a separation rather
   than a room. The rule under each is what says where one ends. */
.gm-tools-body { padding: 0 0.8rem 0.7rem; display: grid; gap: 0.7rem; }
.gm-tools-body > .gm-panel + .gm-panel { padding-top: 0.7rem; border-top: 1px solid var(--line); }

.gm-panel { display: grid; gap: 0.3rem; }
.gm-panel h3 { margin: 0; font-size: 0.85rem; color: var(--text); }
.gm-panel p { margin: 0; font-size: 0.8rem; }

.gm-grid { display: flex; gap: 0.4rem; flex-wrap: wrap; align-items: center; }
.gm-grid select, .gm-grid input { flex: 1; min-width: 7rem; }
.gm-grid .gm-qty { flex: none; width: 5rem; }

/* What is lying in this thread, and who here is carrying what. Read-only, so
   it is a list rather than a row of controls. */

.gm-turn-list { list-style: none; margin: 0; padding: 0; display: grid; gap: 0.2rem; font-size: 0.85rem; }
.gm-turn-list li { display: flex; justify-content: space-between; align-items: center; gap: 0.5rem; }
.gm-turn-box { width: 4rem; }
.gm-whose { color: var(--accent); font-weight: 700; }

.gm-status-list { list-style: none; margin: 0; padding: 0; display: grid; gap: 0.15rem; font-size: 0.85rem; }
.gm-status-list li { display: flex; gap: 0.35rem; align-items: baseline; flex-wrap: wrap; }

/* --------------------------------------------------------- an NPC's line */

/* A GM post is narration around dialogue, and the dialogue is the part people
   are reading for — so it is bigger than the prose around it rather than
   smaller. The face is still square and still not the 200×320 portrait: it is
   beside one sentence, not beside a whole post. */
.post-body .npc-line, .composer-preview .npc-line {
  display: flex;
  gap: 0.85rem;
  align-items: flex-start;
  margin: 1.2rem 0;
}
.post-body .npc-link, .composer-preview .npc-link { display: block; flex: none; line-height: 0; }
.post-body .npc-face, .composer-preview .npc-face {
  width: 88px; height: 88px; object-fit: cover; display: block;
  border: 1px solid color-mix(in srgb, var(--ink) 20%, transparent);
}
/* Frosted glass: a faint wash of the ink colour, no outline, and a small point
   at the top towards the face. The ink, not white, so it holds in both themes. */
.post-body .npc-said, .composer-preview .npc-said {
  position: relative; flex: 1; display: grid; gap: 0.3rem; min-width: 0;
  padding: 0.8rem 1.2rem;
  background: color-mix(in srgb, var(--ink) 6%, transparent);
  border-radius: 4px 16px 16px 16px;
  box-shadow: inset 0 0 0 1px color-mix(in srgb, var(--ink) 8%, transparent);
}
.post-body .npc-said::before, .composer-preview .npc-said::before {
  content: ''; position: absolute; left: -10px; top: 0;
  border-top: 12px solid color-mix(in srgb, var(--ink) 6%, transparent);
  border-left: 10px solid transparent;
}
.post-body .npc-who, .composer-preview .npc-who {
  font-size: var(--label-lg); text-transform: uppercase; letter-spacing: 0.05em;
  color: var(--ink); opacity: 0.7; font-weight: 600; text-decoration: none;
}
.post-body .npc-who:hover, .composer-preview .npc-who:hover { opacity: 1; text-decoration: underline; }
.post-body .npc-link:hover .npc-face, .composer-preview .npc-link:hover .npc-face { border-color: var(--ink); }
/* The quotation marks are typography, not content: they belong to the way a
   spoken line is drawn, and putting them in the stored BBCode would mean
   every line ever written carried a decision about punctuation. */
.post-body .npc-words, .composer-preview .npc-words { display: block; font-size: 1.1em; line-height: 1.55; color: var(--ink); text-align: left; }
@media (max-width: 560px) {
  .post-body .npc-face, .composer-preview .npc-face { width: 56px; height: 56px; }
  .post-body .npc-line, .composer-preview .npc-line { gap: 0.7rem; }
}
.post-body .npc-words::before, .post-body .npc-words::after,
.composer-preview .npc-words::before, .composer-preview .npc-words::after {
  font: 400 2em/0 Georgia, 'Times New Roman', serif; opacity: 0.55; vertical-align: -0.12em;
}
.post-body .npc-words::before, .composer-preview .npc-words::before { content: '\201C'; margin-right: 0.15rem; }
.post-body .npc-words::after, .composer-preview .npc-words::after { content: '\201D'; margin-left: 0.15rem; }

/* Wide enough to read a name without opening it. It used to cap at 12rem and
   then shrink to its content in a tight toolbar, so the closed control said
   "głos MG…" and nothing else — the one state it is in most of the time. */
.composer-toolbar .voice-picker { font-size: 0.8rem; min-width: 11rem; max-width: 16rem; }

/* The box that narrows the list. Sits against the picker as one control. */
.composer-toolbar .voice-filter {
  font-size: 0.8rem;
  width: 7rem;
  min-width: 0; /* an <input> refuses to shrink below its size attribute otherwise */
}

/* The rail's note that a character cannot write yet. */
.sb-pending {
  margin: 0.5rem 0.9rem;
  font-size: 0.75rem;
  color: var(--ink-dim);
  border-left: 2px solid var(--amber);
  padding-left: 0.5rem;
}
.sb-pending a { color: var(--accent); }

/* Where the picker would be, for somebody who has no character yet. */
.sb-new-character {
  display: block;
  margin: 0.5rem 0.9rem;
  padding: 0.35rem 0.6rem;
  font-size: 0.85rem;
  color: var(--accent);
  border: 1px dashed var(--line-bright);
  text-decoration: none;
  text-align: center;
}
.sb-new-character:hover { border-color: var(--accent); }

/* -------------------------------------------------- the rest of the queue */

.queue-page h2 { margin-top: 1.5rem; }
.queue-threshold { display: flex; gap: 0.5rem; align-items: center; margin: 1rem 0 0.5rem; font-size: 0.85rem; }
.queue-list { list-style: none; margin: 0; padding: 0; display: grid; gap: 0.3rem; font-size: 0.9rem; }
.queue-list li {
  padding: 0.4rem 0.6rem;
  background: var(--panel);
  border: 1px solid var(--line);
}
.queue-link { color: var(--accent); text-decoration: none; font-weight: 700; }
.queue-link:hover { text-decoration: underline; }
/* Whose move it is, said the same way the thread's own header says it. */
.queue-turn { display: block; font-size: 0.75rem; color: var(--accent); }

/* Four boxes in two columns. The pairing is the point: silence you have to
   chase on the left, silence somebody announced on the right — so reading
   across a row tells you why a thread is standing. One column on a phone,
   where "across" does not exist and the order becomes the reading order. */
.queue-grid {
  display: grid;
  gap: 1rem 2rem;
  grid-template-columns: repeat(auto-fit, minmax(min(26rem, 100%), 1fr));
  align-items: start;
}
.queue-grid > section { min-width: 0; }
.queue-grid h2 { margin-top: 0; }
.queue-grid > section > p.muted { margin: 0.2rem 0 0.5rem; font-size: 0.8rem; }

/* How long somebody is away. Its own colour because it is the answer to the
   row above it rather than more of the row's detail. */
.queue-away { display: block; font-size: 0.75rem; color: var(--teal-ink); }
.queue-note {
  margin: 0.3rem 0 0;
  font-size: 0.8rem;
  color: var(--text-soft);
  line-height: 1.45;
  white-space: pre-wrap;
}

/* Requests to hand a character over sit above the grid, with the rolls: they
   are something a person is waiting on, not a state of the board to notice. */
.queue-abandon { margin-top: 1.5rem; border-left: 2px solid var(--rust); padding-left: 0.8rem; }
.queue-abandon .queue-actions { margin-top: 0.4rem; display: flex; gap: 0.4rem; }

/* The shelf: characters the game is holding, and the way back off it. Teal
   rather than rust — this is where somebody is kept, not where a decision is
   pending. Empty on most days, and then not drawn at all. */
.queue-background { margin-top: 1.5rem; border-left: 2px solid var(--teal); padding-left: 0.8rem; }
.queue-background .queue-actions { margin-top: 0.4rem; display: flex; gap: 0.4rem; }

/* What the last GM action said. Above the panels, because they are redrawn. */
.gm-said { padding: 0 0.8rem; }
.gm-said:empty { display: none; }

/* ------------------------------------------------------------- the front door */

/* The two of them as one object, centred on the trees.

   Columns are `auto`, not fixed widths: the picture is sized by its height and
   the column takes whatever width that leaves, so making it taller makes it
   wider and nothing here has to be told the artwork's proportions. */
.landing {
  display: grid;
  grid-template-columns: auto auto;
  gap: 0;
  align-items: stretch;
  /* `safe`, because this box is sometimes taller than the window. Plain
     `center` centres the overflow too, which puts the top of the form above
     the top of the page — off the edge, with no scroll that reaches it, so the
     first field of the registration form simply could not be filled in. Safe
     centring gives up and aligns to the start the moment it would overflow,
     and then the page scrolls the way any long page does. */
  align-content: safe center;
  justify-content: center;
  min-height: 82vh;
  padding: 1.5rem 1rem 2.5rem;
}

/* Height-led, so it grows with the window instead of being capped by a column
   width. Portrait artwork in a landscape window has height to spare and width
   to lose, which is the wrong way round for the one picture on the page. */
.landing-art {
  display: block;
  /* Both dimensions are caps, and both are auto. Fixing the height meant that
     whenever the column was narrower than the picture wanted — which, with the
     rail on the left, is most windows — the frame stayed 82vh tall and the
     artwork letterboxed inside it. This way the picture is drawn whole and the
     frame is exactly its size. */
  width: auto;
  height: auto;
  max-height: min(82vh, 820px);
  max-width: 100%;
  object-fit: contain;
  /* Behind the door rather than beside it: the artwork sets the mood and the
     form is the thing to be read, so the picture sits back. */
  opacity: 0.5;
  border: 1px solid var(--line);
  /* They touch, so the shared edge is drawn once. */
  border-right: none;
}
.landing-art[hidden] { display: none; }

/* Stretched to the picture's height by the grid, with the fields held in the
   middle of it — a login box is five short rows and should not be stretched
   into a column of gaps. */
.landing-form { display: flex; }
/* Narrow on purpose. The picture's height is fixed and its width follows, so
   every rem this box takes is a rem the artwork loses — and once it runs out
   of width the image letterboxes inside its own frame. Five short fields do
   not need more than this. */
.landing-form .card {
  margin: 0;
  width: 19rem;
  max-width: 100%;
  display: flex;
  flex-direction: column;
  /* Safe here for the same reason as the grid above: the registration form is
     twice the login form's height, and a card centred inside a shorter column
     hangs its first rows off the top. */
  justify-content: safe center;
}

/* The picture is the first thing to go: a login box pushed below the fold by
   decoration is a worse login box. With it gone the form stops being half of a
   pair and goes back to standing on its own. */
@media (max-width: 60rem) {
  .landing {
    grid-template-columns: minmax(0, 26rem);
    min-height: 0;
    align-content: start;
  }
  .landing-art { display: none; }
  .landing-form .card { width: auto; }
}

/* Invite codes: what is spent reads quieter than what is still usable. */
.invite-roles { display: grid; gap: 0.25rem; margin: 0.25rem 0 0.5rem; }
.studio-table tr.is-spent { opacity: 0.6; }
.studio-table tr.is-fresh td { background: color-mix(in srgb, var(--accent) 12%, transparent); }
.studio-table code { font-size: 0.85em; }

/* Editing a row of the forum tree, folded away until wanted. */
.studio-editor { margin: 0.3rem 0 0.5rem; }
.studio-editor > summary { font-size: 0.8rem; cursor: pointer; }
.studio-editor label { display: grid; gap: 0.15rem; margin: 0.35rem 0; max-width: 26rem; }
.studio-editor label.checkbox { display: flex; align-items: center; gap: 0.4rem; }

/* ---------------------------------------------------- the studio's map --- */

/*
 * Placing a pin: the same drawing the board shows, at whatever size the column
 * gives it, with the pins already on it.
 *
 * The <img> is the coordinate space, not the frame around it — a pin is a
 * percentage of the picture, so the two must be the same box. `display: block`
 * on the image and `position: relative` on a wrapper that shrink-wraps it is
 * what makes that true at every width.
 */
.studio-map {
  position: relative;
  display: block;
  margin: 0.8rem 0 0;
  border: 1px solid var(--line);
  cursor: crosshair;
  line-height: 0;
}
.studio-map-art { display: block; width: 100%; height: auto; }

.studio-pin {
  position: absolute;
  width: 0.7rem;
  height: 0.7rem;
  margin: -0.35rem 0 0 -0.35rem;
  border-radius: 50%;
  background: var(--rust);
  box-shadow: 0 0 0 2px rgba(0, 0, 0, 0.45);
  pointer-events: none;
}
/* A place inside a region reads as the smaller fact it is. */
.studio-pin.is-location {
  width: 0.5rem;
  height: 0.5rem;
  margin: -0.25rem 0 0 -0.25rem;
  background: var(--text);
}
.studio-pin-name {
  position: absolute;
  left: 50%;
  top: 0.6rem;
  transform: translateX(-50%);
  white-space: nowrap;
  font-size: 0.7rem;
  line-height: 1.2;
  color: var(--text);
  text-shadow: 0 0 4px rgba(0, 0, 0, 0.9), 0 0 2px rgba(0, 0, 0, 0.9);
}

.studio-pin-picker { max-width: 100%; }

/* Up and down, beside the name in the tree. */
.studio-move { display: inline-flex; gap: 0.2rem; margin-left: 0.4rem; vertical-align: middle; }
.studio-move button { padding: 0 0.35rem; line-height: 1.4; }

/* The one button in the studio that cannot be undone. */
button.small.danger { color: var(--danger); border-color: var(--danger); }

/* A location's own pin on the board's map: quieter than the region it is in.

   Quieter, not smaller. Two sizes of the same kind of label on one drawing
   read as one of them being further away rather than as a distinction, and a
   map whose names are set in two sizes looks mis-set before it looks
   hierarchical. The weight is carried by the ink instead. */
.map-pin-label.is-location { opacity: 0.9; }

/* Hide or delete a place, on its row in the world listing. */
.place-actions { display: inline-flex; gap: 0.6rem; align-items: center; margin-left: 0.5rem; vertical-align: middle; }
.place-actions label.checkbox { display: inline-flex; align-items: center; gap: 0.3rem; font-size: 0.8rem; margin: 0; }

/* ---------------------------------------------------- radio bands (048) */

/* A band is a name and a list of what stands on it. The name is an input rather
   than a heading because renaming one is the commonest thing done here — the
   region's band arrives called "Stan Kolorado" and stops being that the moment
   the region grows a second horizon. */
.radio-bands > li {
  padding: 0.6rem 0 0.7rem;
  border-bottom: 1px solid var(--line);
}
.radio-bands > li > p { margin: 0.25rem 0 0; }
.band-head { display: flex; align-items: center; gap: 0.6rem; flex-wrap: wrap; }
.band-head input[type="text"] { flex: 0 1 18rem; min-width: 8rem; }
.band-head > span.muted { flex: 1 1 auto; display: block; margin: 0; }

/* The band picker sits on the room's own row, so a column of them reads as the
   one question it is: which horizon is this place on. */
.radio-places select { max-width: 20rem; margin-top: 0.25rem; }
.radio-places .place-actions { flex-wrap: wrap; }

/* Says how to move a map that has somewhere to move to. Nothing on a wide
   screen, where the whole drawing fits and a cursor says the rest. */
.map-hint { display: none; }
.map:not(:has(.is-pannable)) + .map-hint { display: none; }

/* ------------------------------------------------- letting things shrink */

/* A grid or flex item is born with `min-width: auto`: it refuses to be
   narrower than its own min-content. So one unbreakable thing deep inside —
   a chip set to nowrap, a <select> as wide as its longest option, a text input
   with a size — sets the width of everything above it, and finally of the
   page, which starts scrolling sideways. Four screens did this, and in none of
   them was the thing that looked wrong the thing causing it.

   Not in the phone query, though a phone is where you notice it: the same
   overflow was there at 750px, just eleven pixels of it instead of a hundred.
   Above a certain width these rules change nothing, because there is room.

   Two halves. Let the containers shrink: */
.karta > *, .dir-page > *, .dir-list > *,
.studio-forms > *, .studio-tab > *,
.card > *, .card label > * { min-width: 0; }

/* …and let the unbreakable things break. */
.dir-chip { white-space: normal; }
.karta-tabs { min-width: 0; }
.upload-row { flex-wrap: wrap; }
.upload-row input { min-width: 0; }
select { min-width: 0; max-width: 100%; }

/* ============================================================ the phone ===
 *
 * 46rem and down. The number is decided in tokens.css — read the note there
 * before changing it. The shell's own phone rules are in shell.css; these are
 * the screens inside it.
 *
 * Two rules run through all of it. Nothing sits beside anything else. And the
 * furniture around a piece of writing folds up before the writing does. */

/* The fold above a post's prose. Above the breakpoint the wrapper gets out of
   the way entirely: .post-kit goes back to being the flex child of .post-ident
   it was written as, and nothing about the wide layout changes. */
.post-fold { display: contents; }
.post-fold > summary { display: none; }

@media (max-width: 46rem) {

  /* ------------------------------------------------------------- a post */

  /* The portrait was 220×352 in a column of its own, which on a 375px screen
     is the post: the name, the kit and the prose shared what was left. It is
     a face beside the name now, the size of the name. */
  .post-top {
    grid-template-columns: 68px minmax(0, 1fr);
    gap: 0 0.75rem;
    min-height: 0;
    padding: 0.9rem 0.9rem 0;
  }
  .post-face .face { width: 68px; height: 109px; }
  .post-ident { padding: 0; gap: 0.5rem; }
  .post-said { flex-wrap: wrap; gap: 0.1rem 0.6rem; }
  .post-who { font-size: 1rem; }
  .post-when { font-size: 0.72rem; }
  .post-main { padding: 0.9rem 0.9rem 1rem; }

  /* Slots over tags is the wide layout too now — the phone only wants the two
     of them closer together. */
  .post-kit { gap: 0.5rem; }

  .post-fold { display: block; }
  .post-fold > summary {
    display: block;
    cursor: pointer;
    padding: 0.45rem 0;
    border-top: 1px solid var(--line);
    font-size: var(--label);
    letter-spacing: 0.16em;
    text-transform: uppercase;
    color: var(--ink-dim);
    /* The UA's own triangle, in both dialects, replaced by one that points
       the way this fold actually opens. */
    list-style: none;
  }
  .post-fold > summary::-webkit-details-marker { display: none; }
  .post-fold > summary::after { content: ' ▾'; }
  .post-fold[open] > summary::after { content: ' ▴'; }
  .post-fold > summary:hover { color: var(--accent); }

  /* Justified prose needs room to put the spaces. In a 290px column it puts
     them between two words instead, and the page fills with rivers. */
  .post-body, .page-body, .card-prose { text-align: left; }
  /* A pasted link with no spaces in it is wider than the screen, and the page
     grows to hold it. The whole product here is text somebody else typed. */
  .post-body, .page-body, .card-prose, .composer-preview { overflow-wrap: anywhere; }

  /* ------------------------------------------------------ a character card */

  .karta-body { padding: 0 1rem 2rem; }

  /* The masthead is a panel with a printed grid on it, and it was being told
     to have no padding at all — a leftover from when it stood against the
     trees with nothing to sit inside. The face ended up pressed into the
     corner and the name hard against the border. */
  .karta-head { gap: 1rem 1.25rem; padding: 1.25rem 1rem; }
  .karta-head h1 { font-size: 1.55rem; }

  /* The face and the name beside each other. Sending the name to its own row
     left a 100px portrait alone on the first one with three quarters of the
     masthead empty beside it. The facts do take the whole width, under both. */
  .karta-ident { flex: 1 1 11rem; }
  .karta-head .card-facts { flex: 1 1 100%; }

  /* max-content on the term column was setting the width of the page: the
     longest label decided how much room was left for every value beside it,
     and the values ran off the right-hand edge. Term over value instead. */
  .card-facts { grid-template-columns: minmax(0, 1fr); gap: 0; }
  .card-facts dt { margin-top: 0.7rem; }
  .card-facts dt:first-of-type { margin-top: 0; }

  /* The tabs bled 2rem past the panel on each side to sit on its edge. There
     is no 2rem to spare here, and there are more tabs than fit either way, so
     the row scrolls instead. */
  .karta-tabs {
    margin: 0 -1rem 1.25rem;
    padding: 0 1rem;
    overflow-x: auto;
    scrollbar-width: none;
  }
  .karta-tabs::-webkit-scrollbar { display: none; }
  .karta-tab { flex: none; padding: 0.5rem 0.75rem; letter-spacing: 0.1em; }

  .sheet-head { flex-wrap: wrap; gap: 1rem; }
  .karta-avatar-edit { max-width: none; }

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

  /* The page gutter gives some of itself back. 1.75rem down each side of a
     375px screen is a sixth of the screen spent on nothing, and every panel
     inside was paying for it — including the ones that then had no room left
     for padding of their own. The space is wanted inside the panels. */
  .view {
    padding: 1.25rem 1rem 5rem;
    padding-left: max(1rem, env(safe-area-inset-left));
    padding-right: max(1rem, env(safe-area-inset-right));
  }

  /* A field in a stacked label stretches on its own. A date field does not:
     the phone browsers give it an intrinsic width and keep it, so it came out
     a third as wide as everything above and below it. */
  input[type='date'] { width: 100%; }

  /* A card is filled in once, by somebody who has never seen it before. Room
     between the questions, rather than one continuous run of boxes. */
  .card { gap: 1rem; }

  /* ------------------------------------------------------------- the map */

  /* The whole drawing at 375px is a picture of nothing: the three pins land at
     176, 232 and 296px, and their names are 100–144px wide, so what you get is
     three labels on top of each other over a thumbnail.

     So the box stops being the map and becomes a window onto it. The drawing
     is as tall as the window and as wide as its own proportions make it —
     around 1000px on a phone, which is the width at which those same three
     names stop overlapping — and you drag it about. panOn does the moving and
     the clamping; this only decides how much is in view at once. */
  .map {
    height: 62vh;
    min-height: 0;
    overflow: hidden;
  }
  .map .map-frame {
    height: calc(100% * var(--map-zoom, 1));
    width: auto;
  }
  .map .map-art { height: 100%; width: auto; }

  /* The drawing is exactly as tall as its window, so there is nothing to pan
     up or down — and the shared `touch-action: none` would then swallow every
     vertical swipe over the map and strand the reader on it with the rest of
     the page below. Sideways is ours; up and down stays the page's. */
  .map .map-frame.is-pannable { touch-action: pan-y; }
  /* …until it is zoomed in, at which point up and down is the map's too, and
     the − button is the way back out. */
  .map.is-zoomed .map-frame.is-pannable { touch-action: none; }

  /* A drag is invisible until somebody tries it, and on a touch screen there
     is no cursor to say so. Only shown while there is actually something to
     drag to — panOn puts .is-pannable on once it has measured that. */
  .map-hint { display: block; }

  .studio-forms { gap: 0.75rem; }

  /* Anything under 16px and mobile Safari zooms the page in when the field
     takes focus — and does not zoom back out when it loses it. The four named
     rules are the ones that set a size small enough to trigger it; they are
     named rather than swept because a bare element selector loses to every
     one of them. */
  input, select, textarea,
  .topbar-search, .sb-switch, .slot select,
  .card-form textarea, .card-field textarea { font-size: 16px; }
}

/* --------------------------------------------------- a picture, full size */

/* Tapping a picture in a post opens it over the page — see lightbox.js, which
   is also where the two sizes are explained. The cursor says which of the two
   the picture is showing at. */
.lightbox {
  position: fixed;
  inset: 0;
  z-index: 200;
  padding: 1rem;
  background: rgba(8, 10, 9, 0.93);
  overflow: auto;
  overscroll-behavior: contain;
  /* A <dialog> arrives with a border, an em of padding, a max-width that keeps
     it off the edges and a colour scheme of its own. All four are furniture for
     a dialog that looks like a box; this one is the whole screen. */
  border: none;
  max-width: none;
  max-height: none;
  width: 100%;
  height: 100%;
  color: var(--text);
}
/* Only when it is open. The dialog is built once and kept, so a bare
   `display: grid` above would beat the UA's own `display: none` for a closed
   one and leave the overlay across the board for good. */
.lightbox[open] { display: grid; place-items: safe center; }
html[data-theme='light'] .lightbox { background: rgba(20, 22, 20, 0.86); }

/* The two sizes are a control, so the picture sits in a button rather than
   carrying a click handler — otherwise the only way to reach them is a mouse.
   The button is nothing to look at: the picture is the whole of it. */
.lightbox-zoom {
  padding: 0;
  border: none;
  background: none;
  font: inherit;
  display: block;
  cursor: zoom-in;
}
.lightbox.is-full .lightbox-zoom { cursor: zoom-out; }
.lightbox-zoom:focus-visible { outline: 2px solid var(--accent); outline-offset: 3px; }

/* The page behind an overlay does not scroll — the drawer's rule, for the
   drawer's reason. Keyed on the dialog's own [open] rather than a class the
   JS puts on and takes off: the browser sets and clears that attribute itself,
   including when Escape closes the thing without this file hearing about it. */
html:has(dialog.lightbox[open]),
html:has(dialog.lightbox[open]) body { overflow: hidden; }

.lightbox-art {
  max-width: 100%;
  max-height: calc(100dvh - 2rem);
  width: auto;
  height: auto;
  object-fit: contain;
  display: block;
}
/* Its own size, with the overlay scrolling around it. For a screenshot of
   small text, which fitted to the screen is exactly as unreadable as it was
   in the post. */
.lightbox.is-full .lightbox-art {
  max-width: none;
  max-height: none;
}

.lightbox-shut {
  position: fixed;
  top: 0.6rem;
  right: 0.75rem;
  width: 2.4rem;
  height: 2.4rem;
  padding: 0;
  font-size: 1.4rem;
  line-height: 1;
  color: var(--text);
  background: rgba(12, 14, 13, 0.72);
  border: 1px solid var(--line-bright);
}
.lightbox-shut:hover { color: var(--amber-bright); border-color: var(--amber); }

/* A picture you can open says so, quietly. */
.post-body img, .card-prose img, .page-body img, .composer-preview img { cursor: zoom-in; }
.post-body a img, .card-prose a img, .page-body a img { cursor: pointer; }
/* watchPictures marks the ones it made reachable, so the ring follows the
   behaviour rather than a list of selectors kept in step with it by hand. */
img[data-openable]:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }

/* The password an admin just minted, readable exactly once — see accountRow. */
.studio-secret {
  margin: 0.4rem 0 0;
  font-size: 0.85rem;
  color: var(--text-soft);
}
.studio-secret code {
  font-size: 1rem;
  letter-spacing: 0.06em;
  padding: 0.15rem 0.4rem;
  border: 1px solid var(--line-bright);
  border-radius: var(--radius);
  background: var(--bg-surface);
  color: var(--text);
  user-select: all;
}


/* ------------------------------------------------------------------ radio */

/* Krótkofalówki. A read-only chat: a dial at the top and a scrolling column of
   lines below it. There is no box — a transmission is a [radio] passage of a
   scene post (rules.md §8), not something typed here. */
.radio { display: grid; gap: 0.6rem; min-height: 0; }
.radio-head { display: grid; }
.radio-bands { width: 100%; font-size: 0.82rem; }
.radio-where { margin: 0; font-size: 0.72rem; }

.radio-stream {
  display: grid;
  gap: 0.65rem;
  align-content: start;
  height: min(58vh, 34rem);
  overflow-y: auto;
  overscroll-behavior: contain;
  padding: 0.9rem;
  border: 1px solid var(--line);
  border-radius: var(--radius);
  background: var(--bg-surface);
}
.radio-line { display: grid; grid-template-columns: auto minmax(0, 1fr); gap: 0.6rem; align-items: start; }
/* Your own, on the other side, the way every chat says "this one is you". */
.radio-line.is-mine { grid-template-columns: minmax(0, 1fr) auto; }
.radio-line.is-mine .radio-face { grid-column: 2; grid-row: 1; }
.radio-line.is-mine .radio-said { grid-column: 1; grid-row: 1; text-align: right; }
.radio-line.is-mine .radio-who { flex-direction: row-reverse; }
.radio-portrait { width: 34px; height: 54px; object-fit: cover; border-radius: 3px; }
.radio-said { min-width: 0; display: grid; gap: 0.15rem; }
.radio-who { display: flex; flex-wrap: wrap; gap: 0.5rem; align-items: baseline; margin: 0; font-size: 0.82rem; }
.radio-stamp { font-size: 0.7rem; color: var(--text-muted); }
.radio-words { font-family: var(--font-prose); line-height: 1.5; overflow-wrap: anywhere; }
.radio-words p { margin: 0 0 0.35rem; }
.radio-words p:last-child { margin-bottom: 0; }

/* The playlist is whatever has been linked on this band — see radio.js. Folded
   away, because it is something you go looking for rather than something that
   should compete with the conversation. */
.radio-playlist { display: grid; gap: 0.5rem; }
.radio-playlist > summary {
  display: flex;
  justify-content: space-between;
  gap: 0.5rem;
  cursor: pointer;
  font-family: var(--font-title);
  font-size: 0.75rem;
  letter-spacing: 0.16em;
  text-transform: uppercase;
  color: var(--ink-dim);
}
.radio-playlist p { margin: 0; }
.radio-stage { display: grid; }
.radio-embed { width: 100%; aspect-ratio: 16 / 9; max-height: 20rem; border: 0; border-radius: var(--radius); }
.radio-audio { width: 100%; }
/* Two ways to listen, for a GM who also plays: as one of their own, or as
   nobody in particular. Only drawn when both are possible. */
.radio-modes { display: flex; gap: 0.4rem; margin: 0 0 0.75rem; }
.radio-modes .is-on { border-color: var(--green); color: var(--green-ink); }
.radio-tracks { list-style: none; margin: 0; padding: 0; display: grid; gap: 0.15rem; }
.radio-track { display: flex; gap: 0.6rem; align-items: baseline; justify-content: space-between; font-size: 0.78rem; }
.radio-track.is-playing { font-weight: 600; }
.radio-track-kind { font-size: 0.7rem; color: var(--text-muted); }

/* A [radio] passage inside a scene post: the same words, framed as a
   transmission. See shared/bbcode.js. */
.transmisja {
  border-left: 2px solid var(--green);
  padding: 0.15rem 0 0.15rem 0.7rem;
  margin: 0.4rem 0;
  font-family: var(--font-prose);
  color: var(--text-muted);
}
.transmisja p:last-child { margin-bottom: 0; }

/* [radio=slug] — somebody else keying their own set inside this post. */
.transmisja.is-npc { border-left-color: var(--teal); }
.transmisja-who {
  display: block;
  font-family: var(--font-mono);
  font-size: var(--label-sm, 0.7rem);
  letter-spacing: 0.14em;
  text-transform: uppercase;
  color: var(--teal-ink);
  text-decoration: none;
  margin-bottom: 0.15rem;
}
.transmisja-who:hover { text-decoration: underline; }
.transmisja-words p:last-child { margin-bottom: 0; }

/* Szum — what their set picked up while they were writing. Deliberately unlike
   the prose above it: this is not something the author wrote. See renderNoise
   in app.js and radioNoise in server/radio.js. */
.post-szum {
  display: grid;
  gap: 0.25rem;
  margin-top: 0.9rem;
  padding: 0.55rem 0.8rem;
  border: 1px dashed var(--line-bright);
  background: transparent;
}
.szum-label {
  font-family: var(--font-mono);
  font-size: var(--label-sm, 0.7rem);
  letter-spacing: 0.18em;
  text-transform: uppercase;
  color: var(--ink-dim);
}
.szum-line {
  margin: 0;
  display: flex;
  flex-wrap: wrap;
  gap: 0.4rem;
  align-items: baseline;
  font-family: var(--font-mono);
  font-size: 0.78rem;
  color: var(--text-muted);
}
.szum-ear { color: var(--ink-dim); }
.szum-words { quotes: none; letter-spacing: 0.04em; opacity: 0.85; }
.szum-who { font-size: 0.7rem; color: var(--ink-dim); }
.szum-link {
  justify-self: start;
  font-family: var(--font-mono);
  font-size: 0.72rem;
  letter-spacing: 0.1em;
  color: var(--green-ink);
}

/* On the karta and in the ekwipunek: whether their set is switched on, and the
   switch itself when it is yours. */
.karta-radio { display: flex; flex-wrap: wrap; gap: 0.5rem; align-items: baseline; margin: 0.3rem 0 0.2rem; }
.tag.radio { border-color: var(--line-bright); color: var(--text-muted); }
.tag.radio.is-on { border-color: var(--green); color: var(--green-ink); }

/* The GM voice picker's companion: the same line, but over the air. */
.voice-radio { line-height: 1; }
.voice-radio.is-on { border-color: var(--green); color: var(--green-ink); }

/* Away, on the studio's two lists. Teal like .absence-state.is-away on
   Ustawienia, because it is the same fact seen from the other side of the
   desk — and deliberately not the rust of a trigger or a disabled account,
   which are warnings. A holiday is not a warning. */
.studio-absence { margin: 0.25rem 0 0; font-size: 0.85rem; color: var(--teal-ink); }
/* In the character roster it is a span inside the grey tail; on an account
   row it is a sentence of its own. */
span.studio-absence { display: inline; margin: 0; }

.studio-absence-form { margin-top: 0.5rem; }
.studio-absence-form > summary { font-size: 0.8rem; color: var(--text-muted); cursor: pointer; }
.studio-absence-form > summary:hover { color: var(--teal-ink); }
.studio-absence-form > p.muted { margin: 0.4rem 0; font-size: 0.82rem; line-height: 1.5; }
.studio-absence-form > .absence-form { margin-top: 0.5rem; max-width: 34rem; }
.studio-absence-form > button { margin-bottom: 0.5rem; }


/* --------------------------------------------- the recap editor's extras --
   The cast of a section, picked instead of pasted, and the box that starts
   one. Both live in the Epizody portal and nowhere else. */
.page-minis { display: flex; flex-wrap: wrap; gap: 0.5rem; margin: 0.5rem 0; }
.page-mini { display: flex; flex-direction: column; align-items: center; gap: 0.2rem; width: 7rem; font-size: 0.8rem; }
/* A polaroid is square (migration 051), so the box is known before the picture
   arrives. `height: auto` with loading="lazy" meant a row of nothing that
   shoved the list down as each one landed. */
.page-mini img { width: 100%; height: auto; aspect-ratio: 1; object-fit: cover; }
.page-new-grid { display: grid; grid-template-columns: 4rem 4rem 1fr; gap: 0.35rem; margin: 0.4rem 0; }
.nav-new { margin: 0.5rem 0 1rem; font-size: 0.85rem; }

/* Wygląd in Ustawienia: two short groups side by side, the theme in the head.
   The number sits on the label's line, because it is the answer to where the
   handle is. */
.settings-look .look-head { display: flex; align-items: center; justify-content: space-between; gap: 1rem; margin-bottom: 0.9rem; }
.settings-look .look-head h2 { margin: 0; border: none; padding: 0; }
.settings-look .look-grid { display: grid; gap: 1.2rem 2.5rem; grid-template-columns: repeat(auto-fit, minmax(min(18rem, 100%), 1fr)); }
.settings-look .look-group { display: grid; gap: 0.9rem; align-content: start; }
.settings-look h3 {
  margin: 0;
  padding-bottom: 0.3rem;
  border-bottom: 1px solid var(--line);
  font-family: var(--font-title);
  font-size: var(--label-lg);
  font-weight: normal;
  letter-spacing: 0.16em;
  text-transform: uppercase;
  color: var(--ink-dim);
}
.settings-look label { display: grid; grid-template-columns: 1fr auto; align-items: center; gap: 0.2rem 0.6rem; margin: 0; color: var(--text); }
.settings-look label > span { grid-column: 1; grid-row: 1; }
.settings-look output { grid-column: 2; grid-row: 1; font-family: var(--font-mono); font-size: 0.8rem; color: var(--ink-dim); }
.settings-look input[type='range'] { grid-column: 1 / -1; grid-row: 2; width: 100%; }
.settings-look label.look-toggle { display: flex; align-items: flex-start; gap: 0.6rem; cursor: pointer; }
.settings-look .look-toggle input { margin: 0.15rem 0 0; width: 1rem; height: 1rem; accent-color: var(--amber); flex: none; }
.settings-look .look-toggle span { display: grid; gap: 0.1rem; }
.settings-look .look-toggle b { font-weight: normal; }
.settings-look .look-toggle small { font-size: 0.78rem; line-height: 1.4; }
.settings-look .look-foot { margin-top: 1.1rem; }

/* ------------------------------------------------------ Koniec Epizodu --
   The player's form (client/js/summary.js) and its public half under a recap. */

.summary-page { display: grid; gap: 1rem; max-width: 48rem; }
.summary-form { display: grid; gap: 0.75rem; }
.summary-body { border: 0; padding: 0; margin: 0; display: grid; gap: 0.75rem; min-width: 0; }
.summary-body textarea { width: 100%; }
.summary-row { display: flex; flex-wrap: wrap; gap: 0.5rem; align-items: center; }
.summary-row > span { min-width: 7rem; color: var(--ink-dim); }
.summary-meals { display: grid; gap: 0.5rem; }
.summary-claims { display: grid; gap: 0.75rem; }
.summary-claim { display: grid; gap: 0.35rem; }
.summary-points { width: 4.5rem; }

.recap-summaries { display: grid; gap: 1rem; margin-top: 2rem; }
.recap-summary { border-top: 1px solid var(--line); padding-top: 0.75rem; }
/* Already inside .page-body, which sets the prose size; .card-prose setting it
   again made it 1.3 × 1.3. Inherit, so it reads — and scales — like the episode. */
.recap-summary .card-prose { font-size: 1em; }
/* Coloured by the result, as a wash off the left edge — the same gradient as a
   Wątki row that is your move. Green succeeds, amber is "but…", rust fails. */
.summary-roll {
  --tone: var(--amber);
  display: flex; flex-wrap: wrap; align-items: baseline; gap: 0.35rem 0.8rem;
  margin: 0.8rem 0 0; padding: 0.6rem 0.9rem;
  border: 1px solid var(--line-bright); border-left: 4px solid var(--tone);
  background: linear-gradient(90deg, color-mix(in srgb, var(--tone) 22%, transparent), transparent 65%);
  font-size: 0.9rem; text-align: left;
}
.summary-roll.r6, .summary-roll.r5, .roll.r6, .roll.r5 { --tone: var(--green-ink); }
.summary-roll.r4, .summary-roll.r3, .roll.r4, .roll.r3 { --tone: var(--amber); }
.summary-roll.r2, .summary-roll.r1, .summary-roll.r0, .roll.r2, .roll.r1, .roll.r0 { --tone: var(--rust); }
.summary-roll.r6 { background: linear-gradient(90deg, color-mix(in srgb, var(--tone) 34%, transparent), transparent 70%); }
.summary-roll.r0 { background: linear-gradient(90deg, color-mix(in srgb, var(--tone) 34%, transparent), transparent 70%); }
.summary-roll .summary-roll-label { color: color-mix(in srgb, var(--tone) 55%, var(--ink)); }
.summary-roll-head {
  flex-basis: 100%;
  font-family: var(--font-title); font-size: var(--label); letter-spacing: 0.14em;
  text-transform: uppercase; color: var(--ink-dim);
}
.summary-roll-label { font-style: italic; }
.recap-summary .summary-roll { font-size: 0.8em; }
.recap-answer {
  margin: 0.9rem 0 0 1.5rem;
  padding: 0.7rem 1rem;
  border: 1px solid var(--line-bright);
  border-left: 3px solid var(--amber);
  background: color-mix(in srgb, var(--ink) 4%, transparent);
}
.recap-answer-who {
  display: block; margin-bottom: 0.3rem;
  font-family: var(--font-title); font-size: var(--label); letter-spacing: 0.14em;
  text-transform: uppercase; color: var(--amber-bright);
}

/* ========================================================== a finger ======
 *
 * 44px, where the pointer is a finger.
 *
 * Not at the phone breakpoint, and not on every screen: a tablet is a coarse
 * pointer at 1024px and a laptop with a touchscreen is both. `pointer: coarse`
 * asks the only question that matters — is the thing doing the pressing the
 * width of a fingertip — and leaves the desktop's density alone, which is
 * deliberate and would be wrecked by pushing every .small button to 44px.
 *
 * .cookie-note's button already sets 2.75rem by hand, and keeps it: that one
 * is deliberately chunky on every pointer, not a touch accommodation. */
@media (pointer: coarse) {
  button.small,
  .tbtn,
  .karta-tab,
  .tool,
  .dir-filter,
  .scena-mark {
    min-height: 44px;
  }
  /* .nav-step and .rail-toggle are the shell's, and shell.css's own phone
     rules come after this file — they are sized in the twin of this block at
     the end of it, where they can win. */

  /* An inline button in a sentence cannot be 44px tall without pushing the
     line apart, so the target grows and the button does not: a box drawn
     around it that takes the press, laid out of flow so nothing moves. */
  button.link {
    position: relative;
  }
  button.link::after {
    content: '';
    position: absolute;
    inset: -0.7rem -0.35rem;
  }

  /* The thumb was 12px — a target you cannot reliably land on, on the one
     kind of screen where it is dragged rather than clicked. The track keeps
     its 4px; the row around it is what gets big enough to grab. */
  input[type='range'] { height: 44px; }
  input[type='range']::-webkit-slider-thumb {
    width: 22px;
    height: 22px;
    margin-top: -9px; /* still centred on the 4px track */
  }
  input[type='range']::-moz-range-thumb { width: 22px; height: 22px; }
}

/* A page still being written (076): marked in the index and over the text. */
.page-draft-tag {
  margin-left: 0.4em; padding: 0 0.35em; border: 1px solid var(--accent); color: var(--accent);
  font-size: 0.7em; letter-spacing: 0.1em; text-transform: uppercase; vertical-align: middle;
}
.page-draft-note {
  padding: 0.5rem 0.75rem; border-left: 3px solid var(--accent); color: var(--accent);
  font-size: 0.8rem; letter-spacing: 0.1em; text-transform: uppercase;
}

/* A carried thing the card asks to take away — gone on Przyjmij. */
.kit-item.is-removing { opacity: 0.55; }
.kit-item.is-removing h4 { text-decoration: line-through; }
.kit-item.is-removing .kit-head::after { content: 'do usunięcia'; font-size: 0.7rem; letter-spacing: 0.1em; text-transform: uppercase; color: var(--accent); }

/* Pogoń Użyszkodników on the DM Dashboard. */
.nag .nag-gm { margin-left: 0.3em; padding: 0 0.3em; border: 1px solid var(--rust); color: var(--rust-ink); font-size: 0.7em; letter-spacing: 0.1em; }
.nag .nag-sent { margin: 0.5rem 0 0; padding: 0.5rem 0.75rem; border-left: 3px solid var(--amber); white-space: pre-wrap; }

/* A vehicle thread's wheel, under the thread's plate (079). */
.scena-vehicle { display: flex; flex-wrap: wrap; gap: 0.5rem; align-items: center; margin-top: 0.5rem; }
/* Inside the plate it is a fact like the others: no gap above, full width. */
.scena-fact .scena-vehicle { margin-top: 0.35rem; }
.scena-fact.is-vehicle { grid-column: span 2; }
@media (max-width: 46rem) { .scena-fact.is-vehicle { grid-column: 1 / -1; } }
.scena-vehicle select { max-width: 20rem; flex: 1 1 10rem; }
.scena-vehicle .scena-fuel { font-size: 0.85rem; flex-basis: 100%; }
.scena-vehicle .scena-fuel.is-short { color: var(--rust-ink); }

/* Studio → Trasy: litres between regions. */
.routes-table input.route-fuel { width: 4.5rem; }
.routes-table input.route-fuel.is-saved { border-color: var(--green); }

/* The "?" beside Punkty on Koniec Epizodu: the rules for asking. */
.summary-points-head { display: flex; align-items: center; gap: 0.5rem; flex-wrap: wrap; }
.summary-help { font-size: 0.9rem; font-weight: normal; }
.summary-help > summary {
  list-style: none; cursor: pointer; width: 1.4rem; height: 1.4rem; line-height: 1.3rem; text-align: center;
  border: 1px solid var(--amber); border-radius: 50%; color: var(--amber-bright); font-size: 0.85rem;
}
.summary-help > summary::-webkit-details-marker { display: none; }
.summary-help[open] { flex-basis: 100%; }
.summary-help-body { margin-top: 0.5rem; padding: 0.75rem 1rem; border-left: 3px solid var(--amber); background: var(--bg-raised); color: var(--text-soft); }
.summary-help-body ul { margin: 0.5rem 0; padding-left: 1.2rem; }

/* A room with a przekaźnik (board.js mastIcon): a small mark after its name. */
.mast-icon { font-size: 0.85em; margin-left: 0.4em; cursor: help; letter-spacing: 0; }
.scena-road-band select { width: auto; max-width: 100%; }
