/* Catan — page chrome.
 *
 * Same hand as the exhibit next door, rearranged for a game rather than a
 * readout. theme.js publishes every colour and every step of the type and
 * space ladders onto :root at load; the var() fallbacks here only carry the
 * one paint before the script runs.
 *
 * The rearrangement is the point. Watching four bots wants stats on every
 * side; playing wants the board large, your own hand and options across the
 * foot where your eye already is, and only the two readouts that actually
 * change a decision left in the rails.
 */

@font-face { font-family: 'Archia'; src: url('../../assets/fonts/Archia-Light.woff')    format('woff'); font-weight: 300; font-display: swap; }
@font-face { font-family: 'Archia'; src: url('../../assets/fonts/Archia-Regular.woff')  format('woff'); font-weight: 400; font-display: swap; }
@font-face { font-family: 'Archia'; src: url('../../assets/fonts/Archia-Medium.woff')   format('woff'); font-weight: 500; font-display: swap; }
@font-face { font-family: 'Archia'; src: url('../../assets/fonts/Archia-SemiBold.woff') format('woff'); font-weight: 600; font-display: swap; }
@font-face { font-family: 'Archia'; src: url('../../assets/fonts/Archia-Bold.woff')     format('woff'); font-weight: 700; font-display: swap; }

:root {
  --ink: #f4f1e8;
  --gold: #f5c542;
  --felt: #124364;
  --sheet: rgba(7, 30, 46, 0.86);
  --sheet-solid: #071e2e;
  --hair: rgba(244, 241, 232, 0.16);
  --hair-strong: rgba(244, 241, 232, 0.32);
  --ink-2: rgba(244, 241, 232, 0.74);
  --ink-3: rgba(244, 241, 232, 0.5);
  --ink-4: rgba(244, 241, 232, 0.28);
  --radius-md: 8px;
  --radius-sm: 4px;
  --fs-1: 17.14px; --fs-0: 15.2px; --fs-n1: 13.48px; --fs-n2: 11.95px;
  --fs-n3: 10.6px; --fs-n4: 9.4px; --fs-n5: 8.33px; --fs-n6: 7.39px;
  --sp-0: 8px; --sp-1: 10px; --sp-2: 13px; --sp-3: 16px;
  --rail-l: clamp(190px, 15vw, 268px);
  --rail-r: clamp(214px, 17.5vw, 316px);
  --dock-h: 156px;
  --gap: var(--sp-1, 10px);
  /* The strip floats, so what it takes out of the bottom of the window is its
     own height plus the gutter under it. `chrome.js` measures the real figure
     and publishes it as --dock-actual; this is the floor used until it does,
     and by any browser without a ResizeObserver. */
  --dock-floor: calc(var(--dock-h) + var(--gap));
  /* One card size, and only one: the hand is the same size whoever's turn it
     is, and the column is sized to it rather than the other way round.

     In the proportions of a playing card, near enough: five of these in a row
     read as a hand held in front of you, where five squares read as five
     buttons. It is the shape the flying chips on the overlay take too. */
  --card-w: 54px;
  --card-h: 74px;

  /* The number chips are not cards and no longer pretend to be.
     They used to share the card's box, on the argument that two strips of
     tiles reading at two sizes is a difference the eye has to account for.
     That held while both were one row. Wrapping the numbers onto a second row
     sooner — which is what shortens the strip and lets the game log run to the
     bottom of the window — needs them shorter, or the strip grows by a card's
     height to save a card's width. Five to a row, and then they wrap. */
  --num-w: 46px;
  --num-h: 40px;
  --num-row: 5;
}

/* Nothing of the game until the lobby hands over.
   The sheet over it is a radial-gradient of two custom properties that
   theme.js publishes at boot, and a gradient with an undefined colour in it is
   not a gradient at all: for the frame or two before the script ran, the sheet
   was transparent and the empty hand and the empty rails showed through it on
   every reload. Both halves of that are fixed here — the gradient has literal
   fallbacks, and what is underneath is not drawn at all until it is real. */
body.is-booting #app,
body.is-booting #dock,
body.is-booting #overlay { visibility: hidden; }

* { box-sizing: border-box; }

/* `hidden` is a browser rule, and any `display` written here beats it. Several
   things on this page are laid out with flex or grid and are also hidden some
   of the time — a pane, a resume offer, an unused opponent row — and every one
   of them would have stayed on screen. Said once, here, rather than remembered
   at each of them. */
[hidden] { display: none !important; }

html, body {
  margin: 0;
  padding: 0;
  height: 100%;
  overflow: hidden;
  background: var(--felt);
  color: var(--ink);
  font-family: 'Archia', system-ui, -apple-system, 'Segoe UI', sans-serif;
  font-weight: 400;
  -webkit-font-smoothing: antialiased;
  text-rendering: optimizeLegibility;
}

body::before {
  content: '';
  position: fixed;
  inset: 0;
  z-index: 0;
  background:
    radial-gradient(120% 92% at 50% 34%, rgba(255, 255, 255, 0.09), transparent 62%),
    radial-gradient(150% 120% at 50% 50%, transparent 38%, rgba(0, 0, 0, 0.46) 100%);
  pointer-events: none;
}

button, input, select, label, a { font-family: inherit; font-weight: 500; }

/* ================================================================ layout */

#app {
  position: fixed;
  inset: 0;
  z-index: 1;
  display: grid;
  grid-template-columns: var(--rail-l) minmax(0, 1fr) var(--rail-r);
  gap: var(--gap);
  padding: var(--gap);
}

/* The strip along the foot is only as wide as it needs to be and sits against
   the left, so the three columns above it do not all have to stop at the same
   line. The two that stand over it do; the right-hand rail stands beside it
   whenever the strip ends clear of that column, and runs to the bottom of the
   window instead — which is room the game log can use and nothing else can.
   `chrome.js` measures the strip and says whether that is the case.

   `--dock-h` is the floor the stylesheet asks for; `--dock-actual` is what the
   strip turned out to be once its buttons had wrapped. Without script the
   floor is used, which is exactly what this was before it was measured. */
#rail-left,
#stage-wrap {
  margin-bottom: var(--dock-actual, var(--dock-floor));
}

#rail-right {
  margin-bottom: var(--rail-drop, var(--dock-actual, var(--dock-floor)));
}

.rail {
  display: flex;
  flex-direction: column;
  gap: var(--gap);
  min-height: 0;
  overflow: hidden;
}

/* The two readouts at the top are sized to what is in them: a yield row is a
   row and a roll is a roll, and neither is improved by being stretched.

   The two histograms below them are twins, and they share everything the rail
   has left between them, evenly, down to the top of the strip. They are the
   same eleven columns measured two ways — how often each number came up, and
   what it paid when it did — so a difference in height between them would read
   as a difference in importance that is not there. */
#panel-yield { flex: none; }
#panel-roll { flex: none; }
#panel-rolls,
#panel-payout { flex: 1 1 0; min-height: 150px; }
#panel-log { flex: 1 1 auto; }

/* Four panels down the left now. On a short window they scroll rather than
   being clipped by the rail. */
#rail-left { overflow-y: auto; }

/* A histogram panel is a chart with a heading and a footnote, and the chart
   takes what the other two do not. */
#panel-rolls .panel-body,
#panel-payout .panel-body { display: flex; flex-direction: column; }

#stage-wrap { position: relative; min-width: 0; min-height: 0; }
#stage { position: absolute; inset: 0; }
#stage canvas { display: block; width: 100%; height: 100%; touch-action: none; }

/* ============================================================ rail grips */

/* The gutter between a rail and the board is the only strip of this page that
   is not already something, so that is where the handle for resizing the rail
   goes. Inside the rail it would have sat over the log's scrollbar. */
.rail-grip {
  position: absolute;
  top: var(--gap);
  /* Level with the board on both sides, even where the rail beside it carries
     on down: the handle belongs beside the thing it resizes the view of. */
  bottom: calc(var(--dock-actual, var(--dock-floor)) + var(--gap));
  width: var(--gap);
  z-index: 3;
  cursor: col-resize;
  touch-action: none;
}

#grip-left { left: calc(var(--gap) + var(--rail-l)); }
#grip-right { right: calc(var(--gap) + var(--rail-r)); }

/* Invisible until it is looked for. A permanent divider down both sides of
   the board would be two more lines competing with the island. */
.rail-grip::after {
  content: '';
  position: absolute;
  top: 50%;
  left: 50%;
  transform: translate(-50%, -50%);
  width: 2px;
  height: 28px;
  border-radius: 999px;
  background: var(--hair);
  transition: background 140ms ease, height 140ms ease;
}

.rail-grip:hover::after,
.rail-grip.is-dragging::after { background: var(--gold); height: 56px; }

/* A drag that starts on the gutter should not become a drag of the camera if
   the pointer wanders over the board. */
body.is-resizing { cursor: col-resize; user-select: none; -webkit-user-select: none; }
body.is-resizing #stage canvas { pointer-events: none; }

#overlay { position: fixed; inset: 0; z-index: 4; pointer-events: none; }

/* =============================================================== palette */

/* Which felt, and which accent. In the corner of the board rather than in a
   rail, so it stays in the same corner of the same thing when the rails are
   dragged, and small enough to be ignored by anybody who does not want it. */
#palette { position: absolute; top: 10px; right: 10px; z-index: 5; }

