/* Lora — SIL Open Font License (web/fonts/OFL.txt). Self-hosted. */
@font-face {
  font-family: 'Lora';
  font-style: normal;
  font-weight: 400;
  font-display: swap;
  src: url('/fonts/lora-cyrillic-400-normal.woff2') format('woff2');
  unicode-range: U+0301, U+0400-045F, U+0490-0491, U+04B0-04B1, U+2116;
}
@font-face {
  font-family: 'Lora';
  font-style: normal;
  font-weight: 400;
  font-display: swap;
  src: url('/fonts/lora-latin-400-normal.woff2') format('woff2');
  unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+2000-206F;
}
@font-face {
  font-family: 'Lora';
  font-style: italic;
  font-weight: 400;
  font-display: swap;
  src: url('/fonts/lora-cyrillic-400-italic.woff2') format('woff2');
  unicode-range: U+0301, U+0400-045F, U+0490-0491, U+04B0-04B1, U+2116;
}
@font-face {
  font-family: 'Lora';
  font-style: italic;
  font-weight: 400;
  font-display: swap;
  src: url('/fonts/lora-latin-400-italic.woff2') format('woff2');
  unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+2000-206F;
}
@font-face {
  font-family: 'Lora';
  font-style: normal;
  font-weight: 600;
  font-display: swap;
  src: url('/fonts/lora-cyrillic-600-normal.woff2') format('woff2');
  unicode-range: U+0301, U+0400-045F, U+0490-0491, U+04B0-04B1, U+2116;
}
@font-face {
  font-family: 'Lora';
  font-style: normal;
  font-weight: 600;
  font-display: swap;
  src: url('/fonts/lora-latin-600-normal.woff2') format('woff2');
  unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+2000-206F;
}

:root {
  /* Night beyond the edge of worlds; the story is a lit page seen from there. */
  --void: #141522;
  --void-deep: #0f101b;
  --veil: #1c1e30;
  --veil-line: #2d3048;
  --page: #231d19;
  --page-line: #3b3029;
  --ink: #ece4d6;
  --text: #d9d6e4;
  /* Both readable on every background here (at least 4.5:1), the faint one a step quieter. */
  --muted: #a19db5;
  --faint: #8a869f;
  --wanderer: #d8c48f;
  --thread-1: #e0875a;
  --thread-2: #58b7a6;
  --thread-3: #a38ee0;
  --clean: #74b884;
  --costly: #d8a64a;
  --failure: #cf5f52;
  --serif: 'Lora', 'Noto Serif', Georgia, serif;
  --sans: system-ui, -apple-system, 'Segoe UI', 'Noto Sans', 'Helvetica Neue', sans-serif;
  --urgent: #b3443a;
  --bar-h: 56px;
  /* Clear of a notch and the home bar (viewport-fit=cover); zero everywhere else. */
  --safe-t: env(safe-area-inset-top, 0px);
  --safe-r: env(safe-area-inset-right, 0px);
  --safe-b: env(safe-area-inset-bottom, 0px);
  --safe-l: env(safe-area-inset-left, 0px);
  /* The side gutter of the page and the bar: they line up. */
  --gutter: 1.5rem;
  color-scheme: dark;
}

* {
  box-sizing: border-box;
}

[hidden] {
  display: none !important;
}

html {
  background: var(--void-deep);
  /* A phone turned on its side does not blow the text up by itself; the reader's zoom works. */
  -webkit-text-size-adjust: 100%;
  text-size-adjust: 100%;
}

/* A big screen reads from further away: everything a little larger, the lines as long. */
@media (min-width: 112.5em) {
  html {
    font-size: 112.5%;
  }
}

@media (min-width: 150em) {
  html {
    font-size: 125%;
  }
}