#palette-open {
  display: flex;
  gap: 3px;
  align-items: center;
  padding: 5px 6px;
  border-radius: 999px;
  background: var(--sheet-soft);
  border: 1px solid var(--hair);
  backdrop-filter: blur(7px);
  -webkit-backdrop-filter: blur(7px);
}

#palette-open i {
  display: block;
  width: 9px;
  height: 9px;
  border-radius: 999px;
  box-shadow: inset 0 0 0 1px rgba(0, 0, 0, 0.4);
}

#palette-open:hover { border-color: var(--ink-3); background: var(--sheet-strong); }
#palette-open.is-open { border-color: var(--gold); }

#palette-pop {
  position: absolute;
  top: calc(100% + 6px);
  right: 0;
  /* Wide enough for all seven grounds in one row: a wrapped seventh chip
     reads as an afterthought rather than as part of the set. */
  width: 196px;
  padding: var(--sp-1);
  border-radius: var(--radius-md);
  background: var(--sheet-strong);
  border: 1px solid var(--hair);
  box-shadow: 0 6px 22px rgba(0, 0, 0, 0.4);
  backdrop-filter: blur(7px);
  -webkit-backdrop-filter: blur(7px);
}

.pal-label {
  font-size: var(--fs-n5);
  font-weight: 700;
  letter-spacing: 0.16em;
  text-transform: uppercase;
  color: var(--ink-3);
  margin-bottom: 6px;
}

.pal-row { display: flex; gap: 5px; flex-wrap: wrap; margin-bottom: var(--sp-1); }
.pal-row:last-child { margin-bottom: 0; }

.pal-chip {
  width: 20px;
  height: 20px;
  padding: 0;
  border-radius: var(--radius-sm);
  border: 1px solid rgba(0, 0, 0, 0.45);
  box-shadow: inset 0 0 0 1px rgba(255, 255, 255, 0.08);
}

.pal-chip:hover { transform: translateY(-1px); }
.pal-chip.is-on { outline: 2px solid var(--gold); outline-offset: 2px; }

/* The grain switch. Same row rhythm as the swatches, but it is a word rather
   than a colour, because "on" is not a shade of anything. */
.pal-toggle {
  flex: 1;
  padding: 4px 0;
  font-size: var(--fs-n5);
  letter-spacing: 0.12em;
}

.pal-toggle.is-on {
  background: var(--gold);
  border-color: var(--gold);
  color: var(--on-accent);
}

/* ================================================================ panels */

.panel {
  background: var(--sheet);
  border: 1px solid var(--hair);
  border-radius: var(--radius-md);
  backdrop-filter: blur(7px);
  -webkit-backdrop-filter: blur(7px);
  box-shadow: 0 2px 10px rgba(0, 0, 0, 0.26);
  padding: var(--sp-1);
  min-height: 0;
  display: flex;
  flex-direction: column;
}

.panel > h2 {
  margin: 0 0 7px;
  padding-bottom: 6px;
  border-bottom: 1px solid var(--hair);
  font-size: var(--fs-n3);
  font-weight: 700;
  letter-spacing: 0.16em;
  text-transform: uppercase;
  display: flex;
  justify-content: space-between;
  align-items: baseline;
  gap: 8px;
  flex: none;
}

/* The one note on this page that is about a piece taking things away. */
.panel > h2 .note.is-robber { color: var(--robber); font-weight: 700; }

.panel > h2 .note {
  font-weight: 500;
  letter-spacing: 0.06em;
  text-transform: none;
  color: var(--ink-3);
  font-size: var(--fs-n4);
  white-space: nowrap;
}

.panel-body { min-height: 0; flex: 1; }

/* ============================================================ turn strip */

/* Whose move it is, and what the game is waiting for. Over the board rather
   than beside it, because that is where the eye already is. */
#turnstrip {
  position: absolute;
  top: 10px;
  left: 50%;
  transform: translateX(-50%);
  z-index: 3;
  display: flex;
  align-items: baseline;
  gap: 10px;
  padding: 7px 14px;
  border-radius: 999px;
  background: var(--sheet-soft, rgba(7, 30, 46, 0.72));
  border: 1px solid var(--hair);
  backdrop-filter: blur(7px);
  -webkit-backdrop-filter: blur(7px);
  white-space: nowrap;
  pointer-events: none;
  transition: opacity 220ms ease;
}

#turnstrip .who {
  font-size: var(--fs-n2);
  font-weight: 700;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  color: var(--sc, var(--ink));
}

#turnstrip .what {
  font-size: var(--fs-n2);
  color: var(--ink-3);
  letter-spacing: 0.05em;
}

#turnstrip.is-yours { border-color: var(--gold); box-shadow: 0 0 0 1px rgba(245, 197, 66, 0.28); }

/* ========================================================= opponent cards */

/* One card per seat, in four lines that answer the four questions you ask
   about somebody across a table: who they are, how close they are, how much
   they are holding, and what they have on the board.

   It was three lines with a large number floating in the corner. The number
   sat on the name's baseline, which left a card's worth of empty space above
   the name and made the count read as decoration rather than as the thing the
   card is about. */

#opponents { display: flex; flex-direction: column; gap: var(--gap); flex: none; }

/* The seat's colour, all the way round and faintly underneath. A stripe down
   one edge made one side of every card heavier than the other three, which
   reads as a stack of little flags rather than as a column of cards. Whose
   move it is is that outline getting heavier and picking up a haze off it, and
   the weight is a shadow rather than border width, so nothing shifts by two
   pixels when the turn passes. */
.opp {
  background:
    linear-gradient(var(--sc-soft, transparent), transparent 62%),
    var(--sheet);
  border: 1px solid var(--sc-line, var(--hair));
  border-radius: var(--radius-md);
  padding: 9px 11px 10px;
  display: flex;
  flex-direction: column;
  gap: 7px;
  transition: border-color 260ms ease, box-shadow 260ms ease, background 260ms ease;
}

.opp.is-turn {
  border-color: var(--sc);
  box-shadow: 0 0 0 2px var(--sc), 0 0 18px var(--sc-glow);
}

/* ------------------------------------------------------------- the head */

/* The name and the count on one line, centred against each other rather than
   sharing a baseline. A 30px numeral and a 10px name have nothing useful in
   common at the foot of their letters. */
.opp-head {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 8px;
  min-width: 0;
}

.opp-name {
  font-size: clamp(var(--fs-n3), calc(var(--rail-r) * 0.048), var(--fs-0));
  font-weight: 700;
  letter-spacing: 0.12em;
  text-transform: uppercase;
  color: var(--sc, var(--ink-2));
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
  min-width: 0;
}

/* Your own seat, shown exactly as the others are shown, and tagged so it is
   never mistaken for somebody else's. Quiet: it is a note on a name, not a
   second name. */
.opp.is-you .opp-name::after {
  content: 'you';
  margin-left: 7px;
  font-size: var(--fs-n5);
  font-weight: 600;
  letter-spacing: 0.14em;
  color: var(--ink-4);
}

/* The one number on this card that says who is winning, and it was set at the
   size of a caption before all this. Big, and on a line that is the right
   height for it. */
.opp-vp {
  display: flex;
  align-items: baseline;
  gap: 3px;
  flex: none;
  line-height: 1;
}

.opp-vp b {
  font-size: clamp(var(--fs-1), calc(var(--rail-r) * 0.088), 32px);
  font-weight: 700;
  color: var(--ink);
  font-variant-numeric: tabular-nums;
}

.opp-vp i {
  font-style: normal;
  font-size: clamp(var(--fs-n4), calc(var(--rail-r) * 0.036), var(--fs-n2));
  font-weight: 700;
  letter-spacing: 0.12em;
  text-transform: uppercase;
  color: var(--ink-4);
}

/* --------------------------------------------------------- ten points */

/* How close, as a thing you see rather than a number you read. Public points
   only, so it says exactly what the table can see and nothing it cannot. */
.opp-track {
  height: 3px;
  border-radius: 2px;
  background: rgba(0, 0, 0, 0.32);
  overflow: hidden;
}

.opp-track i {
  display: block;
  height: 100%;
  width: 0;
  border-radius: 2px;
  background: var(--sc, var(--ink-3));
  transition: width 320ms cubic-bezier(0.4, 0, 0.2, 1);
}

/* ------------------------------------------------------------- the hand */

.opp-hand {
  display: flex;
  align-items: center;
  gap: 8px;
  min-height: calc(clamp(11px, calc(var(--rail-r) * 0.042), 17px) * 1.4);
}

.opp-cards {
  display: flex;
  align-items: center;
  gap: 3px;
  flex: 1;
  min-width: 0;
  overflow: hidden;
}

/* Cards face down: a count and that many backs, which is exactly what you can
   see across a real table. Drawn as backs rather than as hatched blocks — a
   hairline, an inset rule a pixel in, and a weave too fine to read as a
   pattern, which is what the back of a card looks like from a metre away. */
.opp-back {
  flex: none;
  width: clamp(11px, calc(var(--rail-r) * 0.042), 17px);
  height: calc(clamp(11px, calc(var(--rail-r) * 0.042), 17px) * 1.4);
  border-radius: 2px;
  background:
    repeating-linear-gradient(45deg,
      rgba(244, 241, 232, 0.16) 0 1.5px, rgba(244, 241, 232, 0.04) 1.5px 3px);
  border: 1px solid var(--hair-strong);
  box-shadow: inset 0 0 0 1px rgba(0, 0, 0, 0.28);
}

/* A development card is the one thing in a hand worth telling apart at a
   glance, so it takes the accent and stands off the resources by a gap. */
.opp-back.is-dev {
  margin-left: 4px;
  background:
    repeating-linear-gradient(45deg,
      rgba(245, 197, 66, 0.42) 0 1.5px, rgba(245, 197, 66, 0.14) 1.5px 3px);
  border-color: var(--gold-line, rgba(245, 197, 66, 0.55));
}

.opp-back.is-dev + .opp-back.is-dev { margin-left: 0; }

.opp-cards.is-cut {
  -webkit-mask-image: linear-gradient(to right, #000 calc(100% - 16px), transparent);
  mask-image: linear-gradient(to right, #000 calc(100% - 16px), transparent);
}

.opp-cards.is-empty::before {
  content: 'nothing in hand';
  font-size: var(--fs-n4);
  letter-spacing: 0.05em;
  color: var(--ink-4);
}

/* The figure beside the backs, so nobody has to count to nine. Over the
   seven-card limit it takes the robber's red, because that is who the number
   is a warning about. */
.opp-held {
  flex: none;
  font-size: clamp(var(--fs-n3), calc(var(--rail-r) * 0.042), var(--fs-0));
  font-weight: 700;
  color: var(--ink-2);
  font-variant-numeric: tabular-nums;
  min-width: 1.2em;
  text-align: right;
}

.opp-held.is-over { color: var(--robber); }

/* ------------------------------------------------------------- the board */

/* What they have out there. The figures carry the ink and the words carry
   none, so the line reads as four numbers rather than as a sentence. */
.opp-meta {
  display: flex;
  flex-wrap: wrap;
  align-items: baseline;
  gap: 2px 5px;
  font-size: clamp(var(--fs-n4), calc(var(--rail-r) * 0.034), var(--fs-n2));
  color: var(--ink-4);
  letter-spacing: 0.05em;
}

.opp-meta b { color: var(--ink-2); font-weight: 700; margin-right: 2px; }
.opp-meta em { font-style: normal; color: var(--ink-4); opacity: 0.5; }

/* An award is a fact about the game rather than another count, so it is a
   chip in the accent rather than a third phrase in the same sentence. */
.opp-meta .lead {
  padding: 1px 5px;
  border-radius: 2px;
  background: var(--gold-soft, rgba(245, 197, 66, 0.1));
  color: var(--gold);
  font-weight: 700;
  font-size: 0.92em;
  letter-spacing: 0.09em;
  text-transform: uppercase;
}

/* ==================================================================== log */

.log {
  overflow-y: auto;
  min-height: 0;
  flex: 1;
  font-size: var(--fs-n3);
  line-height: 1.42;
}

/* A scrollbar drawn by the browser is the one piece of this page nobody chose:
   a light grey slab on a felt table, in whatever the operating system thinks a
   scrollbar looks like. Said twice because the two ways of saying it are not
   the same standard and neither is universal. Anything on this page that
   scrolls is listed here, so there is one answer rather than four. */
.log,
#rail-left,
.numbers,
#leaning-list,
#modal-body {
  scrollbar-width: thin;
  scrollbar-color: var(--hair-strong) rgba(0, 0, 0, 0.24);
}

.log::-webkit-scrollbar,
#rail-left::-webkit-scrollbar,
#leaning-list::-webkit-scrollbar,
#modal-body::-webkit-scrollbar { width: 8px; height: 8px; }

.log::-webkit-scrollbar-track,
#rail-left::-webkit-scrollbar-track,
#leaning-list::-webkit-scrollbar-track,
#modal-body::-webkit-scrollbar-track {
  background: rgba(0, 0, 0, 0.24);
  border-radius: 999px;
}

.log::-webkit-scrollbar-thumb,
#rail-left::-webkit-scrollbar-thumb,
#leaning-list::-webkit-scrollbar-thumb,
#modal-body::-webkit-scrollbar-thumb {
  background: var(--hair-strong);
  border-radius: 999px;
  border: 2px solid transparent;
  background-clip: content-box;
}

.log::-webkit-scrollbar-thumb:hover,
#rail-left::-webkit-scrollbar-thumb:hover,
#leaning-list::-webkit-scrollbar-thumb:hover,
#modal-body::-webkit-scrollbar-thumb:hover { background: var(--ink-3); background-clip: content-box; }

.log .line { padding: 2px 0 2px 7px; border-left: 2px solid transparent; color: var(--ink-2); }
.log .line.plain { border-left-color: var(--hair); }
.log .line.faint { color: var(--ink-3); }
.log .line.warn { color: var(--robber-lit); border-left-color: var(--robber-line); }
.log .line.gold { color: var(--gold); border-left-color: rgba(245, 197, 66, 0.5); }
.log .line.accent { color: var(--ink); border-left-color: var(--hair-strong); }
.log .line.win { color: var(--gold); font-weight: 700; }
.log .line.you { background: rgba(245, 197, 66, 0.07); }

/* =================================================================== dock */

/* Your hand and your options. The one place the interface asks you for
   something, so it never moves and never has to be found. */
#dock {
  position: fixed;
  left: var(--gap);
  bottom: var(--gap);
  /* Exactly as wide as what is in it, against the left. Whatever the window
     has over that is left alone at the right-hand end, where the game log can
     have it. */
  width: fit-content;
  max-width: calc(100% - var(--gap) * 2);
  /* As tall as what is in it, and never shorter than this. The height used to
     be a number guessed per breakpoint, and the guess was wrong wherever the
     option buttons wrapped one row further than expected — which depends on
     how wide the options column is, which now depends on how many numbers you
     hold. Rather than guess again at every combination, it is measured. */
  min-height: var(--dock-h);
  z-index: 5;
  display: grid;
  /* One row, five segments. The options no longer need half the strip: their
     buttons come in two kinds and go on two lines, which halves the width they
     ask for and leaves room for a segment that had nowhere to be.

     Your hand and your numbers are sized to what is in them, so neither ever
     changes width for a reason that has nothing to do with your hand.

     Every one of them is a content width and the gaps between them are the
     page's ordinary gap. Spare window width is not shared out along the strip:
     an even spread of it puts a hand's five cards a thumb apart, which reads as
     five things in a line rather than as a hand. It goes to the right of the
     strip instead, where it is worth something to the log.

     And because the contents are now the same whoever's turn it is, none of
     this moves during a game. */
  grid-template-columns:
    max-content                 /* your hand, sized to its cards */
    fit-content(440px)          /* your numbers, sized to them */
    minmax(min-content, auto)   /* your turn, two lines of buttons */
    max-content                 /* what is left in the box */
    max-content;                /* victory points */
  gap: var(--gap);
  padding: var(--gap);
  background: linear-gradient(to top, var(--sheet-strong), var(--sheet-soft));
  /* A thing on the table, not a bar bolted to the window.
     It used to run into the bottom and left edges of the screen and stop, with
     one corner rounded, which made it read as part of the browser rather than
     part of the game — and no other panel on this page touches an edge. It
     floats now, in the same gutter everything else floats in, and it is the
     same rounded rectangle everything else is. */
  border: 1px solid var(--hair);
  border-radius: var(--radius-md);
  transition: border-color 420ms ease, box-shadow 420ms ease;
}

/* Watching is most of a game, and for all of it there is nothing on the strip
   to press. The buttons stay where they are and fade almost out — the same
   tell a build you cannot afford carries, taken further and applied to all of
   them at once. On top of the buttons' own greying that is about a sixth of
   full strength: legible as a shape, plainly not an offer.

   Nothing moves at all when the turn changes. Every card, every chip, every
   count and every button is where it was and the size it was, because a turn
   changing has nothing to do with how big a card of wood is. */
#dock.is-watching .actions {
  opacity: 0.5;
  pointer-events: none;
}


/* Your turn, said once, all the way round.
   A hairline of accent along the top edge was doing this before, and an edge
   is a poor place to put a signal: half of it was off the side of the window
   and the rest was a millimetre of colour under the board. The whole outline
   lights instead, with a haze off it, which is a thing you notice in the
   corner of your eye while you are looking at the island. The opponent cards
   say it the same way, in the seat's own colour rather than the accent. */
#dock:not(.is-watching) {
  border-color: var(--gold-line);
  box-shadow: 0 0 0 1px var(--gold-line), 0 0 26px var(--gold-glow);
}

#dock > div {
  background: var(--sheet);
  border: 1px solid var(--hair);
  border-radius: var(--radius-md);
  padding: 8px 10px;
  min-width: 0;
  display: flex;
  flex-direction: column;
  gap: 6px;
}

/* Every heading on this strip is written out of the width contest.
   A heading is a label for a box, and it was setting the box: "your hand
   12 cards" is wider than five cards and their names, so the hand's segment
   was as wide as its own caption and the last card sat with a finger's width
   of nothing to its right. Nothing to contribute across, full width once
   there is a width to fill — the trick the prompt has always used, now said
   once for all five. */
#dock .dock-label {
  width: 0;
  min-width: 100%;
}

.dock-label {
  font-size: var(--fs-n4);
  font-weight: 700;
  letter-spacing: 0.16em;
  text-transform: uppercase;
  color: var(--ink-3);
  display: flex;
  justify-content: space-between;
  gap: 8px;
}

.dock-label span { font-weight: 500; letter-spacing: 0.05em; text-transform: none; }