body {
  margin: 0;
  min-height: 100vh;
  min-height: 100dvh;
  color: var(--text);
  font-family: var(--sans);
  /* 15px at the usual default: in rem, a bigger font set in the browser makes all of it bigger. */
  font-size: 0.9375rem;
  line-height: 1.5;
  /* A long word or a link from the story never pushes the page sideways. */
  overflow-wrap: break-word;
  background:
    radial-gradient(ellipse 80% 60% at 35% -10%, #262947 0%, transparent 70%),
    radial-gradient(ellipse 120% 90% at 50% 120%, #090a12 0%, transparent 60%), var(--void);
  background-attachment: fixed;
}

a {
  color: var(--wanderer);
}

h1,
h2,
h3,
h4 {
  margin: 0 0 0.5rem;
  font-weight: 600;
  line-height: 1.25;
}

h2 {
  font-family: var(--serif);
  font-size: 1.3rem;
  font-weight: 400;
}

h3 {
  font-size: 0.95rem;
  color: var(--wanderer);
}

h4 {
  font-size: 0.85rem;
  color: var(--muted);
  margin-top: 0.8rem;
}

p {
  margin: 0 0 0.5rem;
}

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

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

.primary {
  cursor: pointer;
  border: 1px solid var(--wanderer);
  background: var(--wanderer);
  color: #1d1a10;
  font-weight: 600;
  border-radius: 999px;
  padding: 0.55rem 1.3rem;
}

.primary:disabled {
  opacity: 0.45;
  cursor: progress;
}

.hint,
.muted {
  color: var(--muted);
}

.error {
  color: #f0a39a;
}

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

.skip {
  position: absolute;
  z-index: 40;
  left: 0.6rem;
  top: -4rem;
  padding: 0.45rem 0.9rem;
  border-radius: 999px;
  background: var(--wanderer);
  color: #1d1a10;
  font-weight: 600;
  text-decoration: none;
}

.skip:focus {
  top: 0.6rem;
}

.visually-hidden {
  position: absolute;
  width: 1px;
  height: 1px;
  margin: -1px;
  padding: 0;
  overflow: hidden;
  clip-path: inset(50%);
  white-space: nowrap;
  border: 0;
}

/* The bar stays on top; the page learns its height from the script (--bar-h). */
.bar {
  position: sticky;
  top: 0;
  z-index: 20;
  padding-top: var(--safe-t);
  background: rgba(15, 16, 27, 0.95);
  border-bottom: 1px solid var(--veil-line);
  backdrop-filter: blur(6px);
}

/* Taller than a quarter of the window (see followBar): it stays at the top, not over the page. */
.bar.tall {
  position: relative;
}

/* The bar's line and the goal under it keep to the page's width, edge to edge with its text.
   On a wide screen it is tall enough for the name and the build to stand clear of its edges. */
.bar-row {
  min-height: 60px;
  display: flex;
  align-items: center;
  gap: 0.4rem 1.1rem;
  max-width: 82.5rem;
  margin: 0 auto;
  padding: 0.4rem max(var(--gutter), var(--safe-r)) 0.4rem max(var(--gutter), var(--safe-l));
}

/* The name with the whorl, and the build under it in a quiet line that keeps its place. Each is
   a link at least 24px high with a gap between them; the block reaches into the bar's padding,
   so the bar keeps its height. */
.brand-block {
  flex: none;
  display: grid;
  justify-items: start;
  row-gap: 4px;
  margin-block: -0.4rem;
  line-height: 1.2;
}

.brand {
  display: inline-flex;
  align-items: center;
  min-height: 24px;
  gap: 0.5rem;
  font-family: var(--serif);
  font-size: 1.05rem;
  text-decoration: none;
  white-space: nowrap;
}

.brand-mark {
  flex: none;
  width: 1.45rem;
  height: 1.45rem;
}

/* The whorl, as the page draws it anywhere: three threads' arcs round an open middle, the gold
   bead at rest on top, a margin of the background round the bead. */
.whorl-arcs path {
  fill: none;
  stroke-width: 8.4;
}

.whorl-arcs .t1 {
  stroke: var(--thread-1);
}

.whorl-arcs .t2 {
  stroke: var(--thread-2);
}

.whorl-arcs .t3 {
  stroke: var(--thread-3);
}

.whorl-gap {
  fill: var(--void-deep);
}

.whorl-bead {
  fill: var(--wanderer);
}

/* «сборка 57 · 25.09»: under the name, leading to what changed. */
.build {
  display: inline-flex;
  align-items: center;
  min-height: 24px;
  margin-left: 1.95rem;
  font-size: 0.72rem;
  color: var(--faint);
  text-decoration: none;
  white-space: nowrap;
}

.build:not(.ready) {
  visibility: hidden;
}

@media (hover: hover) {
  .build:hover {
    color: var(--wanderer);
    text-decoration: underline;
    text-underline-offset: 2px;
  }
}

/* «Что нового» at the end of the lobby's bar; a story's bar has no room for it. */
.bar-news {
  display: inline-flex;
  align-items: center;
  min-height: 24px;
  margin-left: auto;
  font-size: 0.88rem;
  color: var(--muted);
  text-underline-offset: 3px;
}

body[data-view='game'] .bar-news {
  display: none;
}

@media (hover: hover) {
  .bar-news:hover {
    color: var(--wanderer);
  }
}

/* The story's name takes the room left, down to a few words: past that, what follows wraps. */
.bar-world {
  flex: 1 1 6rem;
  min-width: 0;
  display: flex;
  align-items: baseline;
  gap: 0.6rem;
  overflow: hidden;
  white-space: nowrap;
}

.bar-title {
  flex: 0 0 auto;
  max-width: min(24rem, 30vw);
  font-family: var(--serif);
  font-size: 1.05rem;
  color: var(--ink);
  overflow: hidden;
  text-overflow: ellipsis;
}

/* Where the story is set gives way first when the line is short. */
.bar-source {
  flex: 0 100 auto;
  min-width: 0;
  color: var(--faint);
  font-size: 0.85rem;
  overflow: hidden;
  text-overflow: ellipsis;
}

/* «Цель 2/3»: shows or hides the goal; on a phone it opens the panel over the page. */
.goal-toggle {
  flex: none;
  cursor: pointer;
  display: inline-flex;
  align-items: baseline;
  gap: 0.35rem;
  white-space: nowrap;
  font-size: 0.85rem;
  padding: 0.22rem 0.75rem;
  border-radius: 999px;
  border: 1px solid var(--veil-line);
  background: var(--veil);
  color: var(--text);
}

.goal-toggle::after {
  content: '';
  width: 0.4rem;
  height: 0.4rem;
  margin-left: 0.1rem;
  border-right: 1.5px solid currentColor;
  border-bottom: 1.5px solid currentColor;
  transform: translateY(-0.15rem) rotate(45deg);
  transition: transform 0.2s;
}

.goal-toggle[aria-expanded='true']::after {
  transform: translateY(0.05rem) rotate(-135deg);
}

.goal-toggle[aria-expanded='true'] {
  border-color: color-mix(in srgb, var(--wanderer) 60%, var(--veil-line));
}

/* Hover only where there is a pointer that hovers: a tap leaves nothing lit on a phone. */
@media (hover: hover) {
  .goal-toggle:hover {
    border-color: color-mix(in srgb, var(--wanderer) 60%, var(--veil-line));
  }
}

.goal-toggle-count {
  color: var(--clean);
  font-variant-numeric: tabular-nums;
}

.countdown {
  flex: none;
  white-space: nowrap;
  font-size: 0.85rem;
  padding: 0.22rem 0.75rem;
  border-radius: 999px;
  border: 1px solid var(--wanderer);
  color: var(--wanderer);
  font-variant-numeric: tabular-nums;
}

.countdown.urgent {
  border-color: var(--urgent);
  background: var(--urgent);
  color: #fff;
}

/* The goal, its steps and the rule of the clock, under the first line. */
.goal-panel {
  display: grid;
  gap: 0.3rem;
  max-width: 82.5rem;
  margin: 0 auto;
  padding: 0 max(var(--gutter), var(--safe-r)) 0.6rem max(var(--gutter), var(--safe-l));
}

.threat-text {
  margin: 0;
  color: var(--text);
  font-size: 0.88rem;
  line-height: 1.4;
}

/* The goal in steps, in their order: a made one is filled, the last may wait for later. */
.goal-steps {
  display: flex;
  flex-wrap: wrap;
  gap: 0.25rem 1.2rem;
  margin: 0;
  padding: 0;
  list-style: none;
  font-size: 0.84rem;
  line-height: 1.35;
  color: var(--muted);
}

.goal-steps li {
  display: inline-flex;
  align-items: baseline;
  gap: 0.4rem;
}

.step-mark {
  flex: none;
  display: inline-grid;
  place-items: center;
  width: 1.2rem;
  height: 1.2rem;
  border-radius: 50%;
  border: 1px solid var(--muted);
  font-size: 0.7rem;
  font-weight: 600;
  line-height: 1;
  color: var(--muted);
  transform: translateY(0.1rem);
}

.goal-steps li.done {
  color: var(--clean);
}

.goal-steps li.done .step-mark {
  border-color: var(--clean);
  background: var(--clean);
  color: #0e1a11;
}

.goal-steps li.far .step-mark,
.goal-steps li.early .step-mark {
  border-style: dashed;
}

/* A step that can no longer be made: crossed out, with why. */
.goal-steps li.failed {
  color: var(--muted);
}

.goal-steps li.failed .step-text {
  text-decoration: line-through;
  text-decoration-color: var(--failure);
}

.goal-steps li.failed .step-mark {
  border-color: var(--failure);
  color: var(--failure);
}

.goal-steps .step-failed {
  color: var(--failure);
  display: inline-block;
  text-decoration: none;
}

/* The last step waits for the disaster to come nearer. */
.goal-steps .step-later {
  color: var(--faint);
}

/* A backup step: given in place of a failed one, and said so. */
.goal-steps .step-backup {
  display: inline-block;
  margin-left: 0.45rem;
  padding: 0 0.45rem;
  border: 1px dashed color-mix(in srgb, var(--wanderer) 60%, transparent);
  border-radius: 999px;
  font-size: 0.72rem;
  line-height: 1.45;
  color: var(--wanderer);
  text-decoration: none;
  vertical-align: 0.06em;
}

.goal-steps li.backup .step-mark {
  border-color: var(--wanderer);
  color: var(--wanderer);
}

.goal-steps li.backup.done .step-mark {
  border-color: var(--clean);
  color: #0e1a11;
}

/* Given just now: it comes in once. */
.goal-steps li.just-given {
  animation: step-given 0.9s ease-out 1 backwards;
}

@keyframes step-given {
  from {
    opacity: 0;
    transform: translateY(-0.3rem);
  }
}

.clock-rule {
  margin: 0;
  color: var(--faint);
  font-size: 0.78rem;
  line-height: 1.35;
}

/* On a wide screen the rule is read once: it waits behind «?» by the counter. */
@media (min-width: 56.3125em) {
  .goal-panel:not(.rule-open) .clock-rule {
    display: none;
  }
}

.rule-help {
  flex: none;
  cursor: pointer;
  width: 1.6rem;
  height: 1.6rem;
  margin-left: -0.6rem;
  padding: 0;
  border: 1px solid var(--veil-line);
  border-radius: 50%;
  background: none;
  color: var(--muted);
  font-size: 0.85rem;
  line-height: 1;
}

.rule-help[aria-expanded='true'] {
  color: var(--ink);
  border-color: var(--wanderer);
}

@media (hover: hover) {
  .rule-help:hover {
    color: var(--ink);
    border-color: var(--wanderer);
  }
}

/* The disaster came nearer while the page looked: the number drops in, once. */
.countdown.tick .count-num {
  display: inline-block;
  animation: count-drop 0.6s ease-out 1 backwards;
}

.countdown.urgent.tick {
  animation: nudge 0.5s ease-out 0.4s 1;
}

@keyframes count-drop {
  from {
    transform: translateY(-0.7em);
    opacity: 0;
  }
}

@keyframes nudge {
  30% {
    transform: translateX(-3px);
  }
  60% {
    transform: translateX(3px);
  }
}

/* A step made while the page looked: its mark fills and rings once; a failed one shakes. */
.goal-steps li.just-done .step-mark {
  animation: step-made 0.9s ease-out 1;
}

.goal-steps li.just-failed .step-mark {
  animation: step-failed 0.6s ease-out 1;
}

@keyframes step-made {
  0% {
    transform: translateY(0.1rem) scale(0.6);
    box-shadow: 0 0 0 0 var(--clean);
  }
  50% {
    transform: translateY(0.1rem) scale(1.25);
    box-shadow: 0 0 0 6px color-mix(in srgb, var(--clean) 35%, transparent);
  }
  100% {
    box-shadow: 0 0 0 10px transparent;
  }
}

@keyframes step-failed {
  25% {
    transform: translate(-3px, 0.1rem);
  }
  50% {
    transform: translate(3px, 0.1rem);
  }
  75% {
    transform: translate(-2px, 0.1rem);
  }
}

.goal-toggle.bump .goal-toggle-count,
.goal-toggle.bump-failed .goal-toggle-count {
  display: inline-block;
  animation: count-bump 0.8s ease-out 1;
}

@keyframes count-bump {
  40% {
    transform: scale(1.35);
  }
}

/* The phone's panel only: the three, how they are tied, where it is set, the turns, the way back. */
.goal-panel .panel-three,
.goal-panel .panel-end,
.panel-bond,
.panel-source,
.panel-turns,
.panel-links {
  display: none;
}

/* ---------- the thread of turns: a knot for each turn ---------- */

.turn-thread {
  flex: 0 1 auto;
  min-width: 0;
  max-width: 34vw;
  overflow-x: auto;
  scrollbar-width: none;
}

.knots {
  display: flex;
  align-items: center;
  margin: 0;
  padding: 0.15rem 0.1rem;
  list-style: none;
}

.knots li {
  display: flex;
  align-items: center;
}

/* The thread between two knots. */
.knots li + li::before {
  content: '';
  width: 0.55rem;
  height: 1px;
  background: color-mix(in srgb, var(--wanderer) 45%, transparent);
}

.knot {
  --k: var(--muted);
  cursor: pointer;
  display: grid;
  place-items: center;
  width: 1.3rem;
  height: 1.3rem;
  padding: 0;
  border: 0;
  border-radius: 50%;
  background: none;
  color: var(--ink);
  font-size: 0.72rem;
  font-variant-numeric: tabular-nums;
}

.knot::before {
  content: '';
  grid-area: 1 / 1;
  width: 0.6rem;
  height: 0.6rem;
  border-radius: 50%;
  background: var(--k);
  transition: transform 0.15s;
}

@media (hover: hover) {
  .knot:hover::before {
    transform: scale(1.3);
  }
}

.knot .knot-num {
  grid-area: 1 / 1;
  position: absolute;
  width: 1px;
  height: 1px;
  overflow: hidden;
  clip-path: inset(50%);
}

.knot.k-clean {
  --k: var(--clean);
}

.knot.k-costly {
  --k: var(--costly);
}

.knot.k-failure {
  --k: var(--failure);
}

.knot.k-mixed::before {
  background: linear-gradient(90deg, var(--costly) 50%, var(--failure) 50%);
}

.knot.k-start::before,
.knot.k-waiting::before {
  background: none;
  border: 1.5px solid var(--wanderer);
}

.knot.k-waiting::before {
  animation: pulse 1.6s ease-in-out infinite;
}

.knot.k-end {
  --k: var(--wanderer);
}

.knot.k-end::before {
  border-radius: 2px;
  transform: rotate(45deg);
}

/* A step of the goal made in this turn: a green ring round the knot. */
.knot.k-step::before {
  box-shadow:
    0 0 0 2px var(--void-deep),
    0 0 0 3.5px var(--clean);
}

/* The turn being read now. */
.knot[aria-current='true']::before {
  transform: scale(1.45);
  box-shadow:
    0 0 0 2px var(--void-deep),
    0 0 0 3.5px var(--ink);
}

/* In the phone's panel: bigger knots with their numbers on them. */
.knots.numbered {
  flex-wrap: wrap;
  row-gap: 0.5rem;
}

.knots.numbered .knot {
  width: 2.3rem;
  height: 2.3rem;
  font-size: 0.85rem;
  font-weight: 600;
}

.knots.numbered .knot::before {
  width: 2rem;
  height: 2rem;
}

.knots.numbered .knot .knot-num {
  position: relative;
  width: auto;
  height: auto;
  clip-path: none;
  color: #12131d;
}

.knots.numbered .knot.k-start .knot-num,
.knots.numbered .knot.k-waiting .knot-num,
.knots.numbered .knot.k-failure .knot-num,
.knots.numbered .knot.k-mixed .knot-num {
  color: var(--ink);
}

.knots.numbered .knot.k-end::before {
  width: 1.5rem;
  height: 1.5rem;
}

.knots.numbered .knot[aria-current='true']::before {
  transform: none;
}

.knots.numbered .knot.k-end[aria-current='true']::before {
  transform: rotate(45deg);
}

.knots.numbered li + li::before {
  width: 0.4rem;
}

/* «Начало» and «Итог» in words. */
.knots.numbered .knot.word {
  width: auto;
  padding: 0 0.75rem;
  border: 1px solid var(--wanderer);
  border-radius: 999px;
}

.knots.numbered .knot.word::before {
  display: none;
}

.knots.numbered .knot.word .knot-num {
  color: var(--wanderer);
  font-weight: 400;
}

.knots.numbered .knot.word[aria-current='true'] {
  background: color-mix(in srgb, var(--wanderer) 18%, transparent);
}

/* ---------- lobby ---------- */

/*
 * A newcomer: one column down the middle — the Wanderer over the edge of worlds, the name, what
 * the game is, the new world with how many threads to take. One who has come back: his stories,
 * with the new world beside them on a wide screen (after the stories on a phone).
 */
.lobby {
  max-width: 44rem;
  margin: 0 auto;
  padding: 2.4rem max(1.25rem, var(--safe-r)) calc(4rem + var(--safe-b)) max(1.25rem, var(--safe-l));
}

.lobby:not(.returning) .lobby-stories {
  display: none;
}

.threshold {
  text-align: center;
}

/* The Wanderer's bead over the edge, his threads down through it to the people who hear him. */
.hero-loom {
  display: block;
  width: min(30rem, 100%);
  height: auto;
  margin: 0 auto;
  overflow: visible;
}

.hero-loom .edge {
  stroke: color-mix(in srgb, var(--wanderer) 38%, transparent);
  stroke-width: 1.2;
  stroke-dasharray: 2 5;
  stroke-linecap: round;
}

.hero-loom .halo {
  fill: none;
  stroke: color-mix(in srgb, var(--wanderer) 30%, transparent);
  stroke-width: 1.2;
}

.hero-loom .bead {
  fill: var(--wanderer);
}

.hero-loom .thread {
  fill: none;
  stroke-width: 3;
  stroke-linecap: round;
  opacity: 0.95;
}

.hero-loom .t1 {
  stroke: var(--thread-1);
}

.hero-loom .person.t1 {
  fill: var(--thread-1);
}

.hero-loom .t2 {
  stroke: var(--thread-2);
}

.hero-loom .person.t2 {
  fill: var(--thread-2);
}

.hero-loom .t3 {
  stroke: var(--thread-3);
}

.hero-loom .person.t3 {
  fill: var(--thread-3);
}

.hero-loom circle.person {
  stroke: var(--void);
  stroke-width: 3;
}

.hero-loom .off {
  display: none;
}

/* The one moment of the lobby: the threads go down from the bead, then the people light up —
   when it comes into sight, and again when another number of threads is chosen. */
.hero-loom.drawing .thread {
  stroke-dasharray: 1;
  animation: hero-thread 0.9s cubic-bezier(0.3, 0.6, 0.2, 1) both;
}

.hero-loom.drawing .thread[data-at='1'] {
  animation-delay: 0.08s;
}

.hero-loom.drawing .thread[data-at='2'] {
  animation-delay: 0.16s;
}

.hero-loom.drawing .person {
  animation: hero-person 0.5s ease-out 0.75s both;
}

@keyframes hero-thread {
  from {
    stroke-dashoffset: 1;
  }
  to {
    stroke-dashoffset: 0;
  }
}

@keyframes hero-person {
  from {
    opacity: 0;
    transform: scale(0.4);
  }
}

.hero-loom .person {
  transform-box: fill-box;
  transform-origin: center;
}

.wordmark {
  margin: 0.4rem 0 1rem;
  font-family: var(--serif);
  font-weight: 400;
  font-size: clamp(2.3rem, 1.6rem + 3vw, 3.4rem);
  line-height: 1.05;
  letter-spacing: -0.012em;
  color: var(--ink);
}

.threshold .lore {
  max-width: 34rem;
  margin: 0 auto;
  font-family: var(--serif);
  font-size: 1.08rem;
  line-height: 1.72;
  color: var(--text);
}

/* The new world: how many threads, what that means, the button. */
.new-world {
  display: grid;
  justify-items: center;
  margin-top: 2.6rem;
  text-align: center;
}

/* A newcomer has just read the name of the game over it: the heading is for a screen reader. */
.lobby:not(.returning) .new-world-head {
  position: absolute;
  width: 1px;
  height: 1px;
  overflow: hidden;
  clip-path: inset(50%);
  white-space: nowrap;
}

.threads-choice {
  display: grid;
  grid-template-columns: repeat(3, minmax(0, 1fr));
  gap: 0.6rem;
  width: min(31rem, 100%);
  margin: 0;
  padding: 0;
  border: none;
}

.threads-choice legend {
  grid-column: 1 / -1;
  margin: 0 auto 0.7rem;
  padding: 0;
  font-size: 0.9rem;
  color: var(--muted);
}

.choice {
  position: relative;
  cursor: pointer;
  display: grid;
  justify-items: center;
  align-content: start;
  gap: 0.15rem;
  padding: 0.75rem 0.4rem 0.7rem;
  border: 1px solid var(--veil-line);
  border-radius: 10px;
  background: color-mix(in srgb, var(--veil) 70%, transparent);
  color: var(--text);
}

/* The radio itself is the whole card; it stays in reach of the keyboard and a screen reader. */
.choice input {
  position: absolute;
  inset: 0;
  margin: 0;
  opacity: 0;
  cursor: pointer;
}

.choice-glyph {
  width: 2.25rem;
  height: 1.9rem;
  margin-bottom: 0.2rem;
}

.choice-glyph path {
  fill: none;
  stroke-width: 2.2;
  stroke-linecap: round;
}

.choice-glyph .t1 {
  stroke: var(--thread-1);
}

.choice-glyph .t2 {
  stroke: var(--thread-2);
}

.choice-glyph .t3 {
  stroke: var(--thread-3);
}

.choice-glyph circle {
  fill: var(--wanderer);
}

.choice-name {
  font-family: var(--serif);
  font-size: 1.02rem;
  color: var(--ink);
}

.choice-word {
  font-size: 0.8rem;
  color: var(--faint);
}

.choice:has(input:checked) {
  border-color: var(--wanderer);
  background: color-mix(in srgb, var(--wanderer) 9%, var(--veil));
}

.choice:has(input:checked) .choice-word {
  color: var(--wanderer);
}

.choice:has(input:focus-visible) {
  outline: 2px solid var(--wanderer);
  outline-offset: 2px;
}

@media (hover: hover) {
  .choice:hover {
    border-color: color-mix(in srgb, var(--wanderer) 55%, var(--veil-line));
  }
}

.threads-choice:disabled .choice {
  cursor: progress;
  opacity: 0.6;
}

/* What the choice means: two lines kept for it, so another choice moves nothing below. */
.choice-note {
  width: min(31rem, 100%);
  min-height: 3em;
  margin: 0.8rem 0 1.3rem;
  font-size: 0.92rem;
  line-height: 1.5;
  color: var(--muted);
}

.new-world .primary {
  font-size: 1.1rem;
  padding: 0.75rem 2.4rem;
}

.new-world .hint,
.new-world .error {
  max-width: 31rem;
  margin: 0.9rem 0 0;
  font-size: 0.88rem;
}

/* ---------- a world being made: its steps, and «О мире» to read meanwhile ---------- */

.creation {
  width: 100%;
  max-width: 31rem;
  margin-top: 1.1rem;
  text-align: left;
}

.creation-steps {
  list-style: none;
  margin: 0;
  padding: 0;
  display: grid;
  gap: 0.45rem;
  font-size: 0.9rem;
  line-height: 1.4;
}

.creation-steps li {
  display: grid;
  grid-template-columns: 1.1rem 1fr;
  gap: 0.55rem;
  align-items: baseline;
  color: var(--faint);
}

.creation-steps li.done {
  color: var(--muted);
}

.creation-steps li.active {
  color: var(--text);
}

/* Done — a tick in the Wanderer's gold; at work — a bead that breathes; to come — a ring. */
.creation-mark {
  position: relative;
  top: 0.1em;
  width: 0.7rem;
  height: 0.7rem;
  border-radius: 50%;
  border: 1.5px solid var(--veil-line);
}

.creation-steps li.done .creation-mark {
  border: none;
  border-radius: 0;
  width: 0.75rem;
  height: 0.4rem;
  border-left: 2px solid var(--wanderer);
  border-bottom: 2px solid var(--wanderer);
  transform: rotate(-45deg) translate(0.05rem, -0.1rem);
}

.creation-steps li.active .creation-mark {
  border-color: var(--wanderer);
  background: var(--wanderer);
  animation: creation-breath 1.6s ease-in-out infinite;
}

@keyframes creation-breath {
  0%,
  100% {
    opacity: 1;
    box-shadow: 0 0 0 0 rgba(216, 196, 143, 0.45);
  }
  50% {
    opacity: 0.55;
    box-shadow: 0 0 0 0.3rem rgba(216, 196, 143, 0);
  }
}

.creation-about {
  margin-top: 1.4rem;
  padding: 1rem 1.1rem 0.4rem;
  border: 1px solid var(--veil-line);
  border-radius: 0.6rem;
  background: var(--veil);
  font-size: 0.9rem;
  line-height: 1.5;
  color: var(--text);
}

.creation-head {
  margin: 0;
  font-family: var(--serif);
  font-size: 1.05rem;
  color: var(--ink);
}

.creation-point {
  margin: 0.2rem 0 0;
  color: var(--wanderer);
  font-size: 0.85rem;
}

.creation-lead {
  margin: 0.2rem 0 0.8rem;
  color: var(--faint);
  font-size: 0.8rem;
}

.creation-about h4 {
  margin: 0.9rem 0 0.3rem;
  font-size: 0.8rem;
  font-weight: 600;
  letter-spacing: 0.02em;
  color: var(--muted);
}

.creation-about ul {
  margin: 0;
  padding-left: 1.1rem;
}

.creation-about li {
  margin-bottom: 0.25rem;
}

.creation-about p {
  margin: 0 0 0.6rem;
}

.creation-source {
  color: var(--faint);
  font-size: 0.8rem;
}

/* Under it all: what becomes of the stories, and the key for another device (folded). */
.lobby-foot {
  margin-top: 3rem;
  padding-top: 1.1rem;
  border-top: 1px dashed var(--veil-line);
  font-size: 0.88rem;
  color: var(--muted);
}

.notice {
  margin: 0 0 0.6rem;
  color: var(--faint);
}

.key-fold summary {
  cursor: pointer;
  width: fit-content;
  color: var(--muted);
}

.key-fold[open] summary {
  margin-bottom: 0.6rem;
  color: var(--text);
}

.games {
  list-style: none;
  padding: 0;
  margin: 0;
}

/* A story in the lobby: the whole line opens it; its state stands apart on the right. */
.games li {
  position: relative;
  display: grid;
  grid-template-columns: minmax(0, 1fr) auto;
  align-items: baseline;
  gap: 0.2rem 1rem;
  padding: 0.75rem 0.4rem;
  border-bottom: 1px solid var(--veil-line);
}

@media (hover: hover) {
  .games li:has(a):hover {
    background: rgba(255, 255, 255, 0.025);
  }
}

.games a {
  font-family: var(--serif);
  font-size: 1.05rem;
  text-decoration: none;
  color: var(--ink);
}

.games a::after {
  content: '';
  position: absolute;
  inset: 0;
}

@media (hover: hover) {
  .games li:hover a {
    color: var(--wanderer);
  }
}

.games .state {
  font-size: 0.85rem;
  white-space: nowrap;
  color: var(--wanderer);
}

.games .state.win {
  color: var(--clean);
}

.games .state.partial {
  color: var(--costly);
}

.games .state.loss {
  color: var(--failure);
}

.games .meta {
  grid-column: 1 / -1;
  color: var(--muted);
  font-size: 0.88rem;
}

.unavailable {
  margin-top: 1.2rem;
  color: var(--muted);
}

.unavailable summary {
  cursor: pointer;
}

.lobby-games + .lobby-games {
  margin-top: 2.5rem;
}

/* One who has come back knows the game: its name stays for a screen reader, the lore goes. */
.lobby.returning .wordmark {
  position: absolute;
  width: 1px;
  height: 1px;
  margin: 0;
  overflow: hidden;
  clip-path: inset(50%);
  white-space: nowrap;
}

.lobby.returning .threshold .lore {
  display: none;
}

/* One who has come back, on a wide screen: his stories, and the new world beside them. */
@media (min-width: 56.3125em) {
  .lobby.returning {
    max-width: 74rem;
    display: grid;
    grid-template-columns: minmax(0, 1fr) 22rem;
    gap: 0 4.5rem;
    align-items: start;
    padding-top: 2.6rem;
  }

  .lobby.returning .lobby-aside {
    position: sticky;
    top: calc(var(--bar-h) + 1.6rem);
    padding: 1.3rem 1.4rem 1.2rem;
    border: 1px solid var(--veil-line);
    border-radius: 12px;
    background: color-mix(in srgb, var(--veil) 55%, transparent);
  }

  .lobby.returning .hero-loom {
    width: 100%;
    margin-top: -0.4rem;
  }

  .lobby.returning .new-world {
    margin-top: 0.2rem;
  }

  .lobby.returning .new-world-head {
    margin: 0 0 0.9rem;
    font-size: 1.45rem;
    color: var(--ink);
  }

  .lobby.returning .choice {
    padding-inline: 0.2rem;
  }

  .lobby.returning .new-world .primary {
    width: 100%;
  }

  .lobby.returning .lobby-foot {
    margin-top: 1.6rem;
  }
}

.key-row {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 0.5rem 0.7rem;
  margin: 0.7rem 0;
}

.key-row label {
  color: var(--muted);
  font-size: 0.9rem;
}

.key-value {
  flex: 1 1 18rem;
  min-width: 0;
  /* Not under 16px: a phone would zoom the page in to type into it. */
  font:
    max(1rem, 16px) ui-monospace,
    'SFMono-Regular',
    Menlo,
    Consolas,
    monospace;
  color: var(--ink);
  background: var(--veil);
  border: 1px solid var(--veil-line);
  border-radius: 8px;
  padding: 0.4rem 0.6rem;
}

/* ---------- game layout ---------- */

.game {
  display: grid;
  grid-template-columns: minmax(0, 1fr) 23.75rem;
  gap: 2rem;
  max-width: 82.5rem;
  margin: 0 auto;
  padding: 1.5rem max(var(--gutter), var(--safe-r)) 0 max(var(--gutter), var(--safe-l));
  align-items: start;
}

.tabs {
  display: none;
}

.feed-zone {
  max-width: 47.5rem;
  width: 100%;
  justify-self: center;
  padding-bottom: calc(3rem + var(--safe-b));
}

.side {
  position: sticky;
  top: calc(var(--bar-h) + 1rem);
  max-height: calc(100vh - var(--bar-h) - 2rem);
  max-height: calc(100dvh - var(--bar-h) - 2rem);
  overflow-y: auto;
  overscroll-behavior: contain;
  padding-right: 0.25rem;
}

/* More below in the column: its last lines fade out. */
.side.more-below {
  mask-image: linear-gradient(to bottom, #000 calc(100% - 3rem), transparent);
}

.side-tabs {
  display: flex;
  gap: 0.25rem;
  margin-bottom: 1rem;
  border-bottom: 1px solid var(--veil-line);
}

.side-tabs button {
  cursor: pointer;
  border: 0;
  background: none;
  padding: 0.5rem 0.9rem;
  color: var(--muted);
  border-bottom: 2px solid transparent;
  margin-bottom: -1px;
}

.side-tabs button.active {
  color: var(--ink);
  border-bottom-color: var(--wanderer);
}

.game[data-side='team'] .world-zone,
.game[data-side='world'] .team-zone {
  display: none;
}

/* ---------- pages (setting, story) ---------- */

.page {
  position: relative;
  font-family: var(--serif);
  color: var(--ink);
  background:
    radial-gradient(ellipse 90% 80% at 50% 40%, transparent 55%, rgba(0, 0, 0, 0.35) 100%),
    var(--page);
  border: 1px solid var(--page-line);
  border-radius: 3px;
  box-shadow:
    0 1px 0 rgba(255, 240, 215, 0.04) inset,
    0 18px 40px -24px rgba(0, 0, 0, 0.9);
  padding: 1.6rem 2rem 1.4rem;
}

.page p {
  font-size: 1.14rem;
  line-height: 1.68;
  max-width: 66ch;
  margin-bottom: 0.9rem;
}

.setting {
  margin-bottom: 2.5rem;
}

.setting .lore-line {
  font-style: italic;
  color: #c9bfae;
  font-size: 1.02rem;
}

/* «Кто ты»: the same words in every story, open until the story begins. */
.lore-fold {
  margin-bottom: 0.4rem;
}

.lore-fold summary {
  cursor: pointer;
  width: fit-content;
  font-family: var(--sans);
  font-size: 0.85rem;
  color: var(--muted);
}

.lore-fold[open] summary {
  margin-bottom: 0.5rem;
}

.setting-meta {
  font-family: var(--sans);
  font-size: 0.88rem !important;
  color: var(--muted);
  margin-bottom: 0 !important;
}

/* Small heads over the briefing and the story's start: two different things on one page. */
.page-head {
  font-family: var(--sans);
  font-size: 0.85rem;
  font-weight: 600;
  color: var(--muted);
  margin: 0.4rem 0 0.35rem;
}

.briefing {
  margin: 0.6rem 0 1.2rem;
  padding-left: 0.9rem;
  border-left: 2px solid var(--page-line);
}

.briefing p {
  margin-bottom: 0;
}

/* How the three are tied and why they are here: before the story's start, quieter. */
.bond {
  margin: 0 0 1.2rem;
  padding-left: 0.9rem;
  border-left: 2px solid var(--page-line);
}

.bond p {
  margin-bottom: 0;
  font-size: 1.02rem !important;
  color: #d8cfbf;
}

/* A name the glossary explains: a quiet dotted line; hover, focus or a tap shows the line. */
.term {
  text-decoration: underline dotted;
  text-decoration-thickness: 1px;
  text-decoration-color: rgba(216, 196, 143, 0.55);
  text-underline-offset: 0.2em;
  cursor: help;
  border-radius: 2px;
}

.term[aria-describedby] {
  text-decoration-color: var(--wanderer);
}

@media (hover: hover) {
  .term:hover {
    text-decoration-color: var(--wanderer);
  }
}

.term:focus-visible {
  outline: 2px solid var(--wanderer);
  outline-offset: 1px;
}

.term-tip {
  position: fixed;
  z-index: 50;
  max-width: 340px;
  padding: 0.55rem 0.75rem;
  background: var(--veil);
  border: 1px solid var(--veil-line);
  border-radius: 6px;
  box-shadow: 0 12px 30px -12px rgba(0, 0, 0, 0.85);
  font-family: var(--sans);
  font-size: 0.86rem;
  line-height: 1.45;
  color: var(--text);
  pointer-events: none;
}

.term-tip b {
  color: var(--ink);
  font-weight: 600;
}

/* ---------- feed ---------- */

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

/* Whatever the page scrolls to lands below the bar (with the phone's tabs in it). */
.turn,
.setting,
.ending,
.order,
.order-review,
.story {
  scroll-margin-top: calc(var(--bar-h) + 1rem);
}

.turn {
  display: grid;
  gap: 1rem;
}

/* «Ход 5» opens every turn, with a line to the edge: where one turn ends, another begins. */
.turn-head {
  display: flex;
  align-items: center;
  gap: 0.7rem;
  margin: 0;
  font-family: var(--serif);
  font-weight: 400;
  font-size: 1.05rem;
  color: var(--ink);
}

.turn-head::after {
  content: '';
  flex: 1;
  height: 1px;
  background: var(--veil-line);
}

.turn-head .turn-note {
  font-family: var(--sans);
  font-size: 0.82rem;
  color: var(--muted);
}

/* An earlier turn: each attempt in one line, the whole of it one click away. */
.attempt-lines {
  list-style: none;
  margin: 0;
  padding: 0;
  display: grid;
  gap: 0.4rem;
}

.attempt-line {
  display: grid;
  grid-template-columns: minmax(0, 1fr) auto;
  align-items: baseline;
  gap: 0.1rem 1rem;
  border-left: 2px solid var(--thread, var(--wanderer));
  padding-left: 0.8rem;
  font-size: 0.93rem;
  color: var(--text);
}

.attempt-line .attempt-who {
  margin-right: 0.3rem;
}

.attempt-line .verdict {
  white-space: nowrap;
  font-size: 0.86rem;
  color: var(--muted);
}

.attempt-line .verdict b {
  font-weight: 600;
}

.attempt-line .verdict .clean {
  color: var(--clean);
}

.attempt-line .verdict .costly {
  color: var(--costly);
}

.attempt-line .verdict .failure {
  color: var(--failure);
}

.attempt-line .observed-text {
  grid-column: 1 / -1;
  font-size: 1rem;
}

.dice-more {
  font-size: 0.88rem;
  color: var(--muted);
}

.dice-more > summary {
  cursor: pointer;
  width: fit-content;
}

.dice-more[open] > summary {
  margin-bottom: 1rem;
}

.order-line {
  border-left: 3px solid var(--thread, var(--wanderer));
  padding: 0.1rem 0 0.1rem 0.9rem;
}

.order-line .to {
  font-size: 0.82rem;
  color: var(--thread, var(--wanderer));
}

.order-line .said {
  font-family: var(--serif);
  font-style: italic;
  font-size: 1.08rem;
  color: var(--text);
  margin: 0.15rem 0 0;
}

.turn[data-slot='1'],
.attempt-line[data-slot='1'],
.attempt[data-slot='1'],
.pending-attempt[data-slot='1'],
.heard-name[data-slot='1'],
.chip[data-slot='1'],
.tried-one[data-slot='1'],
.cast[data-slot='1'],
.why-one[data-slot='1'],
.three-one[data-slot='1'],
.portrait[data-slot='1'],
.card[data-slot='1'] {
  --thread: var(--thread-1);
}

.turn[data-slot='2'],
.attempt-line[data-slot='2'],
.attempt[data-slot='2'],
.pending-attempt[data-slot='2'],
.heard-name[data-slot='2'],
.chip[data-slot='2'],
.tried-one[data-slot='2'],
.cast[data-slot='2'],
.why-one[data-slot='2'],
.three-one[data-slot='2'],
.portrait[data-slot='2'],
.card[data-slot='2'] {
  --thread: var(--thread-2);
}

.turn[data-slot='3'],
.attempt-line[data-slot='3'],
.attempt[data-slot='3'],
.pending-attempt[data-slot='3'],
.heard-name[data-slot='3'],
.chip[data-slot='3'],
.tried-one[data-slot='3'],
.cast[data-slot='3'],
.why-one[data-slot='3'],
.three-one[data-slot='3'],
.portrait[data-slot='3'],
.card[data-slot='3'] {
  --thread: var(--thread-3);
}

/* A joint attempt or a turn of several threads reads in the Wanderer's colour. */
.attempt[data-slot='all'],
.attempt-line[data-slot='all'],
.tried-one[data-slot='all'],
.cast[data-slot='all'],
.why-one[data-slot='all'],
.pending-attempt[data-slot='all'] {
  --thread: var(--wanderer);
}

/* Each attempt of a turn: who and what, its tier, its own roll. */
.attempts {
  display: grid;
  gap: 1.4rem;
}

.attempt {
  display: grid;
  gap: 0.6rem;
}

.attempt-head {
  margin: 0;
  font-size: 0.95rem;
  color: var(--text);
}

.attempt-who {
  color: var(--thread, var(--wanderer));
  font-weight: 600;
}

/* Several attempts: a quieter block each, marked by its thread, the dice a little smaller. */
.attempts:not([data-count='1']) .attempt {
  border-left: 2px solid var(--thread, var(--wanderer));
  padding-left: 0.9rem;
}

.attempts:not([data-count='1']) .tier {
  font-size: 1.35rem;
}

.pending-attempt {
  border-left: 2px solid var(--thread, var(--wanderer));
  padding-left: 0.9rem;
  display: grid;
  gap: 0.3rem;
}

.pending-attempt .pending-note {
  margin: 0;
}

/* An observation without a roll: what the person learned, instead of the dice. */
.observed-label {
  margin: 0;
  font-family: var(--sans);
  font-size: 0.85rem;
  font-weight: 600;
  color: var(--muted);
}

.observed-text {
  margin: 0;
  max-width: 64ch;
  color: var(--ink);
  font-family: var(--serif);
  font-size: 1.05rem;
  line-height: 1.55;
}

/* The clock moved beyond its step, and why. */
.story .shift-note {
  font-family: var(--sans);
  font-size: 0.92rem !important;
  color: var(--clean);
  margin: 0.2rem 0 0.8rem;
}

.story .shift-note.closer {
  color: var(--failure);
}

/*
 * The turn's sum on the clock, part by part, and what is left: «ход −1 · сделан шаг +1 →
 * осталось 7 ходов». A quiet line under the story, each move in the colour of its way.
 */
.story .clock-ledger {
  margin: 0.9rem 0 0.4rem;
  padding-top: 0.55rem;
  border-top: 1px dashed var(--page-line);
  font-family: var(--sans);
  font-size: 0.86rem !important;
  line-height: 1.5;
  color: var(--muted);
}

.clock-ledger .clock-label {
  color: var(--faint);
}

.clock-ledger .minus {
  color: color-mix(in srgb, var(--failure) 85%, var(--ink));
}

.clock-ledger .plus {
  color: var(--clean);
}

.clock-ledger .clock-sep {
  color: var(--faint);
}

.clock-ledger .clock-left {
  font-weight: 600;
  color: var(--ink);
}

.clock-ledger.closer .clock-left {
  color: color-mix(in srgb, var(--failure) 70%, var(--ink));
}

/* A step of the goal made this turn. */
.story .step-note {
  font-family: var(--sans);
  font-size: 0.95rem !important;
  font-weight: 600;
  color: var(--clean);
  margin: 0.2rem 0 0.6rem;
}

.story .step-note::before {
  content: '✓ ';
}

.story .step-note.failed {
  color: var(--failure);
}

.story .step-note.failed::before {
  content: '✕ ';
}

/* A step told just now: it comes in once, its mark first. */
.story .step-note.arrive {
  animation: note-in 0.7s ease-out 1 backwards;
}

.story .step-note.arrive::before {
  display: inline-block;
  animation: mark-in 0.7s cubic-bezier(0.3, 1.6, 0.5, 1) 1 backwards;
}

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

@keyframes mark-in {
  from {
    transform: scale(2.2);
    opacity: 0;
  }
}

.story .step-note.early,
.story .step-note.cancelled {
  color: var(--muted);
}

.story .step-note.early::before {
  content: '◌ ';
}

.story .step-note.cancelled::before {
  content: '↺ ';
}

.story .step-proof {
  font-weight: 400;
  color: var(--text);
}

/* What could not be done this turn: said plainly after the story, never queued. */
.late {
  border: 1px dashed var(--veil-line);
  border-radius: 6px;
  padding: 0.7rem 0.9rem;
  color: var(--text);
  font-size: 0.92rem;
}

.late-head {
  margin: 0 0 0.3rem;
  font-weight: 600;
  color: var(--costly);
}

.late ul {
  margin: 0;
  padding-left: 1.1rem;
}

.late-note {
  margin: 0.35rem 0 0;
  font-size: 0.8rem;
  color: var(--muted);
}

/* «Не произошло: …» says it in every line; no head and no bullets over it. */
.late.claims ul {
  list-style: none;
  padding-left: 0;
}

.late.claims li + li {
  margin-top: 0.2rem;
}

.chance {
  display: grid;
  gap: 0.3rem;
}

.tier-line {
  display: flex;
  align-items: baseline;
  gap: 0.8rem;
  flex-wrap: wrap;
}

.tier {
  font-family: var(--serif);
  font-size: 1.7rem;
  line-height: 1.1;
  color: var(--ink);
}

.threshold {
  color: var(--muted);
  font-size: 0.95rem;
}

.explanation {
  color: var(--text);
  max-width: 64ch;
  margin: 0;
}

.why {
  color: var(--muted);
  font-size: 0.88rem;
}

.why summary {
  cursor: pointer;
  width: fit-content;
}

.why p,
.why ul {
  margin: 0.4rem 0 0;
}

.why ul {
  padding-left: 1.1rem;
}

/* ---------- the roll: a bead runs round the whorl ---------- */

/*
 * A spindle whorl: the ring is the hundred of the d100, from the top clockwise — the clean
 * success, the success at a cost, the failure, each as long as its chance and each drawn its
 * own way (a full thread, a twisted one, a thin frayed one). The bead runs round and stops at
 * the roll; the arc it stopped on lights, the others dim.
 */
.cast {
  display: grid;
  grid-template-columns: auto minmax(0, 1fr);
  align-items: center;
  gap: 0.4rem 1.2rem;
}

.whorl {
  display: block;
  width: 118px;
  height: 108px;
  overflow: visible;
}

.whorl .arc {
  fill: none;
  stroke-linecap: butt;
  opacity: 0.6;
  transition:
    opacity 0.3s,
    stroke-width 0.3s;
}

.arc.clean {
  stroke: var(--clean);
  stroke-width: 8;
}

.arc.costly {
  stroke: var(--costly);
  stroke-width: 8;
}

.whorl .arc.core {
  stroke: var(--void-deep);
  stroke-width: 2.6;
  opacity: 1;
}

.arc.failure {
  stroke: var(--failure);
  stroke-width: 3.5;
  stroke-dasharray: 0.1 3.4;
  stroke-linecap: round;
}

/* The arc the bead runs over while it runs. */
.cast.rolling .arc.under {
  opacity: 1;
}

/* Landed: the arc of what fell is whole and bright, the others step back. */
.cast.landed .arc {
  opacity: 0.22;
}

.cast.landed[data-outcome='clean'] .arc.clean,
.cast.landed[data-outcome='costly'] .arc.costly:not(.core) {
  opacity: 1;
  stroke-width: 11;
}

.cast.landed[data-outcome='costly'] .arc.core {
  stroke-width: 3.5;
}

.cast.landed[data-outcome='failure'] .arc.failure {
  opacity: 1;
  stroke-width: 5.5;
}

/* Where the hundred begins: a short line across the ring at the top. */
.whorl .start {
  stroke: var(--muted);
  stroke-width: 1.5;
}

.whorl .trail {
  fill: none;
  stroke: var(--wanderer);
  stroke-width: 3;
  stroke-linecap: round;
  opacity: 0.45;
}

.whorl .bead {
  fill: var(--wanderer);
  stroke: var(--void-deep);
  stroke-width: 2;
}

.cast.waiting .bead {
  opacity: 0.45;
}

/* One ring of light where the bead stops, once. */
.whorl .ripple {
  fill: none;
  stroke: var(--wanderer);
  stroke-width: 2;
  opacity: 0;
  transform-box: fill-box;
  transform-origin: center;
}

.cast.flash .ripple {
  animation: ripple 0.7s ease-out 1;
}

@keyframes ripple {
  0% {
    opacity: 0.9;
    transform: scale(1);
  }
  100% {
    opacity: 0;
    transform: scale(3.2);
  }
}

.whorl .roll-number {
  font-family: var(--serif);
  font-size: 30px;
  font-variant-numeric: tabular-nums;
  fill: var(--ink);
}

.cast.waiting .roll-number {
  fill: var(--faint);
}

.cast-side {
  display: grid;
  gap: 0.45rem;
  min-width: 0;
}

/* The three zones in words: what number gives what. */
.cast-legend {
  margin: 0;
  display: flex;
  flex-wrap: wrap;
  gap: 0.15rem 1.1rem;
  font-size: 0.85rem;
  color: var(--muted);
}

.cast-legend .zone-word {
  white-space: nowrap;
}

/* An outcome by shape as well as colour: a full knot, a half one, a cross. */
.omark {
  display: inline-block;
  width: 0.62em;
  height: 0.62em;
  margin-right: 0.35em;
  border: 1.5px solid currentColor;
  border-radius: 50%;
  vertical-align: 0.02em;
}

.omark.clean {
  color: var(--clean);
  background: currentColor;
}

.omark.costly {
  color: var(--costly);
  background: linear-gradient(90deg, currentColor 50%, transparent 50%);
}

.omark.failure {
  position: relative;
  color: var(--failure);
  border: 0;
  border-radius: 0;
}

.omark.failure::before,
.omark.failure::after {
  content: '';
  position: absolute;
  left: -0.08em;
  right: -0.08em;
  top: calc(50% - 1px);
  height: 2px;
  background: currentColor;
  transform: rotate(45deg);
}

.omark.failure::after {
  transform: rotate(-45deg);
}

.cast-result {
  margin: 0;
  font-family: var(--serif);
  font-size: 1.4rem;
  line-height: 1.2;
  transition:
    opacity 0.35s,
    transform 0.35s;
}

.cast-result .omark {
  width: 0.55em;
  height: 0.55em;
}

.cast:not(.landed) .cast-result {
  opacity: 0;
  transform: translateY(4px);
}

.cast[data-outcome='clean'] .cast-result {
  color: var(--clean);
}

.cast[data-outcome='costly'] .cast-result {
  color: var(--costly);
}

.cast[data-outcome='failure'] .cast-result {
  color: var(--failure);
}

/* Where success ends (and the clean one), written on the ring. */
.whorl .bound {
  stroke: var(--muted);
  stroke-width: 1.2;
}

.whorl .ring-label {
  font-family: var(--sans);
  font-size: 11px;
  font-variant-numeric: tabular-nums;
  fill: var(--muted);
}

.whorl .ring-label.threshold {
  fill: var(--text);
  font-weight: 600;
}

/* The tier and the chance: a quiet line; the outcome under it is what the eye finds. */
.cast-chance {
  margin: 0;
  font-size: 0.88rem;
  color: var(--muted);
}

/* Alone, the zones in words only until the bead stops; then the ring and the outcome say it. */
.cast.landed .cast-legend {
  display: none;
}

.cast-result {
  font-size: 1.6rem;
}

/* ---------- two or three rolls in one row ---------- */

.attempts.in-rows {
  gap: 0.9rem;
}

/* What each tried, above the row. */
.tried {
  list-style: none;
  margin: 0;
  padding: 0;
  display: grid;
  gap: 0.45rem;
}

.tried-one {
  border-left: 2px solid var(--thread, var(--wanderer));
  padding-left: 0.8rem;
}

.tried-one .observed-label {
  margin-top: 0.2rem;
}

.cast-row {
  display: grid;
  /* All in one row while each has room for its ring; with a very big font the last goes under. */
  grid-template-columns: repeat(auto-fit, minmax(min(100%, 5.5rem), 1fr));
  gap: 0.6rem;
  max-width: 35rem;
}

.cast-row[data-count='2'] {
  max-width: 23.75rem;
}

.cast.in-row {
  grid-template-columns: minmax(0, 1fr);
  justify-items: center;
  text-align: center;
  gap: 0.25rem;
}

.cast.in-row .whorl {
  width: 104px;
  height: 104px;
}

.cast.in-row .cast-side {
  justify-items: center;
  gap: 0.2rem;
}

.cast-who {
  margin: 0;
  font-weight: 600;
  font-size: 0.92rem;
  color: var(--thread, var(--wanderer));
}

.cast.in-row .cast-chance {
  font-size: 0.8rem;
}

.cast.in-row .cast-result {
  font-size: 1.15rem;
}

/* What the marks mean, once under the row. */
.cast-key {
  margin: 0;
  display: flex;
  flex-wrap: wrap;
  gap: 0.15rem 1rem;
  font-size: 0.8rem;
  color: var(--muted);
}

.cast-key .zone-word {
  white-space: nowrap;
}

/* The reasons of the row, each under its name. */
.why-all .why-one {
  margin-top: 0.7rem;
  padding-left: 0.8rem;
  border-left: 2px solid var(--thread, var(--wanderer));
}

.why-all .why-head {
  margin: 0;
  color: var(--text);
}

.why-all .explanation {
  margin-top: 0.3rem;
  font-size: 0.9rem;
}

.story .noticed {
  font-family: var(--sans);
  font-size: 0.86rem;
  color: var(--muted);
  border-top: 1px solid var(--page-line);
  padding-top: 0.7rem;
  margin-top: 0.3rem;
}

.story .noticed summary {
  cursor: pointer;
  width: fit-content;
}

.story .noticed ul {
  margin: 0.5rem 0 0;
  padding-left: 1.1rem;
}

.story.waiting p {
  color: var(--muted);
  font-style: italic;
}

/* ---------- waiting steps ---------- */

.steps {
  font-family: var(--sans);
  list-style: none;
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 0.3rem 0.5rem;
  margin: 0;
  padding: 0;
  font-size: 0.86rem;
  color: var(--faint);
}

.steps li + li::before {
  content: '';
  display: inline-block;
  width: 1.2rem;
  height: 1px;
  margin-right: 0.5rem;
  vertical-align: middle;
  background: var(--veil-line);
}

.steps .done {
  color: var(--muted);
}

.steps .current {
  color: var(--wanderer);
  animation: pulse 1.6s ease-in-out infinite;
}

.steps .elapsed {
  margin-left: auto;
  color: var(--faint);
  font-variant-numeric: tabular-nums;
}

.steps .elapsed::before {
  display: none;
}

/* Nothing is running for this turn now (see the retry below): no pulse. */
.steps.stalled .current {
  animation: none;
  color: var(--costly);
}

@keyframes pulse {
  50% {
    opacity: 0.45;
  }
}

.pending-note {
  color: var(--text);
  margin: 0;
}

.ending {
  border-top: 1px solid var(--veil-line);
  border-bottom: 1px solid var(--veil-line);
  padding: 1.3rem 1rem;
  margin: 3rem 0 0;
  display: grid;
  gap: 0.9rem;
}

/* Three threads over the end: tied for a win, two tied and one frayed, or all frayed. */
.ending-knot {
  justify-self: center;
  width: 120px;
  height: 64px;
  overflow: visible;
}

.ending-knot .thread {
  opacity: 0.9;
  stroke-width: 2;
  stroke-linecap: round;
}

.ending-knot .thread.tail {
  stroke: var(--wanderer);
  opacity: 0.7;
}

.ending-knot .fray {
  stroke-width: 1.2;
  opacity: 0.7;
}

.ending-knot .tie {
  fill: var(--wanderer);
}

/* The end came while the page looked: the threads are drawn in, then the degree. */
.ending.arrive .ending-knot .thread {
  stroke-dasharray: 90;
  animation: draw-thread 0.9s ease-out 1 backwards;
}

.ending.arrive .ending-knot .tie {
  animation: tie 0.4s ease-out 0.7s 1 backwards;
}

.ending.arrive .ending-degree {
  animation: note-in 0.8s ease-out 0.5s 1 backwards;
}

@keyframes draw-thread {
  from {
    stroke-dashoffset: 90;
  }
  to {
    stroke-dashoffset: 0;
  }
}

@keyframes tie {
  from {
    transform: scale(0);
    transform-origin: 60px 42px;
  }
}

.ending-line {
  font-family: var(--serif);
  font-size: 1.2rem;
  text-align: center;
  color: var(--wanderer);
  margin: 0;
}

.ending-degree {
  font-family: var(--serif);
  font-size: 2.2rem;
  text-align: center;
  color: var(--wanderer);
  margin: 0;
}

/* The degree in the colour of how it went. */
.ending[data-degree='win'] .ending-degree {
  color: var(--clean);
}

.ending[data-degree='partial'] .ending-degree {
  color: var(--costly);
}

.ending[data-degree='loss'] .ending-degree {
  color: var(--failure);
}

.ending-degree + .ending-line {
  font-size: 0.95rem;
  color: var(--muted);
}

/* The end is read like the story: its text in a column of the story's measure, centred; the
   line under the degree and the wait for the epilogue no wider than it. */
.ending > .ending-line,
.ending > .ending-waiting,
.ending > .ending-epilogue,
.ending > .ending-head,
.ending > .ending-fates,
.ending > .ending-lasting {
  width: 100%;
  max-width: 42rem;
  justify-self: center;
}

.ending-epilogue p {
  font-family: var(--serif);
  font-size: 1.08rem;
  line-height: 1.65;
  margin: 0 0 0.7rem;
}

.ending-fates,
.ending-lasting {
  margin: 0;
  padding-left: 1.2rem;
  display: grid;
  gap: 0.35rem;
}

.ending-head {
  font-size: 0.9rem;
  font-weight: 600;
  color: var(--muted);
  margin: 0.4rem 0 0;
}

.ending-fates b {
  font-weight: 600;
}

.ending-fates li[data-slot='1'] b {
  color: var(--thread-1);
}

.ending-fates li[data-slot='2'] b {
  color: var(--thread-2);
}

.ending-fates li[data-slot='3'] b {
  color: var(--thread-3);
}

/* Nobody on a thread knows what became of him: said as it is, quietly. */
.ending-fates li.unknown {
  color: var(--muted);
  font-style: italic;
}

.ending-fates li.unknown b {
  font-style: normal;
}

.fate-cut {
  display: inline-block;
  margin-left: 0.5rem;
  padding: 0 0.45rem;
  border: 1px solid color-mix(in srgb, var(--failure) 55%, transparent);
  border-radius: 999px;
  font-family: var(--sans);
  font-size: 0.72rem;
  font-style: normal;
  line-height: 1.45;
  color: color-mix(in srgb, var(--failure) 80%, var(--ink));
  vertical-align: 0.1em;
}

.ending-waiting {
  text-align: center;
  color: var(--muted);
  font-style: italic;
  margin: 0;
}

/* ---------- order form ---------- */

.order {
  margin-top: 3rem;
  display: grid;
  /* The field's own width (its columns) never widens the column. */
  grid-template-columns: minmax(0, 1fr);
  gap: 0.6rem;
}

/* «С чего начать»: examples of a first order, before the first turn; a tap fills the field. */
.starters {
  padding: 0.8rem 1rem 0.6rem;
  border: 1px dashed var(--veil-line);
  border-radius: 0.6rem;
  background: var(--veil);
}

.starters-head {
  margin: 0 0 0.5rem;
  font-family: var(--serif);
  font-size: 1rem;
  font-weight: 600;
  color: var(--wanderer);
}

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

.starter {
  width: 100%;
  text-align: left;
  cursor: pointer;
  padding: 0.45rem 0.75rem;
  border: 1px solid var(--veil-line);
  border-radius: 0.5rem;
  background: var(--void);
  color: var(--text);
  font: inherit;
  font-size: 0.92rem;
  line-height: 1.4;
}

.starter::before {
  content: '«';
  color: var(--wanderer);
}

.starter::after {
  content: '»';
  color: var(--wanderer);
}

.starter:hover,
.starter:focus-visible {
  border-color: var(--wanderer);
  outline: none;
}

.starters-note {
  margin: 0.5rem 0 0;
  font-size: 0.8rem;
  color: var(--faint);
}

/* Who hears the order being typed: the names it mentions, or everybody. */
.order-heard {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 0.35rem 0.5rem;
  margin: 0;
  font-size: 0.88rem;
  color: var(--muted);
}

.heard-name {
  display: inline-flex;
  align-items: center;
  gap: 0.4rem;
  padding: 0.18rem 0.7rem 0.18rem 0.5rem;
  border: 1px solid var(--thread, var(--veil-line));
  border-radius: 999px;
  background: var(--veil);
  color: var(--ink);
}

.heard-doubt {
  color: var(--costly);
}

.heard-name::before {
  content: '';
  width: 0.5rem;
  height: 0.5rem;
  border-radius: 50%;
  background: var(--thread, var(--wanderer));
}

.chip {
  cursor: pointer;
  border: 1px solid var(--veil-line);
  background: var(--veil);
  color: var(--text);
  border-radius: 999px;
  padding: 0.3rem 0.85rem 0.3rem 0.6rem;
  display: inline-flex;
  align-items: center;
  gap: 0.45rem;
}

/* A chip for one of the three carries its thread's colour as a dot. */
.chip[data-slot]::before {
  content: '';
  width: 0.55rem;
  height: 0.55rem;
  border-radius: 50%;
  background: var(--thread, var(--wanderer));
}

.chip[data-slot='all']::before {
  background: conic-gradient(var(--thread-1) 0 33%, var(--thread-2) 0 66%, var(--thread-3) 0);
}

.chip[aria-checked='true'] {
  border-color: var(--thread, var(--wanderer));
  color: var(--ink);
  background: #25283d;
}

.chip:disabled {
  opacity: 0.4;
  cursor: not-allowed;
}

.order-row {
  display: flex;
  gap: 0.6rem;
  align-items: flex-end;
}

.order textarea {
  flex: 1;
  min-width: 0;
  resize: none;
  /* Not under 16px: a phone would zoom the page in when the field is tapped. */
  font-size: max(1rem, 16px);
  background: var(--veil);
  border: 1px solid var(--veil-line);
  border-radius: 10px;
  padding: 0.6rem 0.8rem;
  line-height: 1.45;
  max-height: 9.5rem;
}

.order textarea:focus {
  border-color: var(--wanderer);
  outline: none;
}

.order textarea:focus-visible {
  box-shadow: 0 0 0 1px var(--wanderer);
}

.order-hint {
  font-size: 0.78rem;
  color: var(--faint);
  margin: 0;
}

.stuck {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 0.5rem 0.9rem;
  font-size: 0.88rem;
  color: var(--muted);
}

.stuck p {
  margin: 0;
  flex: 1 1 18rem;
}

/* Those the order does not name: dimmed, crossed out, so it is plain who will not hear. */
.heard-name.off {
  border-style: dashed;
  border-color: var(--veil-line);
  background: none;
  color: var(--faint);
  text-decoration: line-through;
}

.heard-name.off::before {
  background: none;
  border: 1px solid var(--faint);
}

/* A turn is running: the hint says so plainly. */
.order-hint.busy {
  color: var(--wanderer);
}

/* «Понято так»: the reading of an order before the roll, in the manner of a turn. */
.order-review {
  margin-top: 3rem;
  display: grid;
  gap: 0.8rem;
  border-left: 3px solid var(--wanderer);
  padding: 0.2rem 0 0.2rem 0.9rem;
}

.order-review + .order {
  margin-top: 1.4rem;
}

.order-review-actions {
  display: flex;
  flex-wrap: wrap;
  gap: 0.5rem;
}

/* A question or a deed, and what it waits for: a quiet line under each attempt of the reading. */
.review-kind {
  margin: 0;
  font-size: 0.82rem;
  color: var(--muted);
}

/* The narrator is not sure whom a word is about: asked under the attempt, plainly. */
.review-doubt {
  display: flex;
  align-items: baseline;
  gap: 0.45rem;
  margin: 0.1rem 0 0;
  font-size: 0.92rem;
  color: var(--costly);
}

.doubt-mark {
  flex: none;
  display: inline-grid;
  place-items: center;
  width: 1.15rem;
  height: 1.15rem;
  border: 1.5px solid currentColor;
  border-radius: 50%;
  font-size: 0.75rem;
  font-weight: 700;
  line-height: 1;
  transform: translateY(0.05rem);
}

/* Someone spoken to cannot act now: his part waits. */
.unable-note {
  border-style: solid;
  border-color: color-mix(in srgb, var(--costly) 55%, var(--veil-line));
}

.unable-note ul {
  list-style: none;
  padding-left: 0;
}

/* Slips the narrator read by their sense. */
.review-fixes {
  margin: 0;
  font-size: 0.88rem;
  color: var(--muted);
}

/* «Разбираю приказ…» while the narrator reads it; «В очереди: перед тобой N» while it waits. */
.pending-wait {
  color: var(--muted);
  font-style: italic;
}

/* «Отменить приказ» under a turn still before its dice. */
.pending-cancel {
  margin-top: 0.5rem;
  width: fit-content;
  align-self: flex-start;
  justify-self: start;
}

/* The administrator in someone else's story: where the order field would be. */
.read-only-note {
  margin-top: 3rem;
  font-style: italic;
}

/*
 * «К приказу»: rides at the bottom of the story column while the order field is out of sight.
 * It stands on a band of its own: the story fades out into the night under it, so the button is
 * never over a line being read (scrolling brings the line up above the band).
 */
.jump-dock {
  position: sticky;
  bottom: 0;
  z-index: 12;
  display: flex;
  justify-content: flex-end;
  align-items: flex-start;
  height: 0;
  overflow: visible;
  pointer-events: none;
}

.jump-dock:has(> .jump:not([hidden]))::before {
  content: '';
  position: absolute;
  left: calc(-1 * var(--gutter));
  right: calc(-1 * var(--gutter));
  bottom: 0;
  height: calc(4.6rem + var(--safe-b));
  background: linear-gradient(
    to bottom,
    color-mix(in srgb, var(--void) 0%, transparent),
    color-mix(in srgb, var(--void) 92%, transparent) 42%,
    var(--void) 70%
  );
}

.jump {
  position: relative;
  pointer-events: auto;
  cursor: pointer;
  display: inline-flex;
  align-items: center;
  gap: 0.55rem;
  transform: translateY(calc(-100% - 0.85rem - var(--safe-b)));
  padding: 0.5rem 1rem 0.5rem 0.75rem;
  border: 1px solid var(--wanderer);
  border-radius: 999px;
  background: rgba(28, 30, 48, 0.96);
  color: var(--ink);
  font-size: 0.9rem;
  box-shadow: 0 10px 26px -10px rgba(0, 0, 0, 0.9);
}

/* A knot with its thread running down: where the order is. */
.jump::before {
  content: '';
  width: 0.55rem;
  height: 0.55rem;
  border-radius: 50%;
  background: var(--wanderer);
  box-shadow: 0 0.45rem 0 -0.2rem var(--wanderer);
}

@media (hover: hover) {
  .jump:hover {
    background: #25283d;
  }
}

/* ---------- feedback ---------- */

.end-line {
  display: flex;
  justify-content: flex-end;
  margin: 2.2rem 0 0;
  padding-top: 0.8rem;
  border-top: 1px dashed var(--veil-line);
  font-size: 0.82rem;
}

.end-line:has(> [hidden]) {
  display: none;
}

/* Ending the story: it cannot be undone, so it looks like a warning, not like a link. */
.report[data-end-story] {
  color: color-mix(in srgb, var(--failure) 80%, var(--text));
}

.report-line {
  display: flex;
  justify-content: flex-end;
  align-items: center;
  flex-wrap: wrap;
  gap: 0.3rem 0.8rem;
  margin: 0.6rem 0 0;
  font-size: 0.82rem;
}

.report {
  cursor: pointer;
  background: none;
  border: none;
  padding: 0;
  color: var(--faint);
  text-decoration: underline;
  text-underline-offset: 3px;
}

@media (hover: hover) {
  .report:hover {
    color: var(--muted);
  }
}

.ending-feedback {
  text-align: center;
  margin: 0;
}

/* After the end: what to do next. */
.ending-actions {
  display: flex;
  flex-wrap: wrap;
  justify-content: center;
  gap: 0.6rem;
  margin: 0.4rem 0 0;
}

a.chip {
  text-decoration: none;
}

@media (hover: hover) {
  .chip:hover {
    border-color: color-mix(in srgb, var(--wanderer) 60%, var(--veil-line));
  }
}

.survey {
  width: min(34rem, calc(100% - 2rem));
  max-height: calc(100% - 2rem);
  max-height: calc(100dvh - 2rem);
  overflow: auto;
  padding: 1.2rem 1.3rem;
  color: var(--text);
  background: var(--veil);
  border: 1px solid var(--veil-line);
  border-radius: 12px;
}

.survey::backdrop {
  background: rgb(8 9 16 / 70%);
}

.survey h2 {
  color: var(--ink);
}

.survey-rating {
  display: flex;
  flex-wrap: wrap;
  gap: 0.4rem 0.9rem;
  border: none;
  padding: 0;
  margin: 0.8rem 0;
}

.survey-rating legend {
  color: var(--muted);
  font-size: 0.9rem;
  margin-bottom: 0.3rem;
}

/* The rating as five small pills; the chosen one lit. */
.survey-rating label {
  display: inline-flex;
  align-items: center;
  gap: 0.3rem;
  cursor: pointer;
  padding: 0.2rem 0.75rem 0.2rem 0.5rem;
  border: 1px solid var(--veil-line);
  border-radius: 999px;
}

.survey-rating label:has(input:checked) {
  border-color: var(--wanderer);
  color: var(--ink);
  background: #25283d;
}

.survey-rating input {
  accent-color: var(--wanderer);
  margin: 0;
}

.survey-field {
  display: grid;
  gap: 0.3rem;
  margin: 0.8rem 0;
  font-size: 0.92rem;
}

.survey-field textarea {
  width: 100%;
  resize: vertical;
  font-size: max(1rem, 16px);
  background: var(--void);
  border: 1px solid var(--veil-line);
  border-radius: 8px;
  padding: 0.5rem 0.6rem;
}

.survey-buttons {
  display: flex;
  flex-wrap: wrap;
  justify-content: flex-end;
  gap: 0.6rem;
  margin-top: 0.8rem;
}

/* The administrator's page of feedback. */
.feedback-admin h1 {
  font-family: var(--serif);
  font-weight: 400;
  color: var(--ink);
}

.feedback-filter {
  display: flex;
  flex-wrap: wrap;
  gap: 0.5rem;
  margin: 1rem 0;
}

.feedback-list {
  list-style: none;
  padding: 0;
  margin: 0;
}

.feedback-entry {
  padding: 0.9rem 0;
  border-bottom: 1px solid var(--veil-line);
}

.feedback-entry a {
  color: var(--wanderer);
}

.feedback-entry .meta {
  color: var(--muted);
  font-size: 0.88rem;
}

.feedback-texts {
  margin: 0.4rem 0 0;
}

.feedback-texts dt {
  margin-top: 0.5rem;
  color: var(--faint);
  font-size: 0.8rem;
}

.feedback-texts dd {
  margin: 0.1rem 0 0;
  white-space: pre-wrap;
  overflow-wrap: anywhere;
}

/* The administrator's mark on a feedback entry (stage 3c): a quiet word; «new» in gold. */
.mark-badge {
  display: inline-block;
  padding: 0 0.45rem;
  border-radius: 999px;
  border: 1px solid var(--veil-line);
  font-size: 0.75rem;
  color: var(--muted);
}

.mark-badge.new {
  border-color: var(--wanderer);
  color: var(--wanderer);
}

.mark-badge.fixed {
  border-color: var(--clean);
  color: var(--ink);
}

.mark-actions {
  display: flex;
  flex-wrap: wrap;
  gap: 0.4rem;
  margin: 0.6rem 0 0;
}

/* ---------- threads and cards ---------- */

.loom {
  position: relative;
  padding-top: 3.2rem;
}

.loom-threads {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  pointer-events: none;
  overflow: visible;
}

.wanderer {
  position: absolute;
  top: 0;
  left: 0;
  font-family: var(--serif);
  font-style: italic;
  color: var(--wanderer);
  font-size: 0.95rem;
  padding: 0.1rem 0.6rem;
}

.thread {
  fill: none;
  stroke-width: 1.6;
  opacity: 0.55;
  transition:
    opacity 0.25s,
    stroke-width 0.25s;
}

.thread.t1 {
  stroke: var(--thread-1);
}

.thread.t2 {
  stroke: var(--thread-2);
}

.thread.t3 {
  stroke: var(--thread-3);
}

.thread.lit {
  opacity: 1;
  stroke-width: 2.6;
  filter: drop-shadow(0 0 4px currentColor);
}

.thread.pulling {
  animation: pulse 1.4s ease-in-out infinite;
  opacity: 1;
}

.thread.loose {
  stroke-dasharray: 5 5;
  opacity: 0.45;
}

.thread.loose.snap {
  animation: snap 1s ease-out 1;
}

@keyframes snap {
  0% {
    opacity: 1;
    stroke: var(--failure);
    stroke-width: 3;
    stroke-dasharray: 0 0;
  }
  40% {
    opacity: 1;
    stroke-width: 3;
  }
}

.knot {
  fill: var(--wanderer);
}

.team {
  display: grid;
  gap: 1.1rem;
  padding-left: 2.2rem;
}

.card {
  position: relative;
  display: grid;
  gap: 0.55rem;
  background: var(--veil);
  border: 1px solid var(--veil-line);
  border-left: 3px solid var(--thread);
  border-radius: 6px;
  padding: 0.8rem 0.95rem 0.7rem;
}

/* Called from the strip of the three: the card lights once. */
.card.called {
  animation: called 1s ease-out 1;
}

@keyframes called {
  from {
    box-shadow: 0 0 0 2px var(--thread);
  }
  to {
    box-shadow: 0 0 0 2px transparent;
  }
}

/* A cut thread: dashed and quieter, but still readable. */
.card.lost {
  border-left-style: dashed;
  background: transparent;
}

.card.lost h3,
.card.lost p,
.card.lost li {
  color: var(--muted);
}

/* Cut while the page looks: the card flares once and goes out. */
.card.just-cut {
  animation: cut 1s ease-out 1;
}

@keyframes cut {
  0% {
    background: color-mix(in srgb, var(--failure) 22%, var(--veil));
    border-left-color: var(--failure);
  }
  100% {
    background: transparent;
  }
}

/* The portrait, its name, trade and place. */
.card-head {
  display: grid;
  grid-template-columns: auto minmax(0, 1fr);
  gap: 0.8rem;
  align-items: center;
}

.card h3 {
  font-family: var(--serif);
  font-weight: 400;
  font-size: 1.15rem;
  color: var(--ink);
  margin: 0;
}

/* His trade, right under the name, in his thread's colour. */
.card .trade {
  margin: 0.05rem 0 0;
  font-size: 0.9rem;
  color: var(--thread);
}

.card .where {
  margin: 0.15rem 0 0;
  font-size: 0.86rem;
  color: var(--muted);
}

.card .where .ico {
  color: var(--faint);
}

.card .who {
  color: var(--muted);
  font-size: 0.86rem;
  margin: 0;
}

.card .lost-note {
  margin: 0;
  font-size: 0.88rem;
}

/* A portrait in a ring of the thread's colour: a picture, or the first letter of the name. */
.portrait {
  position: relative;
  flex: none;
  display: grid;
  place-items: center;
  width: 58px;
  height: 58px;
  border-radius: 50%;
  overflow: hidden;
  background:
    repeating-conic-gradient(
      from 0deg,
      color-mix(in srgb, var(--thread, var(--wanderer)) 14%, transparent) 0 6deg,
      transparent 6deg 12deg
    ),
    var(--void-deep);
  box-shadow:
    0 0 0 2px var(--thread, var(--wanderer)),
    0 0 0 4px var(--veil),
    0 0 0 5px color-mix(in srgb, var(--thread, var(--wanderer)) 35%, transparent);
}

.portrait img {
  width: 100%;
  height: 100%;
  object-fit: cover;
  /* A portrait is 3:4: the face is in its upper part. */
  object-position: 50% 22%;
}

/* A portrait being drawn: the letter, a dashed ring and a quiet word; nothing blinks. */
.portrait.drawing {
  box-shadow:
    0 0 0 2px var(--veil),
    0 0 0 3px color-mix(in srgb, var(--thread, var(--wanderer)) 60%, transparent);
  outline: 1px dashed color-mix(in srgb, var(--thread, var(--wanderer)) 70%, transparent);
  outline-offset: 3px;
}

.card .drawing-note {
  margin: 0.05rem 0 0;
  font-size: 0.8rem;
  color: var(--muted);
}

.portrait .mono-letter {
  font-family: var(--serif);
  font-size: 1.7rem;
  line-height: 1;
  color: var(--ink);
}

.portrait.small {
  width: 38px;
  height: 38px;
  box-shadow:
    0 0 0 1.5px var(--thread, var(--wanderer)),
    0 0 0 3px var(--void-deep);
}

.portrait.small .mono-letter {
  font-size: 1.1rem;
}

.card.down .portrait,
.three-one.down .portrait {
  filter: saturate(0.4) brightness(0.8);
}

.card.lost .portrait,
.three-one.lost .portrait {
  filter: grayscale(1) brightness(0.7);
  box-shadow:
    0 0 0 2px transparent,
    0 0 0 3px var(--veil-line);
}

/* He cannot act now: first on the card, with the way back; the thread holds (not a loss). */
.card .down-note {
  margin: 0;
  padding: 0.35rem 0.6rem;
  border-radius: 6px;
  background: color-mix(in srgb, var(--costly) 14%, transparent);
  color: var(--ink);
  font-size: 0.9rem;
}

.card .down-note .ico,
.unable-note .ico {
  color: var(--costly);
}

.heard-name.down {
  border-style: dashed;
}

.heard-down {
  color: var(--costly);
  font-size: 0.82rem;
}

.skills {
  list-style: none;
  padding: 0;
  margin: 0;
  display: flex;
  flex-wrap: wrap;
  gap: 0.3rem;
}

.skills li {
  display: inline-flex;
  align-items: center;
  gap: 0.35rem;
  font-size: 0.78rem;
  padding: 0.08rem 0.55rem 0.08rem 0.45rem;
  border-radius: 999px;
  border: 1px solid color-mix(in srgb, var(--thread) 55%, transparent);
  color: var(--text);
}

.skills .ico {
  color: var(--thread);
}

/* What he has: a bag before each thing. */
.card-has li {
  list-style: none;
  display: flex;
  gap: 0.4rem;
  align-items: baseline;
}

.card .card-has {
  padding-left: 0;
}

.card-has .ico {
  color: var(--wanderer);
  transform: translateY(0.12em);
}

.card details {
  font-size: 0.86rem;
  color: var(--muted);
}

.card details summary {
  cursor: pointer;
  width: fit-content;
}

.card details ul.plain {
  margin-top: 0.3rem;
}

.card .knows-head {
  list-style: none;
  margin: 0.4rem 0 0.15rem -1.1rem;
  font-weight: 600;
}

.plain {
  margin: 0;
  padding-left: 1.1rem;
}

.plain li {
  margin-bottom: 0.25rem;
}

/* Icons: thin threads, the colour of the text around them. */
.ico {
  width: 1.05em;
  height: 1.05em;
  flex: none;
  fill: none;
  stroke: currentColor;
  stroke-width: 1.6;
  stroke-linecap: round;
  stroke-linejoin: round;
  vertical-align: -0.16em;
}

/* ---------- the card from the records: states, the body of thread, things ---------- */

/* Lasting states: a word with its icon and when it passes. */
.card .states {
  list-style: none;
  margin: 0;
  padding: 0;
  display: grid;
  gap: 0.25rem;
  font-size: 0.86rem;
  color: var(--text);
}

.card .states li {
  padding-left: 1.45rem;
  text-indent: -1.45rem;
}

.card .states .ico {
  margin-right: 0.4rem;
  color: var(--costly);
}

.card .states .until {
  color: var(--muted);
}

/* The wounds: the figure and the words beside it. */
.body-box {
  display: grid;
  grid-template-columns: 56px minmax(0, 1fr);
  gap: 0.75rem;
  align-items: center;
  padding: 0.45rem 0.55rem;
  border-radius: 6px;
  background: rgba(0, 0, 0, 0.2);
}

.wounds {
  list-style: none;
  margin: 0;
  padding: 0;
  display: grid;
  gap: 0.3rem;
  font-size: 0.86rem;
  color: var(--text);
}

.wounds li.gone {
  color: var(--ink);
}

/* The body of thread: the parts in thread, a wound in its place — stitches, a ring, waves, a
   break — and a part gone as a dotted trace ending in a knot. No blood. */
.doll {
  width: 56px;
  height: 104px;
  overflow: visible;
}

.doll .part {
  fill: color-mix(in srgb, var(--wanderer) 10%, transparent);
  stroke: color-mix(in srgb, var(--wanderer) 70%, transparent);
  stroke-width: 1.4;
}

.doll .limb {
  stroke-width: 7;
  stroke-linecap: round;
  stroke: color-mix(in srgb, var(--wanderer) 32%, transparent);
}

.doll .part.w1 {
  stroke: var(--costly);
}

.doll .limb.w1 {
  stroke: color-mix(in srgb, var(--costly) 70%, transparent);
}

.doll .part.w2,
.doll .part.w3 {
  fill: color-mix(in srgb, var(--failure) 26%, transparent);
  stroke: var(--failure);
}

.doll .limb.w2 {
  stroke: color-mix(in srgb, var(--failure) 65%, transparent);
}

.doll .limb.w3 {
  stroke: var(--failure);
}

.doll .limb.gone {
  stroke: color-mix(in srgb, var(--muted) 80%, transparent);
  stroke-width: 2.6;
  stroke-linecap: round;
  stroke-dasharray: 0.1 4;
}

.doll .stump {
  fill: var(--ink);
}

.doll .stitch-line {
  fill: none;
  stroke: var(--ink);
  stroke-width: 1.2;
  stroke-linecap: round;
}

.doll .stitch-line.seam {
  stroke-width: 0.8;
  opacity: 0.7;
}

.doll .bruise {
  fill: none;
  stroke: var(--ink);
  stroke-width: 1;
  stroke-dasharray: 1.5 2;
}

.doll .rope {
  fill: none;
  stroke: var(--costly);
  stroke-width: 1.6;
  stroke-dasharray: 3 1.5;
}

.doll .eyes {
  fill: none;
  stroke: var(--ink);
  stroke-width: 1;
}

.doll.pose-down .figure {
  transform: rotate(-9deg);
  transform-origin: 30px 60px;
}

.doll.pose-down .part,
.doll.pose-weak .part {
  opacity: 0.6;
}

/* What he has: each thing with its icon. */
.card .items {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 0.35rem 0.45rem;
  margin: 0;
  font-size: 0.84rem;
}

.card .items-label {
  color: var(--muted);
  margin-right: 0.1rem;
}

.item {
  display: inline-flex;
  align-items: center;
  gap: 0.35rem;
  padding: 0.12rem 0.55rem 0.12rem 0.45rem;
  border-radius: 6px;
  background: rgba(255, 255, 255, 0.05);
  color: var(--ink);
}

.item .ico {
  color: var(--wanderer);
}

.item.fresh {
  box-shadow: 0 0 0 1px var(--wanderer);
}

.card .down-note.fresh {
  box-shadow: 0 0 0 1px color-mix(in srgb, var(--costly) 60%, transparent);
}

/* ---------- what a told turn did, once on the cards (each ≤ 1 s, 0.3 s apart) ---------- */

/* A wound: its place flares and the stitches are drawn. */
.doll .part.just-hurt {
  animation: hurt 0.9s ease-out var(--fx-delay, 0ms) 1 backwards;
}

.doll .wound-mark.just-hurt .stitch-line {
  stroke-dasharray: 14;
  animation: stitch 0.6s ease-out calc(var(--fx-delay, 0ms) + 350ms) 1 backwards;
}

@keyframes hurt {
  0% {
    stroke: color-mix(in srgb, var(--wanderer) 40%, transparent);
  }
  30% {
    stroke: #ff9a80;
    stroke-width: 10;
  }
}

@keyframes stitch {
  from {
    stroke-dashoffset: 14;
  }
  to {
    stroke-dashoffset: 0;
  }
}

/* A line of the card or a note that came with the turn. */
.just-in {
  animation: note-in 0.7s ease-out var(--fx-delay, 0ms) 1 backwards;
}

/* A thing found: it comes in with a knot of light. */
.item.just-got {
  animation: got 0.9s cubic-bezier(0.2, 0.9, 0.3, 1.2) var(--fx-delay, 0ms) 1 backwards;
}

@keyframes got {
  0% {
    opacity: 0;
    transform: translateY(-10px) scale(0.9);
  }
  60% {
    opacity: 1;
    transform: none;
    box-shadow:
      0 0 0 1px var(--wanderer),
      0 0 14px 2px rgba(216, 196, 143, 0.5);
  }
}

/* A thing lost: its ghost is crossed out and goes out. */
.item.ghost {
  position: relative;
  animation: lose 0.9s ease-in var(--fx-delay, 0ms) 1 forwards;
}

.item.ghost::after {
  content: '';
  position: absolute;
  left: 6%;
  top: 50%;
  width: 88%;
  height: 1.5px;
  background: var(--failure);
  transform-origin: left;
  animation: strike 0.4s ease-out var(--fx-delay, 0ms) 1 backwards;
}

@keyframes lose {
  0%,
  45% {
    opacity: 1;
  }
  100% {
    opacity: 0;
  }
}

@keyframes strike {
  from {
    transform: scaleX(0);
  }
}

/* Able to act again, or healed: the card breathes green once. */
.card.just-up {
  animation: up 1s ease-out var(--fx-delay, 0ms) 1;
}

@keyframes up {
  30% {
    box-shadow: 0 0 0 2px color-mix(in srgb, var(--clean) 70%, transparent);
  }
}

/* The phone's tab «Нити» while the cards have news the player has not seen. */
.tabs button.has-news::after {
  content: '';
  display: inline-block;
  width: 0.45rem;
  height: 0.45rem;
  margin-left: 0.35rem;
  border-radius: 50%;
  background: var(--wanderer);
  vertical-align: 0.15em;
}

/* ---------- pictures: the place at the start, the end by the epilogue ---------- */

.picture {
  margin: 0 0 1.2rem;
  border-radius: 4px;
  overflow: hidden;
  border: 1px solid var(--page-line);
  background: var(--void-deep);
}

.picture img {
  display: block;
  width: 100%;
  height: auto;
  aspect-ratio: 16 / 9;
  object-fit: cover;
}

.finale-picture {
  margin: 0.2rem 0 0.4rem;
}

/* Being drawn: a quiet strip where it will be; nothing blinks. */
.picture.pending {
  display: grid;
  place-items: center;
  min-height: 2.6rem;
  background:
    repeating-linear-gradient(
      115deg,
      transparent 0 14px,
      color-mix(in srgb, var(--wanderer) 6%, transparent) 14px 15px
    ),
    var(--void-deep);
}

.picture-wait {
  font-family: var(--sans);
  font-size: 0.85rem;
  color: var(--muted);
}

/*
 * A picture that will not come (refused, or the service away): the page's own vista — hills under
 * the night, the Wanderer's bead and a thread down to the place — with its name (stage 3c).
 */
.picture.vista {
  position: relative;
  background: linear-gradient(180deg, var(--veil) 0%, #2a2233 70%, var(--page) 100%);
}

.vista-art {
  display: block;
  width: 100%;
  height: auto;
  aspect-ratio: 16 / 5;
}

.vista-moon {
  fill: var(--wanderer);
  opacity: 0.14;
}

.vista-far {
  fill: #2b2430;
}

.vista-near {
  fill: var(--void-deep);
}

.vista-thread {
  fill: none;
  stroke: var(--thread-1);
  stroke-width: 1.5;
  stroke-linecap: round;
  opacity: 0.8;
}

.vista-bead {
  fill: var(--wanderer);
}

.vista-person {
  fill: var(--thread-1);
}

/* Left of the thread, which comes down at two thirds of the width (x 318 of 480): a long name
   wraps to a second line there rather than crossing it on a phone (the second 3c review). */
.vista-caption {
  position: absolute;
  left: 0.9rem;
  bottom: 0.55rem;
  max-width: calc(62% - 1.2rem);
  font-family: var(--serif);
  font-size: 0.95rem;
  line-height: 1.25;
  color: var(--ink);
  text-shadow: 0 1px 6px var(--void-deep);
  overflow: hidden;
  display: -webkit-box;
  -webkit-box-orient: vertical;
  -webkit-line-clamp: 2;
  line-clamp: 2;
}

/* ---------- the three: over the story's start and in the phone's goal panel ---------- */

.three {
  display: flex;
  flex-wrap: wrap;
  gap: 0.5rem 1.2rem;
  margin: 0.2rem 0 1.1rem;
}

.three:empty {
  display: none;
}

.three-one {
  cursor: pointer;
  display: flex;
  align-items: center;
  gap: 0.6rem;
  padding: 0.25rem 0.8rem 0.25rem 0.3rem;
  border: 1px solid transparent;
  border-radius: 999px;
  background: none;
  color: var(--text);
  font-family: var(--sans);
  text-align: left;
}

@media (hover: hover) {
  .three-one:hover {
    border-color: var(--page-line);
  }
}

.three-text {
  display: grid;
  line-height: 1.25;
  font-size: 0.8rem;
  color: var(--muted);
}

.three-text b {
  font-weight: 600;
  font-size: 0.95rem;
  color: var(--ink);
}

.three-one.down .three-text span {
  color: var(--costly);
}

.three-one.lost .three-text b {
  color: var(--muted);
}

/* ---------- world panel ---------- */

.world-block {
  margin-bottom: 1.4rem;
}

.fact-group h4 {
  margin: 0.7rem 0 0.2rem;
}

.glossary {
  margin: 0;
  font-size: 0.9rem;
}

.glossary dt {
  color: var(--ink);
  font-weight: 600;
  margin-top: 0.5rem;
}

.glossary dd {
  margin: 0.1rem 0 0;
  color: var(--muted);
  line-height: 1.45;
}

.journal {
  list-style: none;
  margin: 0;
  padding: 0;
  font-size: 0.9rem;
  color: var(--muted);
}

.journal li {
  padding: 0.35rem 0;
  border-bottom: 1px solid var(--veil-line);
}

/* «ход 3»: a way back to that turn of the story. */
.journal .when {
  cursor: pointer;
  margin-right: 0.4rem;
  padding: 0;
  border: 0;
  background: none;
  font: inherit;
  color: var(--faint);
  text-decoration: underline dotted;
  text-underline-offset: 3px;
}

@media (hover: hover) {
  .journal .when:hover {
    color: var(--wanderer);
  }
}

.more summary {
  cursor: pointer;
  color: var(--muted);
  font-size: 0.88rem;
  margin-top: 0.5rem;
}

/* ---------- phones ---------- */

@media (max-width: 56.25em) {
  :root {
    --gutter: 0.9rem;
  }

  /* A narrow column and a very big font: a word longer than the line breaks, not the page. */
  body {
    overflow-wrap: anywhere;
  }

  .rule-help {
    display: none;
  }

  .cast-row {
    gap: 0.3rem;
  }

  .cast.in-row .whorl {
    width: 96px;
    height: 96px;
  }

  .cast.in-row .cast-result {
    font-size: 1.02rem;
  }

  .cast.in-row .cast-chance {
    font-size: 0.74rem;
  }

  .portrait {
    width: 52px;
    height: 52px;
  }

  /* One thin line stays on top: the story's name, «Цель 2/3», the turns left; the sections
     under it. Whatever does not fit wraps to a line of its own, never off the screen. */
  .bar-row {
    min-height: 48px;
    flex-wrap: wrap;
    gap: 0 0.5rem;
    padding-top: 0.35rem;
    padding-bottom: 0.35rem;
  }

  .brand-block {
    margin-block: -0.35rem;
  }

  body[data-view='game'] .brand-block,
  .bar-source,
  .turn-thread {
    display: none;
  }

  /* «5 ходов» is enough on a phone; the words before it are for a screen reader. */
  .countdown-word {
    position: absolute;
    width: 1px;
    height: 1px;
    overflow: hidden;
    clip-path: inset(50%);
    white-space: nowrap;
  }

  .bar-title {
    flex: 0 1 auto;
    max-width: none;
    font-size: 1rem;
  }

  /* An ended story says how it went at its end; the line keeps its name. */
  .countdown.ended {
    display: none;
  }

  .goal-toggle,
  .countdown {
    padding: 0.22rem 0.6rem;
  }

  /* The panel opens over the page, under the bar's first line (over the tabs' line when they
     have one of their own, see followBar); the page does not move. */
  .goal-panel {
    position: absolute;
    top: var(--panel-top, 100%);
    left: 0;
    right: 0;
    max-width: none;
    max-height: calc(100dvh - var(--panel-top, var(--bar-h)) - 1rem);
    overflow-y: auto;
    overscroll-behavior: contain;
    gap: 0.75rem;
    padding: 0.9rem max(var(--gutter), var(--safe-r)) calc(1.1rem + var(--safe-b))
      max(var(--gutter), var(--safe-l));
    background: var(--void-deep);
    border-bottom: 1px solid var(--veil-line);
    box-shadow: 0 22px 40px -16px rgba(0, 0, 0, 0.9);
  }

  .threat-text {
    font-size: 0.95rem;
    line-height: 1.45;
  }

  .goal-steps {
    flex-direction: column;
    gap: 0.5rem;
    font-size: 0.92rem;
  }

  .clock-rule {
    font-size: 0.82rem;
  }

  .panel-source {
    display: block;
    margin: 0;
    color: var(--faint);
    font-size: 0.82rem;
  }

  .panel-bond {
    display: block;
  }

  .goal-panel .panel-three {
    display: flex;
    margin: 0;
  }

  .goal-panel .panel-end {
    display: flex;
    margin: 0.4rem 0 0;
  }

  .goal-panel .panel-end:has(> [hidden]) {
    display: none;
  }

  .panel-bond p {
    margin: 0;
    font-size: 0.9rem;
    line-height: 1.45;
    color: var(--text);
  }

  .panel-head {
    margin: 0 0 0.2rem;
    font-family: var(--sans);
    font-size: 0.82rem;
    font-weight: 600;
    color: var(--muted);
  }

  .panel-turns {
    display: block;
  }

  .panel-links {
    display: flex;
    flex-wrap: wrap;
    justify-content: space-between;
    gap: 0.5rem 1rem;
    margin: 0.2rem 0 0;
    font-size: 0.9rem;
  }

  /* «Что нового · сборка 57» among the panel's links: the story's bar has no room for it. */
  .panel-news {
    color: var(--muted);
  }

  .panel-build {
    color: var(--faint);
  }

  .panel-build.ready::before {
    content: ' · ';
  }

  /* «Сказать» beside the field, not under it: with the keyboard open both stay in sight. */
  .order-row .primary {
    flex: none;
    padding-inline: 1rem;
  }

  .order textarea {
    min-height: 3.2rem;
  }

  .game {
    display: block;
    padding-top: 0.9rem;
  }

  /* The sections are in the bar, a line of their own on an upright phone: one block on top. */
  .tabs {
    display: flex;
    flex: 1 0 100%;
    gap: 0.35rem;
    padding-top: 0.5rem;
  }

  /* Under the open goal the tabs' line is out of sight, and out of reach of Tab too. */
  .bar.tabs-under:has(> .goal-panel:not([hidden])) .tabs {
    visibility: hidden;
  }

  .tabs button {
    flex: 1;
    cursor: pointer;
    border: 1px solid var(--veil-line);
    background: var(--veil);
    color: var(--text);
    border-radius: 999px;
    padding: 0.35rem 0;
    font-size: 0.92rem;
  }

  .tabs button.active {
    border-color: var(--wanderer);
    color: var(--ink);
  }

  .side-tabs {
    display: none;
  }

  .side {
    position: static;
    max-height: none;
    overflow: visible;
    mask-image: none;
    padding-bottom: calc(2rem + var(--safe-b));
  }

  .game[data-tab='feed'] .side,
  .game[data-tab='team'] .feed-zone,
  .game[data-tab='world'] .feed-zone {
    display: none;
  }

  .page {
    padding: 1.2rem 1.1rem 1rem;
  }

  .page p {
    font-size: 1.06rem;
  }

  .whorl {
    width: 104px;
    height: 104px;
  }

  .cast {
    column-gap: 0.9rem;
  }

  .cast-legend {
    flex-direction: column;
  }

  .cast-result {
    font-size: 1.25rem;
  }

  .tier {
    font-size: 1.45rem;
  }

  .attempt-line {
    grid-template-columns: minmax(0, 1fr);
  }

  /* Under the attempt on a narrow screen what fell may wrap too, never past the edge. */
  .attempt-line .verdict {
    white-space: normal;
  }

  .lobby {
    padding: 1.6rem max(var(--gutter), var(--safe-r)) calc(3rem + var(--safe-b))
      max(var(--gutter), var(--safe-l));
  }

  .threshold .lore {
    font-size: 1rem;
    line-height: 1.65;
  }

  .new-world {
    margin-top: 2rem;
  }

  .choice-note {
    min-height: 4.5em;
  }

  /* One who has come back, on a phone: his stories, then the new world, then the ended ones. */
  .lobby.returning {
    display: flex;
    flex-direction: column;
  }

  .lobby.returning .lobby-stories,
  .lobby.returning .lobby-aside {
    display: contents;
  }

  .lobby.returning .hero-loom {
    display: none;
  }

  .lobby.returning .lobby-games {
    order: 1;
  }

  .lobby.returning .new-world {
    order: 2;
    margin: 2.4rem 0 0;
    padding: 1.2rem 1rem 1.1rem;
    border: 1px solid var(--veil-line);
    border-radius: 12px;
    background: color-mix(in srgb, var(--veil) 55%, transparent);
  }

  .lobby.returning .new-world-head {
    margin: 0 0 0.8rem;
    font-size: 1.35rem;
    color: var(--ink);
  }

  .lobby.returning .lobby-ended {
    order: 3;
    margin-top: 2.4rem;
  }

  .lobby.returning .lobby-all {
    order: 4;
  }

  .lobby.returning .lobby-foot {
    order: 5;
  }
}

/* A phone on its side, a small tablet: the sections go into the bar's one line, so the bar takes
   one line of a short screen, not two. */
@media (min-width: 33.75em) and (max-width: 56.25em) {
  .tabs {
    flex: 0 1 auto;
    padding-top: 0;
  }

  .tabs button {
    flex: none;
    padding: 0.35rem 0.95rem;
  }
}

/* The narrowest phones: a little less margin, a little more line. */
@media (max-width: 22.5em) {
  :root {
    --gutter: 0.75rem;
  }

  .page {
    padding: 1rem 0.9rem 0.9rem;
  }

  .briefing,
  .bond {
    padding-left: 0.7rem;
  }

  .goal-toggle,
  .countdown {
    padding: 0.22rem 0.5rem;
  }
}

/*
 * A finger, not a mouse: targets grow to 44px where there is room (buttons, chips, the side tabs,
 * the links of the panels). Where the look must stay small (in the bar, in a line of text), an
 * invisible margin round it takes the touch instead. In the bar the name and the build are 28px
 * high, one above the other, with their margins in the gaps: a deliberate compromise, so that
 * the bar keeps its height on a phone.
 */
@media (pointer: coarse) {
  .goal-toggle,
  .rule-help,
  .brand,
  .build,
  .bar-news,
  .tabs button,
  .journal .when {
    position: relative;
  }

  .goal-toggle::before,
  .rule-help::before,
  .brand::before,
  .build::before,
  .bar-news::before,
  .tabs button::before,
  .journal .when::before {
    content: '';
    position: absolute;
    inset: -8px -3px;
  }

  /* The name and the build, one above the other: taller for a finger, the gap between them
     left free, the build's touch wider than its small words. */
  .brand,
  .build {
    min-height: 28px;
  }

  .build::before {
    inset: 0 -10px;
  }

  /* The goal's pill and the tabs under it share the gap between them, never overlapping. */
  .goal-toggle::before {
    inset: -11px -3px -4px;
  }

  .tabs button::before {
    inset: -4px -2px -6px;
  }

  .rule-help::before {
    inset: -10px -9px;
  }

  .brand::before {
    inset: -10px 0 0;
  }

  .bar-news::before {
    inset: -13px -6px;
  }

  .journal .when::before {
    inset: -11px -4px;
  }

  .chip,
  .jump,
  .primary,
  .side-tabs button,
  .key-value,
  .survey-rating label {
    min-height: 44px;
  }

  .report,
  #panel-home,
  .panel-news {
    display: inline-flex;
    align-items: center;
    min-height: 44px;
  }

  /* In the goal's panel its links stay a line of text, so that the panel fits a phone's height:
     the touch is taken round them, in the gaps above and below. */
  .goal-panel .report,
  #panel-home,
  .panel-news {
    position: relative;
    min-height: 0;
  }

  .goal-panel .report::before,
  #panel-home::before,
  .panel-news::before {
    content: '';
    position: absolute;
    inset: -11px -4px;
  }

  summary {
    min-height: 44px;
    padding-block: 12px;
  }

  /* Two folds one under the other on a card: their tall lines are the gap between them. */
  .card details + details {
    margin-top: -0.55rem;
  }

  .knots .knot,
  .knots.numbered .knot {
    width: 2.75rem;
    height: 2.75rem;
  }

  .knots.numbered .knot.word {
    width: auto;
  }

  .knots li + li::before,
  .knots.numbered li + li::before {
    width: 0.3rem;
  }

  /* In the bar the knots are far apart for a finger: one thread runs through all of them. */
  .turn-thread .knots {
    width: max-content;
    background: linear-gradient(
        color-mix(in srgb, var(--wanderer) 45%, transparent),
        color-mix(in srgb, var(--wanderer) 45%, transparent)
      )
      center / calc(100% - 2.95rem) 1px no-repeat;
  }

  .turn-thread .knots li + li::before {
    width: 0;
  }
}

@media (prefers-reduced-motion: reduce) {
  *,
  *::before,
  *::after {
    animation: none !important;
    transition: none !important;
  }
}

/* A line of a card that changed in the last turn. */
.fresh-mark {
  display: inline-block;
  margin-right: 0.4rem;
  padding: 0 0.35rem;
  border-radius: 999px;
  font-size: 0.7rem;
  line-height: 1.4;
  vertical-align: 0.1em;
  color: var(--bg, #0f101b);
  background: var(--wanderer);
}

/* ---------- «Что нового»: the notes of every build, as the pages of a book ---------- */

.notes {
  padding: 2.4rem max(var(--gutter), var(--safe-r)) calc(4rem + var(--safe-b))
    max(var(--gutter), var(--safe-l));
}

.notes-book {
  max-width: 46rem;
  margin: 0 auto;
  padding: 3rem 3.5rem 1.6rem;
  font-family: var(--serif);
  color: var(--ink);
  background:
    radial-gradient(ellipse 90% 80% at 50% 40%, transparent 55%, rgba(0, 0, 0, 0.35) 100%),
    var(--page);
  border: 1px solid var(--page-line);
  border-radius: 3px;
  box-shadow:
    0 1px 0 rgba(255, 240, 215, 0.04) inset,
    0 18px 40px -24px rgba(0, 0, 0, 0.9);
}

.notes-head {
  text-align: center;
  margin-bottom: 1.2rem;
}

.notes-head h1 {
  margin: 0 0 0.5rem;
  font-family: var(--serif);
  font-weight: 400;
  font-size: clamp(2rem, 1.5rem + 2vw, 2.7rem);
  line-height: 1.1;
  color: var(--ink);
}

.notes-lead {
  margin: 0;
  font-style: italic;
  color: color-mix(in srgb, var(--ink) 72%, var(--page));
}

/* Under the title, in place of an asterism: a knot of each thread. */
.notes-head::after {
  content: '';
  display: block;
  width: 3.2rem;
  height: 0.5rem;
  margin: 1.5rem auto 0;
  background:
    radial-gradient(circle, var(--thread-1) 0 0.19rem, transparent 0.21rem) 0 50% / 0.5rem 0.5rem
      no-repeat,
    radial-gradient(circle, var(--thread-2) 0 0.19rem, transparent 0.21rem) 50% 50% / 0.5rem 0.5rem
      no-repeat,
    radial-gradient(circle, var(--thread-3) 0 0.19rem, transparent 0.21rem) 100% 50% / 0.5rem 0.5rem
      no-repeat;
}

.notes-state {
  margin: 1.6rem 0 1.2rem;
  text-align: center;
  font-style: italic;
  color: color-mix(in srgb, var(--ink) 72%, var(--page));
}

.notes-list {
  margin: 0;
  padding: 0;
  list-style: none;
}

/* One build: its number in the margin, as a chapter's; what changed beside it. */
.note {
  display: grid;
  grid-template-columns: 5.4rem minmax(0, 1fr);
  gap: 0 1.5rem;
  padding: 1.9rem 0 1.8rem;
  scroll-margin-top: calc(var(--bar-h) + 1rem);
}

.note + .note {
  border-top: 1px solid var(--page-line);
}

.note-mark {
  text-align: right;
}

/* Up to four digits on one line: a number never breaks in two. */
.note-number {
  font-size: 2.5rem;
  line-height: 0.95;
  white-space: nowrap;
  color: var(--wanderer);
  font-variant-numeric: oldstyle-nums;
}

.note-knot {
  display: inline-block;
  width: 0.7rem;
  height: 0.7rem;
  margin-top: 0.75rem;
  border-radius: 50%;
  background: var(--wanderer);
}

.note-title {
  margin: 0 0 0.25rem;
  font-family: var(--serif);
  font-size: 1.5rem;
  font-weight: 400;
  line-height: 1.25;
  color: var(--ink);
}

.note-meta {
  margin: 0 0 1rem;
  font-family: var(--sans);
  font-size: 0.84rem;
  color: color-mix(in srgb, var(--ink) 62%, var(--page));
}

/* On a wide page the number stands in the margin: the words are for a screen reader. */
@media (min-width: 56.3125em) {
  .note-build {
    position: absolute;
    width: 1px;
    height: 1px;
    overflow: hidden;
    clip-path: inset(50%);
    white-space: nowrap;
  }
}

.note-now {
  display: inline-block;
  margin-left: 0.6rem;
  padding: 0 0.5rem;
  border: 1px solid color-mix(in srgb, var(--wanderer) 60%, transparent);
  border-radius: 999px;
  font-size: 0.76rem;
  line-height: 1.5;
  color: var(--wanderer);
}

.note-items {
  display: grid;
  gap: 0.6rem;
  margin: 0;
  padding: 0;
  list-style: none;
}

/* Each change marked by a knot, the threads' colours in turn. */
.note-items li {
  position: relative;
  padding-left: 1.3rem;
  font-size: 1.08rem;
  line-height: 1.68;
}

.note-items li::before {
  content: '';
  position: absolute;
  left: 0.1rem;
  top: 0.68em;
  width: 0.44rem;
  height: 0.44rem;
  border-radius: 50%;
  background: var(--thread-1);
}

.note-items li:nth-child(3n + 2)::before {
  background: var(--thread-2);
}

.note-items li:nth-child(3n)::before {
  background: var(--thread-3);
}

@media (max-width: 56.25em) {
  .notes {
    padding-top: 1.2rem;
  }

  .notes-book {
    padding: 2rem 1.2rem 0.8rem;
  }

  .note {
    grid-template-columns: minmax(0, 1fr);
    padding: 1.5rem 0 1.4rem;
  }

  .note-mark {
    display: none;
  }

  .note-title {
    font-size: 1.32rem;
  }

  .note-items li {
    font-size: 1.04rem;
  }
}