/* The prompt wears the heading's type and not its layout. Every other heading
   on this strip is a label at one end and a note at the other, which is what
   `space-between` is for; the prompt is one sentence, and a sentence that
   colours two of its words is several nodes rather than one. Spread apart,
   "your turn, and a knight in hand" arrived as three phrases pinned to three
   different places. */
#dock-prompt {
  display: block;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}

/* ---------------------------------------------------------------- hand */

/* The five resources and then the development cards, on one row, centred in
   whatever height the dock turned out to be. */
.hand-row {
  display: flex;
  align-items: center;
  gap: var(--sp-2);
  flex: 1;
  min-height: 0;
  min-width: 0;
}

.hand { display: flex; gap: 6px; flex-wrap: nowrap; align-items: center; }

/* Big enough to read without leaning in. These are the five numbers a player
   checks more often than anything else on the page, and they were previously
   the smallest type on it.

   One fixed size, and the same one whoever's turn it is. Cards that grew into
   whatever room the dock happened to have made the whole strip resize every
   time the turn changed, which is a lot of movement to pay for a little less
   empty space. The column is sized to them instead. */
.card {
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: 3px;
  flex: 0 0 var(--card-w);
  min-height: var(--card-h);
  padding: 5px 6px;
  border-radius: var(--radius-sm);
  background: var(--rc, var(--hair));
  border: 1px solid rgba(0, 0, 0, 0.35);
  color: var(--on-accent, #08202f);
  line-height: 1;
}

.card .n { font-size: var(--fs-3); font-weight: 700; font-variant-numeric: tabular-nums; }
.card .r {
  font-size: var(--fs-n5);
  font-weight: 700;
  letter-spacing: 0.09em;
  text-transform: uppercase;
  opacity: 0.72;
  white-space: nowrap;
}
.card.is-empty { background: transparent; border-color: var(--hair); color: var(--ink-4); }
.card.is-empty .r { opacity: 1; }

/* A card being counted out for a discard, or spent on a build. */
.card.is-pick { outline: 2px solid var(--gold); outline-offset: 1px; }
.card.is-short { outline: 2px solid var(--robber-lit); outline-offset: 1px; }

/* Over seven, and a seven costs you half of them. The count carries the
   robber's own red because the robber is who it is warning you about, and it
   is the only thing in the hand's heading that ever changes colour. */
#hand-count.is-over { color: var(--robber); font-weight: 700; }

/* Development cards sit to the right of the ore, on the same row and at the
   same height, separated by a rule rather than by being somewhere else. There
   are rarely more than two, and each is as wide as the word on it. */
.hand.devs { gap: 6px; flex-wrap: nowrap; align-items: center; }

.hand.devs:not(:empty) {
  padding-left: var(--sp-2);
  border-left: 1px solid var(--hair);
}

/* Playable right now: clicking it plays it. Marked the way an armed build
   button is marked, because it is the same kind of offer. */
.hand.devs .card.is-playable {
  cursor: pointer;
  border-color: var(--gold);
  box-shadow: 0 0 0 1px rgba(245, 197, 66, 0.35);
}

.hand.devs .card.is-playable:hover { background: rgba(245, 197, 66, 0.28); }
.hand.devs .card.is-playable:focus-visible { outline: 2px solid var(--gold); outline-offset: 2px; }

.hand.devs .card {
  /* As wide as the word on it, and exactly as tall as a resource card. Being
     shorter made them read as a lesser kind of thing sitting on the same
     shelf, which is not what a knight in hand is. */
  flex: 0 0 auto;
  min-width: var(--card-w);
  min-height: var(--card-h);
  background: rgba(245, 197, 66, 0.16);
  border-color: rgba(245, 197, 66, 0.42);
  color: var(--gold);
}

.hand .none { font-size: var(--fs-n3); color: var(--ink-4); letter-spacing: 0.05em; }

/* ------------------------------------------------------------- numbers */

/* The rolls that pay you. One chip per number, and a swatch for every card it
   puts in your hand, so a city reads as two of something rather than as one
   corner that is somehow worth more. */
/* Capped at five to a row, then it wraps.
   Left to run, eleven numbers made this the widest thing on the strip by a
   long way, and the strip's width is what decides whether the game log gets to
   run to the bottom of the window. Two short rows cost about forty pixels of
   height, which the hand was already asking for, and save two hundred and
   fifty of width, which nothing else was going to give back. */
.numbers {
  display: flex;
  gap: 5px;
  flex-wrap: wrap;
  align-items: center;
  align-content: center;
  justify-content: flex-start;
  max-width: calc(var(--num-w) * var(--num-row) + 5px * (var(--num-row) - 1));
  flex: 1;
  min-height: 0;
  overflow: hidden;
}

.numbers .none { font-size: var(--fs-n3); color: var(--ink-4); letter-spacing: 0.05em; }

/* A chip, not a card. Same corner radius, same padding, same vocabulary of
   swatches, and deliberately shorter and narrower: a card is a thing you hold
   and a number is a thing the board does, and once the numbers wrap onto two
   rows the difference has to be visible or the strip reads as ten cards. */
.num {
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: 2px;
  flex: 0 0 var(--num-w);
  min-height: var(--num-h);
  padding: 4px 5px;
  border-radius: var(--radius-sm);
  background: rgba(244, 241, 232, 0.06);
  border: 1px solid var(--hair);
  transition: border-color 200ms ease, background 200ms ease;
}

.num-n {
  font-size: var(--fs-1);
  font-weight: 700;
  line-height: 1;
  font-variant-numeric: tabular-nums;
}

/* A swatch per card the roll pays. A corner on three hot tiles can pay more
   than fits across the tile, so they wrap rather than push the rectangle out
   of shape. */
.num-pays {
  display: flex;
  gap: 2px;
  flex-wrap: wrap;
  justify-content: center;
  max-width: 100%;
}

/* As tall as the word on a resource card, so a chip and a card have the same
   content height and therefore land on the same minimum. */
.num-pays i { display: block; width: 8px; height: var(--fs-n5); border-radius: 1px; }

/* Six and eight, in the ink a printed board prints them in.
   They carried the accent before, which was wrong twice: the accent is the
   colour of a development card and of your own turn, so a hot number looked
   like something you could buy, and it looked like it on somebody else's turn
   too. This is the token's own red, lifted off the printed value only as far
   as a dark panel needs it lifted, and it means one thing.

   That leaves two reds on this strip, which is on purpose and is not a
   collision: this one is set in type and never moves, and it is drawn with a
   solid outline. The robber's is a warning light and is always dashed. */
.num.is-hot { border-color: var(--token-line); background: var(--token-soft); }
.num.is-hot .num-n { color: var(--token-lit); }

/* One tile of a number under the robber; the number still pays, but less. */
.num-pays i.is-held { opacity: 0.32; box-shadow: inset 0 0 0 1px var(--robber); }

/* Every tile of it under the robber: the number pays nothing at all, which is
   the reason to go and move the thing. Dashed, always, so that a held six is
   never mistaken for a hot one at a glance. */
.num.is-dead {
  border-color: var(--robber-line);
  border-style: dashed;
  background: var(--robber-soft);
}
.num.is-dead .num-n { color: var(--robber); text-decoration: line-through; }
.num.is-hot.is-dead { border-style: dashed; }

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

/* Two lines: what you can buy, and what you can do. Centred as a block, so
   the seam between them lands on the middle of the strip and the buttons sit
   either side of it rather than drifting up and down as their number changes. */
.actions {
  display: flex;
  flex-direction: column;
  justify-content: center;
  gap: 6px;
  flex: 1;
  min-height: 0;
  /* Only ever between the two states of a turn, and slow enough to read as a
     change of mode rather than as a flicker. */
  transition: opacity 420ms ease;
}

/* Both lines are present whether or not they have anything in them, and they
   split the height evenly, so the seam between them sits exactly on the middle
   of the strip and stays on that pixel when a knight leaves your hand. */
/* The two lines are the same width as each other, always.
   They are set by whichever of them is wider — the price list, usually, which
   carries four costs — and the other spreads its buttons across that width
   rather than leaving a ragged end. Nothing is stretched and no button changes
   size; only the air between them moves, and `gap` is still the floor, so a
   line that genuinely needs the room simply wraps as it did. */
.action-line {
  justify-content: space-between;
  display: flex;
  /* Grown from their own content rather than from nothing, so a line that has
     to wrap makes the dock taller instead of overflowing it. Both lines are a
     single row of buttons at any width worth having, and equal, so the seam
     lands on the middle; where one has to wrap, a correct height beats an
     exactly centred seam. */
  flex: 1 1 auto;
  gap: 6px;
  flex-wrap: wrap;
  align-items: center;
  min-height: 30px;
}

/* The eight buttons are a fixed vocabulary, but not every screen spells them
   the same: before the roll, "roll the dice" stands where "end turn" stands
   after it, and that pair of words is a couple of dozen pixels wider. Left to
   itself the column would breathe in and out twice a turn, which is the one
   piece of movement the rest of this strip no longer has. A floor as wide as
   the widest spelling settles it — and only where there is width to spare,
   since below this the buttons already have a row to themselves. */
@media (min-width: 861px) {
  #action-row { min-width: 366px; }
}

/* The end of the game is the one screen with a single thing to press on it,
   and that one thing takes the middle of the box rather than the left of it.
   A line with one button in it is spread by nothing, so it is centred by
   hand rather than left against the edge. */
.actions > .action-line:only-child { justify-content: center; }
.action-line:has(> button:only-child) { justify-content: center; }

/* The buy line reads down from the seam and the do line up to it, so the two
   sit against the middle rather than against the edges. */
.action-line.is-buy { align-items: flex-end; }
.action-line.is-do { align-items: flex-start; }

/* The price list. Its buttons carry a cost under the name rather than beside
   it, which is what lets four of them sit on one line in a column a third the
   width the old single row needed. */
.action-line.is-buy button {
  flex-direction: column;
  align-items: flex-start;
  gap: 2px;
  padding: 5px 10px;
}

.action-line.is-buy button .cost { margin-left: 0; }

button {
  appearance: none;
  cursor: pointer;
  display: inline-flex;
  align-items: baseline;
  gap: 7px;
  border-radius: var(--radius-sm);
  border: 1px solid var(--hair-strong);
  background: rgba(244, 241, 232, 0.06);
  color: var(--ink);
  font-size: var(--fs-n3);
  font-weight: 600;
  letter-spacing: 0.1em;
  text-transform: uppercase;
  padding: 7px 11px;
  transition:
    background 180ms cubic-bezier(0.22, 0.61, 0.36, 1),
    border-color 180ms cubic-bezier(0.22, 0.61, 0.36, 1),
    color 180ms cubic-bezier(0.22, 0.61, 0.36, 1),
    opacity 220ms ease,
    transform 180ms cubic-bezier(0.22, 0.61, 0.36, 1);
}

/* This strip changes what it is offering several times a turn, and every one
   of those changes used to arrive instantly. A button lifting a pixel under
   the pointer and settling back, and an armed one settling into its colour
   rather than snapping to it, is the difference between an interface that
   responds and one that flickers. */
button:hover:not(:disabled) { transform: translateY(-1px); }
button:active:not(:disabled) { transform: translateY(0); }

button:hover:not(:disabled) { background: rgba(244, 241, 232, 0.13); border-color: var(--ink-3); }
button:disabled { opacity: 0.32; cursor: not-allowed; }
button.primary { background: var(--gold); border-color: var(--gold); color: var(--on-accent, #08202f); }
button.primary:hover:not(:disabled) { background: #ffd764; border-color: #ffd764; }
button.is-armed { background: var(--gold); border-color: var(--gold); color: var(--on-accent, #08202f); }
button.ghost { border-color: var(--hair); color: var(--ink-3); }

button .label { white-space: nowrap; }

/* What a thing costs, in the colours the rest of the dock uses for resources.
   Written out in words this was the widest thing on the strip, and it was what
   stopped the four things you can buy sitting on one line. */
button .cost {
  display: inline-flex;
  align-items: center;
  gap: 5px;
  text-transform: none;
  letter-spacing: 0;
}

.cost-bit { display: inline-flex; align-items: center; gap: 2px; }

.cost-bit i {
  display: block;
  width: 9px;
  height: 10px;
  border-radius: 1px;
  box-shadow: inset 0 0 0 1px rgba(0, 0, 0, 0.4);
}

.cost-bit b {
  font-size: var(--fs-n5);
  font-weight: 700;
  font-variant-numeric: tabular-nums;
  opacity: 0.85;
}

.hint {
  font-size: var(--fs-n4);
  color: var(--ink-3);
  letter-spacing: 0.05em;
  min-height: 1em;
}

/* The heading and the reason sit in a box whose width is set by its buttons,
   and neither gets a say in it. A sentence measured on one line is wider than
   the eight buttons it is explaining, so left to contribute they would size
   the column — and the strip would then shift by however long the sentence, or
   somebody's name, happened to be. Nothing to contribute across, full width
   once there is a width to fill. */
#dock-prompt,
#dock-actions .hint {
  width: 0;
  min-width: 100%;
}

.hint.is-bad { color: var(--robber-lit); }

/* A line that has just changed is worth a glance and a line that has not
   should not ask for one, so the animation is fired by the script only when
   the words are actually different. */
@keyframes dock-say {
  from { opacity: 0; transform: translateY(3px); }
  to   { opacity: 1; transform: none; }
}

#dock-prompt.is-fresh,
#dock-actions .hint.is-fresh { animation: dock-say 260ms ease both; }

/* Two nouns in this game are also two colours.
   The robber is the piece that takes things away and the knight is the card
   that moves it, and they are the only words down here that a player reacts to
   rather than reads. Wherever a prompt or a hint names one, it is set in the
   colour that thing is drawn in everywhere else. */
#dock em.w-robber,
#dock em.w-knight { font-style: normal; font-weight: 700; }
#dock em.w-robber { color: var(--robber); }
#dock em.w-knight { color: var(--gold); }

/* ------------------------------------------------------------------ you */

/* ---------------------------------------------------------- left in the box */

/* Roads, settlements and cities still unbuilt — drawn as the pieces they are,
   in your colour, rather than as three numerals. A supply is something you
   look at rather than a figure you read: eleven little bars is "plenty" at a
   glance and three is "careful now", and no arithmetic happened either time.

   Every piece is drawn whether or not you still have it, and a spent one is
   left as a ghost of itself, so the strip never changes width as you build and
   what you have spent stays as legible as what is left. */
/* Three shelves, stacked.
   Side by side, the tallest thing in this box was one row of pieces about
   fifteen pixels high in a strip a hundred and fifty tall: the panel was
   mostly the room over and under a single line, with the three counts written
   out in words underneath to fill it. Stacked, the supplies use the height
   they were given, each count sits on the line of the pieces it counts, and
   the whole segment is a third narrower — which is width the strip gives back
   to the game log. */
.pieces {
  display: flex;
  flex-direction: column;
  justify-content: center;
  align-items: flex-start;
  gap: 7px;
  flex: 1;
  min-height: 0;
}

/* The count first and the supply after it, so the three numbers form a column
   that can be read down in one movement. The piece rows are three different
   widths and never could. */
.pile {
  display: grid;
  grid-template-columns: 1.6em max-content;
  align-items: center;
  gap: 9px;
}

/* The pieces themselves, on the baseline they would stand on. */
.pile-row {
  display: flex;
  align-items: flex-end;
  gap: 4px;
  min-height: 17px;
}

.pile-n {
  font-size: var(--fs-n1);
  font-weight: 700;
  line-height: 1;
  color: var(--ink-2);
  text-align: right;
  font-variant-numeric: tabular-nums;
}

/* Three silhouettes, cut out of a coloured block: a length of road, a house,
   and a house with a second hall on it. Small enough that fifteen of the first
   are a strip rather than a queue. */
.pip {
  display: block;
  /* A piece is a fixed size or it is not a piece. Left to the flex default
     these shrank to fit whatever the row was given, and fifteen roads came out
     as fifteen hairlines. */
  flex: none;
  background: var(--pc, var(--ink-3));
  box-shadow: inset 0 0 0 1px rgba(0, 0, 0, 0.35);
}

.pip.is-road { width: 4px; height: 15px; border-radius: 1px; }

/* Fifteen of anything in a row is a thing you estimate rather than count, so
   they stand in threes: close enough to read as one supply, spaced enough that
   the answer is arithmetic on a very small number. */
.pip.is-road:nth-child(3n + 1):not(:first-child) { margin-left: 5px; }

.pip.is-settlement {
  width: 13px;
  height: 14px;
  clip-path: polygon(50% 0, 100% 42%, 100% 100%, 0 100%, 0 42%);
}

.pip.is-city {
  width: 17px;
  height: 16px;
  clip-path: polygon(0 100%, 0 46%, 42% 46%, 42% 18%, 71% 0, 100% 18%, 100% 100%);
}

/* Spent: the same silhouette, all but gone. The shape is the point — it says
   what was there — so it keeps its outline rather than its ink. */
.pip.is-spent { background: var(--pc, var(--ink-3)); opacity: 0.17; box-shadow: none; }

/* None left is a rule you have just run into, so it is coloured like one. */
.pile.is-out .pile-n { color: var(--robber); }

/* ------------------------------------------------------------------ you */

/* The count, and nothing competing with it. It is the only number on this
   strip that says whether you are winning, so it is the biggest thing on it. */
/* The heading sits at the top left of the box, as every heading on this strip
   does; the number and its footnote take the middle of what is left, which the
   two auto margins between them do without a wrapper.

   A set width, because the footnote under the number is a sentence that grows
   a clause every time you take a city or the longest road — and sized to its
   longest line, this box would have shunted the whole strip sideways twice a
   game. Written past `#dock > div`, which is a type selector on an id and
   quietly out-weighs a bare one. */
#dock #dock-you {
  align-items: flex-start;
  width: 158px;
}

/* Two numbers, not one: what the table can see, and what is in your hand on
   top of it. Adding them together on screen would hide the only lead that is
   worth having. */
.vp-big { display: flex; align-items: baseline; gap: 5px; line-height: 0.86; margin-top: auto; }
.vp-pub { font-size: 54px; font-weight: 700; color: var(--gold); }
.vp-op { font-size: var(--fs-1); font-weight: 500; color: var(--ink-3); }
.vp-hid { font-size: 26px; font-weight: 700; color: var(--ink-2); }

/* The breakdown is a footnote to the number, not a paragraph beside it. */
.vp-break {
  font-size: var(--fs-n5);
  color: var(--ink-3);
  letter-spacing: 0.05em;
  line-height: 1.45;
  margin-bottom: auto;
}

.vp-break b { color: var(--ink-2); font-weight: 600; }
.vp-secret { display: block; margin-top: 2px; color: var(--gold); }

/* ================================================================= modal */

#modal {
  position: absolute;
  inset: 0;
  z-index: 6;
  display: grid;
  place-items: center;
  background: var(--sheet-veil, rgba(4, 18, 28, 0.55));
  backdrop-filter: blur(3px);
  -webkit-backdrop-filter: blur(3px);
}

#modal[hidden] { display: none !important; }

.modal-card {
  width: min(460px, 88vw);
  background: var(--sheet-solid);
  border: 1px solid var(--hair-strong);
  border-radius: var(--radius-md);
  box-shadow: 0 18px 50px rgba(0, 0, 0, 0.5);
  padding: var(--sp-2);
  display: flex;
  flex-direction: column;
  gap: 10px;
}

.modal-card h3 {
  margin: 0;
  font-size: var(--fs-n1);
  font-weight: 700;
  letter-spacing: 0.14em;
  text-transform: uppercase;
}

/* An offer is two columns of steppers side by side, which needs more width
   than one column of anything. */
.modal-card.is-wide { width: min(620px, 92vw); }

.modal-sub { margin: 0; font-size: var(--fs-n3); color: var(--ink-3); line-height: 1.45; }
.modal-actions { display: flex; gap: 7px; justify-content: flex-end; flex-wrap: wrap; }

/* ---------------------------------------------------------------- offers */

.offer-cols {
  display: grid;
  grid-template-columns: minmax(0, 1fr) minmax(0, 1fr);
  gap: var(--sp-2);
}

.offer-col { display: flex; flex-direction: column; gap: 6px; min-width: 0; }

/* A resource claimed by the other side of the offer. Dimmed rather than
   removed, so the column keeps its shape and the reason is visible: a card
   cannot be on both sides of a trade. */
.stepper.is-out { opacity: 0.34; border-style: dashed; }

/* An offer put to you, read rather than edited. */
.bundle { display: flex; flex-direction: column; gap: 6px; min-width: 0; }
.bundle-cards { display: flex; gap: 6px; flex-wrap: wrap; align-items: center; }
.bundle-cards .card { flex: 0 0 auto; max-width: none; }
.bundle-cards .none { font-size: var(--fs-n3); color: var(--ink-4); letter-spacing: 0.05em; }

@media (max-width: 560px) {
  .offer-cols { grid-template-columns: minmax(0, 1fr); }
}

/* A row of steppers: give this, take that. */
.stepper-row { display: flex; flex-direction: column; gap: 5px; }

.stepper {
  display: grid;
  grid-template-columns: 1fr auto auto auto;
  align-items: center;
  gap: 7px;
  padding: 4px 6px;
  border-radius: var(--radius-sm);
  border: 1px solid var(--hair);
}

.stepper .name {
  font-size: var(--fs-n3);
  letter-spacing: 0.09em;
  text-transform: uppercase;
  color: var(--ink-2);
  display: flex;
  align-items: center;
  gap: 7px;
}

.stepper .swatch { width: 10px; height: 10px; border-radius: 2px; background: var(--rc); }
.stepper .held { font-size: var(--fs-n4); color: var(--ink-4); }
.stepper .n { font-size: var(--fs-n1); font-weight: 700; min-width: 18px; text-align: center; }
.stepper button { padding: 2px 8px; font-size: var(--fs-n2); letter-spacing: 0; }

.choice-row { display: flex; gap: 6px; flex-wrap: wrap; }

.choice {
  flex: 1 1 auto;
  padding: 9px 8px;
  border-radius: var(--radius-sm);
  border: 1px solid var(--hair);
  background: rgba(244, 241, 232, 0.05);
  cursor: pointer;
  text-align: center;
  font-size: var(--fs-n3);
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: var(--ink-2);
}

.choice:hover { border-color: var(--ink-3); background: rgba(244, 241, 232, 0.1); }
.choice.is-on { border-color: var(--gold); background: rgba(245, 197, 66, 0.16); color: var(--gold); }
.choice .sub { display: block; font-size: var(--fs-n5); letter-spacing: 0.05em; text-transform: none; color: var(--ink-3); margin-top: 3px; }

/* ================================================================ banner */

#banner {
  position: absolute;
  inset: 0;
  z-index: 5;
  display: grid;
  place-content: center;
  text-align: center;
  pointer-events: none;
  opacity: 0;
  transition: opacity 320ms ease;
}

#banner.is-on { opacity: 1; }

#banner .who {
  font-size: clamp(30px, 5vw, 62px);
  font-weight: 700;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: var(--sc, var(--gold));
  text-shadow: 0 4px 30px rgba(0, 0, 0, 0.5);
}

#banner .what {
  font-size: var(--fs-n1);
  letter-spacing: 0.22em;
  text-transform: uppercase;
  color: var(--ink-2);
  margin-top: 6px;
}

/* ================================================================= lobby */

/* Opaque from the first paint, and opaque without any script having run.
   Both colours in the gradient are published by theme.js, and a gradient with
   an undefined colour in it is not a gradient: for the frame or two before the
   script ran there was no sheet here at all and the empty game showed through
   it. The literal fallbacks are the printed pair, so the page is the right
   colour before it is the chosen one, and a flat colour under the gradient
   catches anything the gradient itself cannot. */
#lobby {
  position: fixed;
  inset: 0;
  z-index: 9;
  display: grid;
  place-items: center;
  overflow-y: auto;
  background-color: var(--sheet-solid, #071e2e);
  background-image: radial-gradient(120% 100% at 50% 30%,
    var(--felt-lift, #1a5c85), var(--sheet-solid, #071e2e) 78%);
  transition: opacity 380ms ease;
}

#lobby.is-gone { opacity: 0; pointer-events: none; }

/* Two columns rather than one narrow stack.
   The old card was 430 pixels wide, which is a phone's worth of room given to
   a page opened on a desk, and everything on it had to queue: the tabs, your
   name, the table, the opponents, the seed and the button, in one column that
   ran off the bottom of a laptop as soon as three bots were chosen. The
   decisions divide cleanly in two — what kind of game, and who is at it — so
   they sit side by side, and the one that was always the most interesting gets
   the room to say what it is offering. */
.lobby-card {
  width: min(940px, 92vw);
  margin: auto;
  display: flex;
  flex-direction: column;
  gap: var(--sp-2);
  padding: var(--sp-3);
  text-align: left;
}

.lobby-head {
  display: flex;
  align-items: flex-end;
  justify-content: space-between;
  flex-wrap: wrap;
  gap: var(--sp-2);
  padding-bottom: var(--sp-1);
  border-bottom: 1px solid var(--hair);
}

.lobby-head .tabs { margin: 0; flex: 0 1 420px; }

.lobby-grid {
  display: grid;
  grid-template-columns: minmax(0, 1fr) minmax(0, 1fr);
  gap: var(--sp-3);
  align-items: start;
}

.lobby-col { display: flex; flex-direction: column; gap: var(--sp-2); min-width: 0; }

.lobby-card h1 {
  margin: 0;
  font-size: clamp(36px, 7vw, 58px);
  font-weight: 700;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  line-height: 1;
}

.lobby-sub {
  margin: 2px 0 0;
  font-size: var(--fs-n2);
  letter-spacing: 0.14em;
  text-transform: uppercase;
  color: var(--ink-3);
}

.lobby-none {
  margin: 0;
  padding: var(--sp-2);
  border: 1px dashed var(--hair);
  border-radius: var(--radius-md);
  font-size: var(--fs-n3);
  line-height: 1.5;
  color: var(--ink-4);
}

.lobby-field { display: flex; flex-direction: column; gap: 6px; }

.lobby-field label {
  font-size: var(--fs-n4);
  font-weight: 700;
  letter-spacing: 0.16em;
  text-transform: uppercase;
  color: var(--ink-3);
}

.lobby-field label .note { font-weight: 500; letter-spacing: 0.05em; text-transform: none; color: var(--ink-4); }

.seg { display: flex; gap: 6px; }
.seg button { flex: 1; }
.seg button.is-on { background: var(--gold); border-color: var(--gold); color: var(--on-accent, #08202f); }

/* ------------------------------------------------------- carrying on */

/* A game left running, offered above the dials because carrying on is not a
   variation on setting a new table. */
/* Across the top of both columns: it is not a variation on setting a table,
   it is the alternative to setting one. */
.resume {
  display: flex;
  align-items: center;
  justify-content: space-between;
  flex-wrap: wrap;
  gap: var(--sp-1) var(--sp-2);
  padding: var(--sp-1) var(--sp-2);
  border-radius: var(--radius-md);
  border: 1px solid var(--gold-line, rgba(245, 197, 66, 0.4));
  background: var(--gold-soft, rgba(245, 197, 66, 0.07));
}

.resume-say { display: flex; flex-direction: column; gap: 2px; min-width: 0; }

.resume-lead {
  font-size: var(--fs-n4);
  font-weight: 700;
  letter-spacing: 0.16em;
  text-transform: uppercase;
  color: var(--gold);
}

#resume-what { font-size: var(--fs-n3); color: var(--ink-3); letter-spacing: 0.05em; }

.resume-do { display: flex; align-items: center; gap: 7px; }

#resume-go { padding: 10px 18px; font-size: var(--fs-n2); }
#resume-drop { padding: 6px 10px; font-size: var(--fs-n5); letter-spacing: 0.1em; }

/* --------------------------------------------------------- the opponents */

/* One row per bot seat: what that seat plays like. The label sits with the
   control rather than above it, because there are up to three of them and a
   stack of six lines reads as a form. */
.personas { display: flex; flex-direction: column; gap: 7px; }

/* Three things on a row: which seat, what it is playing, and what that means.
   A select can only ever show a name, and eight names on their own are eight
   words nobody has any way of choosing between. */
.persona-row {
  display: grid;
  grid-template-columns: 5.5em minmax(0, 1fr);
  align-items: center;
  gap: 4px 8px;
}

.persona-lean {
  grid-column: 2;
  font-size: var(--fs-n4);
  line-height: 1.4;
  letter-spacing: 0.04em;
  color: var(--ink-4);
}

.persona-row label {
  font-size: var(--fs-n5) !important;
  letter-spacing: 0.1em !important;
  color: var(--ink-4) !important;
}

.persona-row select {
  appearance: none;
  -webkit-appearance: none;
  width: 100%;
  background: rgba(0, 0, 0, 0.28);
  border: 1px solid var(--hair);
  border-radius: var(--radius-sm);
  color: var(--ink);
  font-size: var(--fs-n3);
  font-weight: 600;
  letter-spacing: 0.08em;
  padding: 7px 9px;
  cursor: pointer;
}

.persona-row select:focus { outline: none; border-color: var(--gold); }
.persona-row select option { background: var(--sheet-solid); color: var(--ink); }

/* Sits on the label line, which is where a second thought about the same
   field belongs. */
.lobby-field label .shuffle {
  float: right;
  margin-top: -3px;
  padding: 3px 7px;
  font-size: var(--fs-n6);
  letter-spacing: 0.1em;
  border-color: var(--hair);
  color: var(--ink-3);
}

/* And the whole set, for anybody who wants to read it rather than trust the
   half-line. Closed by default, because somebody who has played before does
   not need it twice, and on the page rather than in the README because this is
   where the choice is being made. */
.leanings {
  border: 1px solid var(--hair);
  border-radius: var(--radius-md);
  background: rgba(0, 0, 0, 0.16);
}

.leanings > summary {
  cursor: pointer;
  list-style: none;
  padding: 9px 11px;
  font-size: var(--fs-n4);
  font-weight: 700;
  letter-spacing: 0.16em;
  text-transform: uppercase;
  color: var(--ink-3);
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 8px;
}

.leanings > summary::-webkit-details-marker { display: none; }

.leanings > summary::after {
  content: '+';
  font-size: var(--fs-0);
  font-weight: 500;
  color: var(--ink-4);
  line-height: 1;
}

.leanings[open] > summary::after { content: '\2212'; }
.leanings > summary:hover { color: var(--ink-2); }

#leaning-list {
  display: grid;
  grid-template-columns: minmax(0, 1fr) minmax(0, 1fr);
  gap: 1px;
  max-height: 46vh;
  overflow-y: auto;
  border-top: 1px solid var(--hair);
  background: var(--hair);
}

.leaning { padding: 9px 11px; background: var(--sheet-solid, #071e2e); }

.leaning h3 {
  margin: 0;
  font-size: var(--fs-n2);
  font-weight: 700;
  letter-spacing: 0.1em;
  text-transform: uppercase;
  color: var(--gold);
}

.leaning-lean {
  margin: 2px 0 0;
  font-size: var(--fs-n4);
  letter-spacing: 0.05em;
  color: var(--ink-2);
}

.leaning-about {
  margin: 5px 0 0;
  font-size: var(--fs-n4);
  line-height: 1.55;
  color: var(--ink-4);
}

#lobby-seed, #lobby-name {
  background: rgba(0, 0, 0, 0.28);
  border: 1px solid var(--hair);
  border-radius: var(--radius-sm);
  color: var(--ink);
  font-size: var(--fs-n2);
  letter-spacing: 0.06em;
  padding: 9px 10px;
}

#lobby-seed:focus, #lobby-name:focus { outline: none; border-color: var(--gold); }

/* The one press this whole page exists to collect. */
#lobby-start, #host-open, #join-go { padding: 13px; font-size: var(--fs-n2); }
#lobby-start { margin-top: auto; }

.lobby-foot {
  margin: 0;
  font-size: var(--fs-n4);
  line-height: 1.55;
  color: var(--ink-4);
}

.lobby-foot a { color: var(--ink-3); }

/* ============================================================ small print */

/* One row of five segments is a lot to ask of a narrow window, so they leave
   in the order of what they are worth, widest query first so that the narrower
   ones override them.

   The buttons are what suffer first when the row is short — squeezed, they
   wrap and the dock grows to hold them — so each thing that goes gives its
   width to the options. */

/* The box count goes. It is the slowest-moving thing on the strip: a piece
   count changes a handful of times in a game, so its absence costs least. */
@media (max-width: 1520px) {
  #dock #dock-box { display: none; }
  #dock {
    grid-template-columns:
      max-content fit-content(440px) minmax(min-content, auto) max-content;
  }
}

/* Small enough that a full-size hand and the numbers stop leaving the options
   a usable share. A smaller card keeps them on one row a while longer. */
@media (max-width: 1240px) {
  :root {
    --card-w: 46px; --card-h: 64px;
    --num-w: 42px; --num-h: 37px;
  }
  button { padding: 6px 9px; letter-spacing: 0.07em; }
  .action-line.is-buy button { padding: 5px 8px; }
  button .cost { gap: 4px; }
  .vp-pub { font-size: 46px; }
}

/* And the victory count. It is the one thing down here that the turn strip
   already says. */
@media (max-width: 1150px) {
  :root {
    --card-w: 44px; --card-h: 60px;
    --num-w: 40px; --num-h: 35px;
    /* One fewer to a row: what is left of the strip at this width is the hand,
       the numbers and eight buttons, and the buttons are what suffer first. */
    --num-row: 4;
  }
  /* Written past `#dock > div`, which is a type selector on an id and was
     quietly out-weighing a bare id. */
  #dock #dock-you { display: none; }
  #dock {
    grid-template-columns: max-content fit-content(360px) minmax(min-content, auto);
  }
}

/* Under about 1100px the left rail stops earning its width. The board and the
   dock are the game; the readouts fold away. */
@media (max-width: 1100px) {
  #app { grid-template-columns: minmax(0, 1fr) var(--rail-r); }
  #rail-left { display: none; }
  #grip-left { display: none; }
}

@media (max-width: 820px) {
  #app { grid-template-columns: minmax(0, 1fr); }
  #rail-right { display: none; }
  #grip-right { display: none; }

  /* Across the window again, still floating in its own gutter. The strip stops
     short of the right-hand edge so the game log can have what is left, and at
     this width there is no log beside it to give anything to. */
  #dock {
    right: var(--gap);
    width: auto;
    max-width: none;
  }

  /* The options take a row of their own: two lines of buttons never fitted
     beside two strips of tiles on a phone. */
  #dock { grid-template-columns: max-content minmax(0, 1fr); }
  #dock-hand    { grid-column: 1; grid-row: 1; }
  #dock-numbers { grid-column: 2; grid-row: 1; }
  #dock-actions { grid-column: 1 / -1; grid-row: 2; }
  #dock-numbers .numbers { justify-content: center; margin: 0 auto; }
}

/* Narrower than the five cards and their labels can sit beside anything. */
@media (max-width: 560px) {
  #dock { grid-template-columns: minmax(0, 1fr); }
  #dock-numbers { grid-column: 1; grid-row: 2; }
  #dock-actions { grid-column: 1; grid-row: 3; }
  #dock-hand .hand-row { overflow-x: auto; }
  /* One row across the phone rather than a five-wide block with a ragged end:
     there is no other segment beside it competing for the width. */
  #dock-numbers .numbers {
    justify-content: flex-start;
    max-width: none;
    margin: 0;
    flex-wrap: nowrap;
    overflow-x: auto;
  }
  .action-line { justify-content: flex-start; }
}

@media (prefers-reduced-motion: reduce) {
  * { animation-duration: 0.01ms !important; transition-duration: 0.01ms !important; }
}

/* ========================================================= expected yield */

/* One grid for the whole panel rather than one per row.
   Three separate grids can only agree on a column width by being told the same
   number, which is how the names came to be cut to four letters in script: a
   thirty pixel column, decided before anybody was named. Here the rows are
   `display: contents` and the panel owns the tracks, so every name is measured
   against the same column and the column is measured against the panel.

   The name column takes a third of whatever the rail has been dragged to, and
   stops at the width of the longest name plus a little air. Drag the rail out
   and the names spell themselves out; drag it in and they ellipsise. The
   longest name cannot be known in CSS, so `rails.js` measures it once, when a
   seat is named, and publishes it. */
#yield-body {
  display: grid;
  grid-template-columns:
    max(2.4em, min(34%, var(--yield-name-max, 3em)))
    minmax(0, 1fr)
    3.1em;
  align-items: center;
  gap: 4px 7px;
}

.yield-row { display: contents; }

.yield-name {
  font-size: var(--fs-n4);
  font-weight: 700;
  letter-spacing: 0.11em;
  text-transform: uppercase;
  color: var(--sc, var(--ink-2));
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
  min-width: 0;
}

.yield-track {
  display: flex;
  height: 9px;
  border-radius: 2px;
  background: rgba(0, 0, 0, 0.26);
  overflow: hidden;
}

.yield-track i { display: block; height: 100%; width: 0; transition: width 260ms ease; }

.yield-total {
  font-size: var(--fs-n3);
  font-weight: 700;
  text-align: right;
  font-variant-numeric: tabular-nums;
}

/* The key to the bars, under the bars.
   It sat under the whole panel before, which put the green square that stands
   for wood a name's width to the left of the wood segment it is a key to. It
   starts in the bar column now, so the two greens are on the same pixel. */
.yield-legend {
  grid-column: 2 / -1;
  display: flex;
  flex-wrap: wrap;
  gap: 3px 9px;
  margin-top: 4px;
  padding-top: 6px;
  border-top: 1px solid var(--hair);
  font-size: var(--fs-n5);
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: var(--ink-3);
}

.yield-legend span { display: inline-flex; align-items: center; gap: 4px; }
.yield-legend b { width: 7px; height: 7px; border-radius: 1px; }

/* =============================================================== histogram */

/* =============================================================== the roll */

/* What the last roll paid, and to whom. This is where the production lines
   went when the game log stopped carrying them. */
#panel-roll { flex: none; }

/* Four and three make seven, written as that.
   It used to be a large sum with "4 + 3" beside it in small grey type, which
   is a number and a footnote about the number rather than the sentence the
   dice actually said. The two dice are now the same size as the total, because
   they are the same kind of thing; they carry less ink instead, because they
   are the given and the total is the answer. */
.roll-head {
  display: flex;
  align-items: baseline;
  gap: 5px;
  padding-bottom: 6px;
  margin-bottom: 6px;
  border-bottom: 1px solid var(--hair);
}

.roll-n,
.roll-d {
  font-size: var(--fs-3);
  line-height: 1;
  font-variant-numeric: tabular-nums;
}

.roll-d { font-weight: 500; color: var(--ink-3); min-width: 0.62em; text-align: center; }
.roll-n { font-weight: 700; min-width: 1.25em; }

.roll-op {
  font-size: var(--fs-0);
  font-weight: 500;
  color: var(--ink-4);
  padding: 0 1px;
}

/* Six and eight take the ink a printed token prints them in; seven takes the
   robber's, which is a different red on purpose. The dice that made the number
   follow it, a shade back, so the whole equation changes colour together. */
.roll-head.is-hot .roll-n { color: var(--token-lit); }
.roll-head.is-hot .roll-d { color: var(--token-line); }
.roll-head.is-seven .roll-n { color: var(--robber); }
.roll-head.is-seven .roll-d { color: var(--robber-line); }
.roll-head.is-seven .roll-op { color: var(--robber-line); }

.roll-list { display: flex; flex-direction: column; gap: 2px; }

.roll-row {
  display: grid;
  grid-template-columns: 72px minmax(0, 1fr);
  align-items: center;
  gap: 7px;
  padding: 2px 0 2px 6px;
  border-left: 2px solid var(--sc, var(--hair));
}

/* A seat that got nothing still gets a line. Who went without is half of what
   a roll means, and a panel listing only the winners makes you count the
   missing names yourself. */
.roll-row.is-empty { opacity: 0.48; }

.roll-who {
  font-size: var(--fs-n4);
  font-weight: 700;
  letter-spacing: 0.1em;
  text-transform: uppercase;
  color: var(--sc, var(--ink-2));
  overflow: hidden;
  text-overflow: ellipsis;
}

.roll-got { display: flex; gap: 6px; flex-wrap: wrap; align-items: center; }

.roll-gain { display: inline-flex; align-items: center; gap: 3px; }
.roll-gain i { display: block; width: 7px; height: 8px; border-radius: 1px; }

.roll-gain b {
  font-size: var(--fs-n3);
  font-weight: 700;
  font-variant-numeric: tabular-nums;
}

.roll-none { font-size: var(--fs-n4); color: var(--ink-4); letter-spacing: 0.05em; }

.roll-notes { margin-top: 6px; display: flex; flex-direction: column; gap: 3px; }

.roll-note {
  font-size: var(--fs-n4);
  line-height: 1.4;
  letter-spacing: 0.04em;
  color: var(--robber-lit);
}

/* Eleven columns, twice.
   The chart takes whatever the panel has left rather than a written height,
   because the two of them now split the foot of the rail between them and a
   fixed number would have had to be right for every window. */
.hist {
  display: grid;
  grid-template-columns: repeat(11, 1fr);
  gap: 2px;
  align-items: end;
  flex: 1 1 auto;
  min-height: 72px;
}

.hist-col { display: flex; flex-direction: column; min-height: 0; height: 100%; }

.hist-track {
  position: relative;
  flex: 1;
  display: flex;
  align-items: flex-end;
  background: rgba(0, 0, 0, 0.2);
  border-radius: 2px 2px 0 0;
  min-height: 0;
}

/* Shaded down the column rather than filled flat, which is how the exhibit
   next door draws the same chart: a solid reads as a thing standing up, a flat
   block of colour reads as a swatch. */
.hist-track i {
  display: block;
  width: 100%;
  height: 0;
  background: linear-gradient(180deg, var(--hair-strong), rgba(244, 241, 232, 0.14));
  border-radius: 2px 2px 0 0;
  transition: height 240ms ease;
}

/* The 36-outcome expectation, as a dashed line across each column. */
.hist-track u {
  position: absolute;
  left: 0;
  right: 0;
  height: 0;
  border-top: 1px dashed var(--gold);
  opacity: 0.65;
  transition: bottom 240ms ease;
}

.hist-col.is-last .hist-track i {
  background: linear-gradient(180deg, var(--gold), var(--gold-soft));
}
.hist-col.is-last .hist-label { color: var(--gold); }

/* The numbers along the foot are the whole key to both charts, and they were
   the smallest type on the page. One rung up the ladder, and a shade less
   faint with it. */
.hist-label {
  font-size: var(--fs-n3);
  font-weight: 600;
  text-align: center;
  color: var(--ink-3);
  padding-top: 4px;
  font-variant-numeric: tabular-nums;
}

.hist-foot {
  margin-top: 6px;
  padding-top: 6px;
  border-top: 1px solid var(--hair);
  font-size: var(--fs-n5);
  color: var(--ink-4);
  letter-spacing: 0.04em;
  flex: none;
}

/* ------------------------------------------------------- paid by number */

/* The twin chart, stacked in resource colours. One column is everything the
   island has paid the table on that number, in the order the rest of this
   interface lists resources in, building up from the floor. */
/* Full height, and the segments measured against it.
   A stack sized by its own contents cannot give those contents a percentage
   height to be sized by, which is a circle the browser resolves by drawing
   nothing. It fills the track instead and packs its segments against the
   floor, so each one is a plain percentage of the chart. */
.hist-stack {
  display: flex;
  flex-direction: column-reverse;
  justify-content: flex-start;
  width: 100%;
  height: 100%;
  border-radius: 2px 2px 0 0;
  overflow: hidden;
}

.hist-stack i {
  display: block;
  width: 100%;
  height: 0;
  flex: none;
  transition: height 240ms ease;
}

/* The number that has just come up is marked by its label and by the floor it
   stands on, since the column itself is already carrying five colours and has
   none left to spare. An outline round the whole column would have drawn a
   tall empty box over a short bar. */
.hist.is-stacked .hist-col.is-last .hist-label { color: var(--gold); }

/* Its own layer rather than an inset shadow, which the stack standing on it
   would paint straight over. */
.hist.is-stacked .hist-col.is-last .hist-track::after {
  content: '';
  position: absolute;
  left: 0;
  right: 0;
  bottom: 0;
  height: 2px;
  background: var(--gold);
}

/* ====================================================== lobby, three ways in */

.tabs { margin-bottom: 2px; }
.tabs button { flex: 1; font-size: var(--fs-n4); padding: 10px 8px; }

.pane { display: flex; flex-direction: column; gap: var(--sp-2); }
.pane[hidden] { display: none !important; }

.row { display: flex; gap: 7px; }
.row button { flex: 1; }

/* The room code is going to be read off a screen and typed into a chat
   window, so it is set large, spaced, and in the one accent colour. */
.code {
  font-size: clamp(26px, 6vw, 40px);
  font-weight: 700;
  letter-spacing: 0.22em;
  color: var(--gold);
  text-align: center;
  padding: 10px 6px;
  border: 1px dashed rgba(245, 197, 66, 0.45);
  border-radius: var(--radius-md);
  background: rgba(245, 197, 66, 0.07);
  user-select: all;
}

#join-code {
  text-transform: uppercase;
  letter-spacing: 0.3em;
  font-size: var(--fs-1);
  font-weight: 700;
  text-align: center;
}

.netstatus {
  margin: 0;
  min-height: 1.3em;
  font-size: var(--fs-n4);
  letter-spacing: 0.05em;
  color: var(--ink-3);
  line-height: 1.5;
}

.netstatus.is-bad { color: #ff9b7a; }

#host-room { display: flex; flex-direction: column; gap: var(--sp-1); }

/* Below this the two columns are two columns of nothing much, so they stack in
   the order the decisions are made: what kind of game, then who is at it. */
@media (max-width: 760px) {
  .lobby-grid { grid-template-columns: minmax(0, 1fr); }
  .lobby-head { align-items: flex-start; }
  .lobby-head .tabs { flex: 1 1 100%; }
  #leaning-list { grid-template-columns: minmax(0, 1fr); }
  .resume { flex-direction: column; align-items: stretch; }
  .resume-do { justify-content: space-between; }
}
