:root {
  /* The card fill. The frame art carries no fill of its own, so this is the
     only thing that sets the parchment colour, and every accent below is
     picked to stay legible on it. */
  --parchment-bg:   #d8c898;
  --parchment-mid:  #d4a84b;
  --parchment-dark: #503010;
  /* A light tile, and the lighter cream for a highlight, an input or tag text. */
  --cream:          #f2e4b8;
  --cream-hi:       #fdf5e0;
  --ink:            #3b2005;
  --ink-mid:        #603010;
  --gold-title:     #8b5e00;
  --gold-bright:    #c9890a;
  --danger:         #b83232;
  --danger-dark:    #7a1e1e;
  --heading:        #884010;
  /* Faint browns, all the frame brown at three strengths: a fill, a faint
     line, a soft line. --rule is a row's dotted underline; --divider-heavy
     and --divider-light are the two rules between sections. */
  --brown-rgb:      80, 48, 16;
  --tint:           rgba(var(--brown-rgb), 0.08);
  --line-faint:     rgba(var(--brown-rgb), 0.3);
  --line-soft:      rgba(var(--brown-rgb), 0.45);
  --rule:           1px dotted var(--line-faint);
  --divider-heavy:  2px solid var(--parchment-dark);
  --divider-light:  1px solid var(--line-soft);
  --shadow:         rgba(0, 0, 0, 0.28);
  /* Advisory severities (engine.sailing_preview.Advisory.severity). A new
     severity needs a `.advisory-<name>` class below as well as the pair here. */
  --info:           var(--ink-mid);
  --info-dark:      var(--ink);
  --success:        #2e7a2e;
  --discord-blue:   #5865F2;
  /* Layout. A column is 460px; the wider grids are whole multiples of it, so a
     card is never squeezed narrower than the phone reads. */
  --grain:          url(/static/frames/ParchmentGrain.png);
  --card-max:       460px;
  --wide-2:         942px;
  --wide-3:         1424px;
  --card-gap:       22px;
  --gutter:         12px;
}
* { box-sizing: border-box; margin: 0; padding: 0; }
body { background: #4a7ab8; font-family: sans-serif; }
button, input, select, textarea { font-family: inherit; }
.sky-bg { position: fixed; inset: 0; z-index: 0; overflow: hidden; background: #4a7ab8; }
.sky-bg img { position: absolute; width: 100%; height: 100%; object-fit: cover;
  object-position: center; image-rendering: pixelated; image-rendering: crisp-edges; }
.container { position: relative; z-index: 1; min-height: 100vh; display: flex;
  flex-direction: column; align-items: center; padding: 20px var(--gutter) 40px; }
.page-title-banner { font-size: 1.25rem;
  color: #fff; text-shadow: 2px 2px 0 rgba(0,0,0,0.5); text-align: center;
  margin-bottom: 14px; letter-spacing: 0.03em; }
.back-link { display: inline-block; font-size: 0.875rem; color: rgba(255,255,255,0.85);
  text-decoration: none; text-shadow: 1px 1px 0 rgba(0,0,0,0.5); margin-bottom: 10px; }
.back-link:hover { color: #fff; }
/* A hand-drawn 9-slice frame over a tiled grain, drawn at its native size like
   the rest of the page's art. The border is the depth the bar sits at; the
   image still draws a full 32px cell thick, so the corner scrolls keep their
   room and overhang inward, and the padding has to clear them. The fill is
   clipped to the padding box, or it would paint past the torn edge and square
   the card off. tools/build_parchment_frame.py measures all three numbers
   against the art and refuses to build a sheet they no longer fit. */
.parchment { width: 100%; max-width: var(--card-max); position: relative;
  border: solid transparent;
  border-width: 13px 15px 14px 13px;
  border-image: url(/static/frames/Parchment.png) 32 / 32px / 0 stretch;
  padding: 25px 23px 27px 25px;
  background-color: var(--parchment-bg);
  background-image: var(--grain);
  background-repeat: repeat;
  background-clip: padding-box;
  image-rendering: pixelated;
  filter: drop-shadow(5px 5px 0 var(--shadow));
  margin-bottom: var(--card-gap); }
/* The responsive shell (WEBUI_EXPANSION.md §8). Every wide layout is one
   column below 966px, then steps to two and three columns' width. Column
   counts step only where a full-width card still fits. A page opts in to the
   generic grid by wrapping its cards in .page-grid. */
.page-grid, .sail-grid, .console-page, .chardata { display: grid; width: 100%; max-width: var(--card-max);
  grid-template-columns: minmax(0, 1fr); gap: var(--card-gap); align-items: start; }
.event-slot, .battle-roster, .facility-panel { max-width: var(--card-max); }
.page-grid > .parchment, .sail-cell > .parchment, .console-page > .parchment,
.chardata > .parchment { max-width: none; margin-bottom: 0; }
.grid-span-all { grid-column: 1 / -1; }
@media (min-width: 966px) {
  .page-grid, .sail-grid, .console-page, .chardata, .event-slot, .battle-roster,
  .facility-panel { max-width: var(--wide-2); }
  .page-grid { grid-template-columns: repeat(2, minmax(0, 1fr)); }
}
@media (min-width: 1448px) {
  .page-grid, .sail-grid, .console-page, .chardata, .event-slot, .battle-roster,
  .facility-panel { max-width: var(--wide-3); }
  .page-grid { grid-template-columns: repeat(3, minmax(0, 1fr)); }
}
.section-title { font-size: 1rem;
  color: var(--heading);
  margin-bottom: 10px; padding-bottom: 6px; border-bottom: var(--divider-heavy); }
.char-name { font-size: 1.625rem;
  color: var(--ink); line-height: 1.4; margin-bottom: 4px; }
.char-sub { font-size: 0.9375rem;
  color: var(--ink-mid); margin-bottom: 14px; }
/* A ship's name and its hull type, stacked tight so they read as one heading. */
.ship-name { margin-bottom: 2px; }
.ship-type { color: var(--ink-mid); margin-bottom: 8px; }
.stat-row { display: flex; align-items: center; justify-content: space-between;
  padding: 6px 0; border-bottom: var(--rule); gap: 8px; }
.stat-row:last-child { border-bottom: none; }
.stat-label { display: flex; align-items: center; gap: 8px;
  font-size: 0.875rem;
  color: var(--ink-mid); flex: 1 1 auto; }
/* Width holds the widest icon, so icon rows and glyph rows share a gutter. */
.stat-icon { font-size: 1rem; line-height: 1; flex-shrink: 0; width: 32px; text-align: center; }
/* Pixel art, drawn at 16px for the buttons and 32px for everything else, renders
   crisp at that size; the one exception is button art, below. The art is drawn
   for the surface it sits on and gets no tile of its own. */
.ui-icon { width: auto; vertical-align: -0.3em; flex-shrink: 0;
  image-rendering: pixelated; image-rendering: crisp-edges; }
.ui-icon-16 { height: 16px; }
.ui-icon-32 { height: 32px; }
/* Button art draws at 19px and smoothed, as Discord draws its emoji, to match
   the label's text. */
.btn .ui-icon-16 { height: 19px; image-rendering: auto; vertical-align: -4px; }
/* The refusal pages carry one icon and nothing else, at a clean 2x. */
.error-icon .ui-icon-32 { height: 64px; }
/* Photographic, not pixel art, so it scales smoothly and is sized to sit with
   the 32px icons rather than with the text. The card sets image-rendering for
   its own frame art and that inherits, so this opts back out. */
.faction-flag { height: 24px; width: auto; vertical-align: -0.45em; margin-right: 0.3em;
  flex-shrink: 0; image-rendering: auto; }
/* Standing between two phrases rather than in front of one, so it needs the
   same air on both sides. */
.faction-flag.flag-sep { margin-left: 0.3em; }
/* The value gives way before the label, so a long one (a duration with its
   weather range) wraps inside the parchment. */
.stat-value { font-size: 0.9375rem;
  color: var(--ink); font-weight: bold; text-align: right;
  flex: 0 1 auto; min-width: 0; overflow-wrap: break-word; }
/* Shared text modifiers. These carry no layout, only the weight or colour a
   line needs, so they compose with whatever block they sit in. */
.txt-small  { font-size: 0.75rem; }
.txt-note   { font-size: 0.8125rem; }
.txt-body   { font-size: 0.875rem; }
.txt-bold   { font-weight: bold; }
.txt-mid    { color: var(--ink-mid); }
.txt-danger { color: var(--danger); }
.txt-success { color: var(--success); }
.txt-caution { color: var(--gold-title); }
.nowrap { white-space: nowrap; }
/* Shared pieces: the house style for text, boxes, button groups and art. A
   page's own rules carry only what is unique to it, so check here before
   adding one. */
/* Body text, and the two sizes of muted aside under it. */
.status-line, .box-text, .facility-p { font-size: 0.875rem; color: var(--ink); line-height: 1.5; }
.fine-print, .status-note { font-size: 0.75rem; color: var(--ink-mid); }
.side-note { font-size: 0.8125rem; color: var(--ink-mid); }
/* A ruled box: a job, a commission, a running total. */
.box { border: 2px solid var(--parchment-dark); padding: 8px 10px; margin-bottom: 10px; }
.box::after { content: ""; display: block; clear: both; }
.box-tint { background: var(--tint); }
.box-head { display: flex; align-items: flex-start; justify-content: space-between; gap: 8px; }
.box-head > .btn { flex: none; }
.box-title { font-weight: bold; color: var(--ink); margin-bottom: 4px; }
.box-text { margin: 4px 0; }
/* Buttons in a wrapping row, each sized to its label. In a box the box's
   padding is the margin, so they drop their own. */
.btn-group { display: flex; flex-wrap: wrap; gap: 0 10px; margin-top: 8px; }
.btn-group > .btn { width: auto; min-width: 10em; }
.box .btn-group > .btn, .trainer-confirm .btn-group > .btn { margin-bottom: 0; }
/* A card that is itself a button: an officer to hire, a patron to pick. */
.pick { display: flex; width: 100%; text-align: left; font: inherit; color: var(--ink); cursor: pointer;
  background: rgba(212, 168, 75, 0.35); border: 2px solid var(--parchment-dark);
  box-shadow: 3px 3px 0 0 var(--parchment-dark); }
.pick:hover { background: rgba(212, 168, 75, 0.65); }
.pick:active { transform: translate(3px, 3px); box-shadow: none; }
.pick[aria-pressed="true"] { background: var(--parchment-mid); border-color: var(--parchment-dark);
  transform: translate(3px, 3px); box-shadow: none; }
.pick:disabled { opacity: 0.5; cursor: not-allowed; box-shadow: none; background: transparent; }
/* Pixel art in a dark frame: a scene, a portrait. */
.framed { display: block; flex: none; border: 2px solid var(--parchment-dark); image-rendering: pixelated; }
/* A portrait leading a row in a list (Tavern officers, Lodge captains). */
.face-sm { width: 44px; height: 52px; }
/* Two halves, stacked with a rule between, then side by side from 966px
   (.halves) or, for halves holding wide tables, from 1448px (.halves-wide). */
.halves, .halves-wide { display: grid; grid-template-columns: minmax(0, 1fr); }
.half + .half { border-top: var(--divider-heavy); margin-top: 16px; padding-top: 16px; }
@media (min-width: 966px) {
  .halves { grid-template-columns: repeat(2, minmax(0, 1fr)); column-gap: 14px; }
  .halves > .half + .half { border-top: none; margin-top: 0; padding-top: 0;
    border-left: var(--divider-heavy); padding-left: 14px; }
}
@media (min-width: 1448px) {
  .halves-wide { grid-template-columns: repeat(2, minmax(0, 1fr)); column-gap: 14px; }
  .halves-wide > .half + .half { border-top: none; margin-top: 0; padding-top: 0;
    border-left: var(--divider-heavy); padding-left: 14px; }
}
.parchment-link { color: #204060; font-size: 0.875rem; text-decoration: none; }
.parchment-link:hover { text-decoration: underline; }
.chronicle-entry { padding: 8px 0; border-bottom: var(--rule); }
.chronicle-entry:last-child { border-bottom: none; }
.chronicle-title { cursor: pointer; font-size: 1rem; color: var(--ink); }
.chronicle-summary { font-size: 0.8125rem;
  color: var(--ink-mid); font-style: italic; margin: 6px 0 12px; }
.chronicle-text, .chronicle-line { font-size: 0.875rem; color: var(--ink); line-height: 1.3;
  margin: 0 0 10px; white-space: pre-line; overflow-wrap: anywhere; }
.chronicle-line { color: var(--gold-title); padding-left: 14px; }
.pixel-bar-wrap { width: 100%; height: 12px; background: #c49030;
  border: 2px solid var(--parchment-dark); box-shadow: 2px 2px 0 var(--ink);
  overflow: hidden; margin: 4px 0 8px; }
.pixel-bar-wrap.mini { width: 72px; height: 10px; box-shadow: 1px 1px 0 var(--ink); margin: 0; flex-shrink: 0; }
.pixel-bar-fill { height: 100%;
  background: repeating-linear-gradient(90deg, var(--success) 0px, var(--success) 5px, #3d9e3d 5px, #3d9e3d 7px); }
.pixel-bar-fill.warn { background: repeating-linear-gradient(90deg, #b87800 0px, #b87800 5px, #d49400 5px, #d49400 7px); }
.pixel-bar-fill.danger { background: repeating-linear-gradient(90deg, var(--danger) 0px, var(--danger) 5px, #d44444 5px, #d44444 7px); }
/* How full the bar is. A width belongs in a style attribute, but the CSP has no
   'unsafe-inline' (§6.4), so the fill picks a class instead and the percentage
   is rounded to the whole number these cover. */
.pbw-0 { width: 0%; } .pbw-1 { width: 1%; } .pbw-2 { width: 2%; } .pbw-3 { width: 3%; } .pbw-4 { width: 4%; } .pbw-5 { width: 5%; } .pbw-6 { width: 6%; } .pbw-7 { width: 7%; } .pbw-8 { width: 8%; } .pbw-9 { width: 9%; }
.pbw-10 { width: 10%; } .pbw-11 { width: 11%; } .pbw-12 { width: 12%; } .pbw-13 { width: 13%; } .pbw-14 { width: 14%; } .pbw-15 { width: 15%; } .pbw-16 { width: 16%; } .pbw-17 { width: 17%; } .pbw-18 { width: 18%; } .pbw-19 { width: 19%; }
.pbw-20 { width: 20%; } .pbw-21 { width: 21%; } .pbw-22 { width: 22%; } .pbw-23 { width: 23%; } .pbw-24 { width: 24%; } .pbw-25 { width: 25%; } .pbw-26 { width: 26%; } .pbw-27 { width: 27%; } .pbw-28 { width: 28%; } .pbw-29 { width: 29%; }
.pbw-30 { width: 30%; } .pbw-31 { width: 31%; } .pbw-32 { width: 32%; } .pbw-33 { width: 33%; } .pbw-34 { width: 34%; } .pbw-35 { width: 35%; } .pbw-36 { width: 36%; } .pbw-37 { width: 37%; } .pbw-38 { width: 38%; } .pbw-39 { width: 39%; }
.pbw-40 { width: 40%; } .pbw-41 { width: 41%; } .pbw-42 { width: 42%; } .pbw-43 { width: 43%; } .pbw-44 { width: 44%; } .pbw-45 { width: 45%; } .pbw-46 { width: 46%; } .pbw-47 { width: 47%; } .pbw-48 { width: 48%; } .pbw-49 { width: 49%; }
.pbw-50 { width: 50%; } .pbw-51 { width: 51%; } .pbw-52 { width: 52%; } .pbw-53 { width: 53%; } .pbw-54 { width: 54%; } .pbw-55 { width: 55%; } .pbw-56 { width: 56%; } .pbw-57 { width: 57%; } .pbw-58 { width: 58%; } .pbw-59 { width: 59%; }
.pbw-60 { width: 60%; } .pbw-61 { width: 61%; } .pbw-62 { width: 62%; } .pbw-63 { width: 63%; } .pbw-64 { width: 64%; } .pbw-65 { width: 65%; } .pbw-66 { width: 66%; } .pbw-67 { width: 67%; } .pbw-68 { width: 68%; } .pbw-69 { width: 69%; }
.pbw-70 { width: 70%; } .pbw-71 { width: 71%; } .pbw-72 { width: 72%; } .pbw-73 { width: 73%; } .pbw-74 { width: 74%; } .pbw-75 { width: 75%; } .pbw-76 { width: 76%; } .pbw-77 { width: 77%; } .pbw-78 { width: 78%; } .pbw-79 { width: 79%; }
.pbw-80 { width: 80%; } .pbw-81 { width: 81%; } .pbw-82 { width: 82%; } .pbw-83 { width: 83%; } .pbw-84 { width: 84%; } .pbw-85 { width: 85%; } .pbw-86 { width: 86%; } .pbw-87 { width: 87%; } .pbw-88 { width: 88%; } .pbw-89 { width: 89%; }
.pbw-90 { width: 90%; } .pbw-91 { width: 91%; } .pbw-92 { width: 92%; } .pbw-93 { width: 93%; } .pbw-94 { width: 94%; } .pbw-95 { width: 95%; } .pbw-96 { width: 96%; } .pbw-97 { width: 97%; } .pbw-98 { width: 98%; } .pbw-99 { width: 99%; }
.pbw-100 { width: 100%; }
.btn { display: block; width: 100%; font-size: 0.9375rem; text-align: center; text-decoration: none;
  padding: 10px 12px; border: 3px solid var(--parchment-dark); cursor: pointer;
  letter-spacing: 0.03em; line-height: 1.4; border-radius: 0; margin-bottom: 10px;
  transition: transform 0.05s, box-shadow 0.05s; }
.btn:active { transform: translate(3px, 3px); box-shadow: none !important; }
.btn:disabled { opacity: 0.45; cursor: not-allowed; box-shadow: 2px 2px 0 0 var(--parchment-dark) !important; }
/* Disabled look for non-button elements (a <span> can't be :disabled). pointer-events:none
   also kills the :active/:hover depress the current-port span used to show. */
.btn-off { opacity: 0.5; cursor: not-allowed; pointer-events: none; }
.btn-discord { background: #5865F2; color: #fff;
  box-shadow: 4px 4px 0 0 #3b4ac0, 6px 6px 0 0 var(--shadow); }
.btn-discord:hover { background: #6875f5; }
/* The page-foot way back: smaller than the buttons that actually do something,
   and it shrinks to its text instead of spanning the column. */
.btn-compact { display: inline-block; width: auto; margin-bottom: 0;
  font-size: 0.8125rem; padding: 7px 16px; border-width: 2px;
  box-shadow: 3px 3px 0 0 #3b4ac0, 4px 4px 0 0 var(--shadow); }
.page-foot { text-align: center; margin: 4px 0 2px; }
/* A destination chip in the Set Course / Adjacent Regions pickers: shrinks to
   its label and wraps into rows, rather than spanning the card like .btn does. */
.btn-chip { display: inline-block; width: auto; margin: 4px 4px 0; }
.chip-row { text-align: center; }
/* A second heading in the same parchment (Adjacent, then Distant Regions). */
.chip-row + .section-title { margin-top: 14px; }
/* Sits on a line of running text (the item rows), so it keeps its own margins
   out of the way. */
.btn-mini { display: inline-block; width: auto; padding: 6px 10px; margin: 0; }
/* What a picker says when it has nothing to offer: an empty grid reads as a bug. */
.picker-note { padding: 8px 0; color: var(--ink-mid); text-align: center; }
.btn-action { background: var(--parchment-mid); color: var(--ink);
  box-shadow: 4px 4px 0 0 var(--parchment-dark), 6px 6px 0 0 var(--shadow); }
.btn-action:hover { background: #c49030; }
/* A toggle in force, such as the shore party's order, or the open tab: held
   pressed in. */
.btn-action[aria-pressed="true"], .chardata-nav > .btn[aria-current="page"] { background: #b8862e; transform: translate(3px, 3px); box-shadow: none; }
.btn-danger { background: var(--danger); color: #ffe8e8;
  box-shadow: 4px 4px 0 0 var(--danger-dark), 6px 6px 0 0 var(--shadow); }
.btn-danger:hover { background: #c83a3a; }
/* The commit button on a confirmation. .btn-stop is the same button when the
   thing being confirmed is a heading into a blockade. */
.btn-go { background: var(--success); border-color: #1a5c1a; }
.btn-stop { background: var(--danger); border-color: var(--danger-dark); }
/* A choice that turns something down: Discord's grey secondary button. */
.btn-quiet { background: #b5a88a; border-color: #6b5a3e; }
.btn-quiet:hover { background: #a89a7a; }
/* The small word-tag that trails a name (Home, Equipped, First Mate). */
.tag { display: inline-block; font-size: 0.8125rem; background: var(--gold-title); color: var(--cream-hi);
  padding: 1px 6px; margin-left: 6px; vertical-align: middle; letter-spacing: 0.05em; }
/* The flag alone says flagship, so it reads as a mark beside the ship's name
   rather than a second heading. Half size: a 32px flag outweighs the name. */
.flagship { margin-left: 4px; }
.flagship .ui-icon { height: 16px; vertical-align: -0.15em; }
.stat-grid { display: grid; grid-template-columns: 1fr 1fr; gap: 0;
  padding-left: 36px; }
.stat-grid-cell { display: flex; align-items: center; gap: 6px;
  font-size: 0.75rem;
  color: var(--ink-mid); padding: 3px 0; border-bottom: var(--rule); }
.stat-grid-cell span { color: var(--ink); font-weight: bold; }
/* Labelled cells are wider, so columns wrap to one rather than overflow. */
.stat-grid-labelled { grid-template-columns: repeat(auto-fill, minmax(10em, max-content));
  column-gap: 28px; justify-content: start; padding-left: 0; }
.stat-grid-cell .stat-grid-name { color: var(--ink-mid); font-weight: normal; }
/* The character sheet: the Commodore's, a mate's, a tavern hire's. */
.char-sheet { display: grid; grid-template-columns: 84px minmax(0, 1fr); gap: 12px; align-items: start; }
.char-sheet-face { width: 84px; height: 100px; }
.char-sheet-body { min-width: 0; }
.char-sheet .sheet-levels { margin-bottom: 8px; }
.stat-grid-cell .sheet-xp { color: var(--ink-mid); font-weight: normal; }
.sheet-skills .stat-grid-cell { border-bottom: none; }
/* Under the stats when the yard cap cut a part short. */
.sheet-note { font-size: 0.75rem; color: var(--ink-mid); margin: 4px 0 8px; }
/* Skills: icon and name on a sheet, the icon alone in a table. A skill still
   waiting on art shows its name. */
.skill-list { display: inline-flex; flex-wrap: wrap; gap: 2px 14px; }
.skill-label { display: inline-flex; align-items: center; gap: 4px; }
.skill-icons { display: inline-flex; gap: 2px; align-items: center; vertical-align: middle; }
.skill-text { font-size: 0.6875rem; font-weight: normal; }
/* Bank has only 16px art; a clean 2x keeps it level with its 32px neighbours. */
.stat-grid-cell .stat-icon .ui-icon-16 { height: 32px; }
.error-icon { font-size: 2.5rem; margin-bottom: 12px; text-align: center; }
.error-title { font-size: 1.25rem;
  color: var(--danger); margin-bottom: 10px; text-align: center; line-height: 1.6; }
.error-body { font-size: 0.875rem;
  color: var(--ink-mid); margin-bottom: 20px; line-height: 2; text-align: center; }
.footer { font-size: 0.8125rem;
  color: rgba(255,255,255,0.55); text-align: center; margin-top: 8px;
  line-height: 2; text-shadow: 1px 1px 0 rgba(0,0,0,0.5); }
.footer a { color: inherit; }
/* The sail page (webui/player/sail.py). One column on a phone, in map,
   destinations, regions, seas, notes, events order. 966px up: the map left
   and destinations right, regions and seas beneath, the rest below. */
/* Inset by .parchment's border-width and shadowed like one, so the map's frame
   lines up with the parchments' torn edges. */
.sail-cell > .map-container { max-width: none; width: auto;
  margin: 13px 15px 14px 13px; box-shadow: 5px 5px 0 var(--shadow); }
@media (min-width: 966px) {
  .sail-grid { grid-template-columns: repeat(2, minmax(0, 1fr)); }
  .sail-map { grid-column: 1; grid-row: 1; }
  .sail-dest { grid-column: 2; grid-row: 1; }
  .sail-regions { grid-column: 1; }
  .sail-seas { grid-column: 2; }
}
@media (min-width: 1448px) {
  .sail-grid { grid-template-columns: minmax(0, 1.6fr) minmax(0, 1fr); }
}
.map-container { position: relative; width: 100%; max-width: var(--card-max);
  margin-bottom: var(--card-gap);
  border: 3px solid var(--parchment-dark); box-shadow: 7px 7px 0 5px var(--shadow); }
.map-container img { width: 100%; display: block;
  image-rendering: pixelated; image-rendering: crisp-edges; }
.notice { font-size: 0.75rem;
  color: var(--ink-mid); line-height: 1.5; margin-bottom: 12px;
  padding: 8px 10px; border: 2px dotted #c49030; background: rgba(196,144,48,0.12); }
/* One mission on the journal page, and one line of a market event on the
   region page: both are a stack of lines with a rule under them. */
.quest-block { padding: 10px 0; border-bottom: var(--rule); }
.quest-title { font-size: 0.875rem; color: var(--ink); font-weight: bold; margin-bottom: 4px; }
.quest-desc { margin-bottom: 6px; }
.quest-objectives-title { margin: 8px 0 2px; }
.quest-objectives { list-style: none; margin: 0; padding: 0; }
.obj { display: flex; align-items: center; gap: 8px; font-size: 0.8125rem; color: var(--ink); padding: 3px 0; }
.obj-done { color: var(--ink-mid); }
.obj-mark { display: inline-flex; justify-content: center; width: 16px; flex: none; }
/* An open objective: an empty box the size of the 16px tick art. */
.obj-open { width: 10px; height: 10px; border: 2px solid var(--ink-mid); }
.quest-buttons { display: flex; justify-content: flex-end; margin-top: 6px; }
.item-block { padding: 8px 0; border-bottom: var(--rule); }
/* Indented past the icon gutter, under the item it describes. */
.item-desc { padding-left: 36px; margin-bottom: 4px; }
.event-row { padding: 4px 0; border-bottom: var(--rule); }
/* The season a set of navigation notes describes, above the notes themselves. */
.season-label { font-size: 0.75rem; color: var(--gold-title); margin-bottom: 6px; }
/* A departure advisory. The card's frame is art, so a severity cannot tint the
   border the way it used to: it marks the card with a rule down the inside edge
   and colours the heading. The rule is a background layer rather than an inset
   shadow, which would land underneath the frame's own bar. Each severity needs
   its own class here. */
.advisory { background-image: linear-gradient(var(--advisory), var(--advisory)), var(--grain);
  background-repeat: no-repeat, repeat;
  background-size: 4px 100%, auto;
  background-position: 4px 0, 0 0; }
.advisory .section-title { color: var(--advisory); }
.advisory-body { font-size: 0.8125rem; color: var(--advisory-dark); }
.advisory-info { --advisory: var(--info); --advisory-dark: var(--info-dark); }
.advisory-danger { --advisory: var(--danger); --advisory-dark: var(--danger-dark); }
.portrait-grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(88px, 1fr));
  gap: 8px; justify-content: center; }
.portrait-cell { margin: 0; }
.portrait-cell button { display: block; width: 100%; padding: 4px; cursor: pointer;
  line-height: 0; background: var(--cream); border: 3px solid var(--parchment-dark);
  box-shadow: 3px 3px 0 0 var(--shadow);
  transition: transform 0.05s, box-shadow 0.05s; }
.portrait-cell button:hover { background: var(--cream-hi); }
.portrait-cell button:active { transform: translate(3px, 3px); box-shadow: none; }
.portrait-cell.selected button { border-color: var(--gold-bright); background: var(--cream-hi);
  box-shadow: 0 0 0 2px var(--gold-bright), 3px 3px 0 0 var(--shadow); }
.portrait-cell img { display: block; width: 80px; height: 96px; margin: 0 auto;
  image-rendering: pixelated; image-rendering: crisp-edges; }
.portrait-group-title { font-size: 0.875rem; color: var(--ink-mid); margin: 6px 0 10px; }
/* The new web captain's naming page: three subjects over a grid of faces at
   their native size. The selected frame is the one the grid fills. */
.parchment.naming { max-width: 720px; }
.naming-head { display: flex; flex-wrap: wrap; align-items: center; justify-content: space-between; gap: 8px; }
.naming-head .section-title { flex: 1 1 14em; margin: 0; }
.naming-head .btn { width: auto; margin: 0 6px 6px 0; padding: 8px 24px; }
.naming-subjects { display: grid; grid-template-columns: minmax(0, 1fr); gap: 16px; margin: 16px 0; }
@media (min-width: 480px) { .naming-subjects { grid-template-columns: repeat(3, minmax(0, 1fr)); gap: 8px; } }
.naming-subject { display: flex; flex-direction: column; align-items: center; gap: 6px; }
.naming-label { font-weight: bold; color: var(--ink); }
.naming-face { padding: 4px; cursor: pointer; background: var(--cream); border: 3px solid var(--parchment-dark); line-height: 0; }
.naming-face img { width: 84px; height: 100px; image-rendering: pixelated; image-rendering: crisp-edges; }
.naming-face[aria-pressed="true"] { border-color: var(--ink); box-shadow: 0 0 0 2px var(--ink); }
.naming-subject .hull-art { box-sizing: content-box; margin: 5px 0; }
.naming-name { width: 100%; max-width: 14em; text-align: center; }
.naming-name[aria-invalid="true"] { border-color: var(--danger); }
.naming-error { color: var(--danger-dark); }
.naming-hint { margin: 8px 0; }
.naming-privacy { margin: 16px 0 0; }
.naming-privacy ul { margin: 6px 0; padding-left: 1.2em; }
.naming-privacy li { margin-bottom: 4px; }
.naming-grid { display: grid; grid-template-columns: repeat(auto-fill, 52px); gap: 6px; justify-content: center; }
.naming-cell { padding: 3px; cursor: pointer; line-height: 0; background: var(--cream);
  border: 3px solid var(--parchment-dark); }
.naming-cell:hover { background: var(--cream-hi); }
.naming-cell.selected { border-color: var(--gold-bright); background: var(--cream-hi); }
.naming-cell img { width: 40px; height: 48px; image-rendering: pixelated; image-rendering: crisp-edges; }
.discovery-card { display: flex; gap: 12px; align-items: flex-start;
  padding: 12px 0; border-bottom: var(--rule); }
.discovery-card:last-child { border-bottom: none; }
.discovery-thumb { width: 64px; height: 64px; flex-shrink: 0;
  border: 3px solid var(--parchment-dark); box-shadow: 3px 3px 0 0 var(--shadow);
  background: var(--cream); display: flex; align-items: center; justify-content: center;
  overflow: hidden; }
.discovery-thumb img { width: 100%; height: 100%; object-fit: cover; display: block;
  image-rendering: pixelated; image-rendering: crisp-edges; }
.discovery-thumb.empty { font-size: 1.7rem; opacity: 0.5; }
.discovery-body { flex: 1; min-width: 0; }
.discovery-name { font-size: 1rem;
  color: var(--ink); font-weight: bold; line-height: 1.4; margin-bottom: 5px; }
.discovery-meta { font-size: 0.75rem;
  color: var(--gold-title); letter-spacing: 0.03em; text-transform: uppercase; margin-bottom: 5px; }
.discovery-blurb { font-size: 0.75rem;
  color: var(--ink-mid); line-height: 1.5; margin-bottom: 5px; }
.discovery-loc { font-size: 0.75rem;
  color: var(--ink-mid); font-style: italic; }
/* The location console (webui/player/console.py). Below 966px: banner, then the
   two parchments stacked. 966px up: banner across the top, parchments side by
   side. 1448px up: the parchments sit in the outer columns over the scene art,
   which is drawn at 712x400 and so lands at about 2x in the 1424px box. The box
   is as tall as the taller parchment, and the art is cropped to fit. */
.console-art { display: block; border: 2px solid var(--parchment-dark); }
/* The banner's shape is pinned, so the box keeps its height while a resize
   swaps the scene for the banner and the new image is still loading. */
.console-art img { display: block; width: 100%; height: auto; aspect-ratio: 1048 / 400;
  object-fit: cover; image-rendering: pixelated; image-rendering: crisp-edges; }
@media (min-width: 966px) {
  .console-page { grid-template-columns: repeat(2, minmax(0, 1fr)); }
  .console-art { grid-column: 1 / -1; }
}
@media (min-width: 1448px) {
  /* The min-height keeps a sparse console (the wreck) from cropping its scene
     to a sliver: the box never gets shorter than a typical console. */
  .console-page { position: relative; align-items: stretch;
    min-height: 400px;
    grid-template-columns: minmax(0, 0.9fr) minmax(0, 1.2fr) minmax(0, 0.9fr); }
  /* Inset by .parchment's border-width, so no art shows past the parchments'
     outer edges. */
  .console-art { position: absolute; inset: 13px 15px 14px 13px; }
  .console-art img { width: 100%; height: 100%; object-fit: cover; }
  .console-hud { grid-column: 1; grid-row: 1; }
  .console-actions { grid-column: 3; grid-row: 1; }
}
/* The event parchment (webui/player/event.py): above the console, as wide as
   its centre track, so a pending event reads first. Empty, it takes no space. */
.event-slot { display: grid; width: 100%; grid-template-columns: minmax(0, 1fr); }
.event-slot > .parchment { max-width: none; }
.event-text { font-size: 0.9375rem; color: var(--ink); line-height: 1.5; }
.event-text::after { content: ""; display: block; clear: both; }
.event-text p { margin: 0 0 10px; }
/* The art sits right, its top level with the first line of text. */
.event-thumb { float: right; width: 128px; height: auto; margin: 4px 0 6px 12px; }
.event-buttons > .btn:last-child, .event-dismiss { margin-bottom: 0; }
.event-dismiss { margin-top: 14px; }
.event-rule { margin: 14px 0; border-top: var(--divider-heavy); }
/* The battle outcome card (webui/player/battle_outcome.py): Discord's banner, full width. */
.outcome-banner { display: block; width: 100%; height: auto; margin-bottom: 10px; image-rendering: auto; }
/* The conversation parchment (webui/player/conversation.py) shares the event
   slot's layout. Each choice: the line on the left, its arrow on the right. */
.conversation-choices { margin-top: 4px; }
.conversation-choice { display: flex; align-items: center; justify-content: space-between;
  gap: 10px; padding: 5px 0; border-top: var(--divider-light); }
.conversation-line { font-size: 0.9375rem; color: var(--ink); line-height: 1.4; }
.conversation-choice > .btn { flex: none; }
@media (min-width: 1448px) {
  .event-slot { column-gap: var(--card-gap);
    grid-template-columns: minmax(0, 0.9fr) minmax(0, 1.2fr) minmax(0, 0.9fr); }
  .event-slot > .parchment { grid-column: 2; }
}
/* The battle roster: a wide parchment under the console, as wide as the box at
   each breakpoint. Our side and theirs sit side by side from 966px and stack
   below it, so a phone never scrolls inside the parchment. */
.battle-roster, .facility-panel { margin-top: var(--card-gap); }
.roster-side { font-size: 0.9375rem; color: var(--ink); margin-bottom: 4px; }
.roster { width: 100%; border-collapse: collapse; margin-top: 6px; font-size: 0.875rem;
  color: var(--ink); font-variant-numeric: tabular-nums; }
/* The figure columns centre rather than right-align: each one is headed by a
   32px icon that is wider than its numbers, so a right-aligned figure hangs off
   the end of its own heading. */
.roster th, .roster td { padding: 3px 6px; text-align: center; white-space: nowrap; }
.roster th.name, .roster td.name { text-align: left; white-space: normal; overflow-wrap: anywhere; width: 100%; }
.roster thead th { border-bottom: var(--divider-heavy); }
.roster tbody tr:nth-child(even) td { background: var(--tint); }
.roster td.own { font-weight: bold; }
.roster tr.sunk td { color: var(--ink-mid); }
.roster tr.sunk td.name { text-decoration: line-through; }
.battle-orders { margin-top: 14px; }
/* A facility parchment: the table, the buttons in as many rows as they need,
   then the open form. Nine columns don't fit a phone, so only the table scrolls. */
.table-scroll { overflow-x: auto; margin-bottom: 14px; }
/* The Harbor's table: squadron, loading dock and moored hulls in one table so the
   columns line up. A figure sits over its button below 1448px, beside it above. */
.roster.harbor-table th, .roster.harbor-table td { padding: 3px 5px; vertical-align: middle; }
.roster.harbor-table th.name, .roster.harbor-table td.name { width: auto; min-width: 7em; max-width: 18em; }
@media (min-width: 966px) { .roster.harbor-table td.name { min-width: 10em; } }
.harbor-cell { display: flex; flex-direction: column; align-items: center; gap: 2px; }
@media (min-width: 1448px) { .harbor-cell { flex-direction: row; justify-content: center; gap: 6px; } }
.roster.harbor-table tr.harbor-goods td { text-align: left; font-size: 0.8125rem; color: var(--ink-mid);
  white-space: normal; }
.roster.harbor-table tr.harbor-section th { text-align: left; padding: 14px 0 0; background: none;
  border-bottom: var(--divider-heavy); }
.roster.harbor-table thead + tbody tr.harbor-section th { padding-top: 4px; }
.roster.harbor-table tr.harbor-section .section-title { margin: 0; padding-bottom: 4px; border: 0; }
.btn-row.btn-icon { padding: 2px 3px; line-height: 0; }
.btn-row.btn-icon .ui-icon { display: block; }
/* Character Data (webui/player/character.py): the tab strip, then the open pane.
   Below 966px the strip sits on top, two buttons to a row; from 966px it runs
   down the left beside the pane. */
.chardata-nav { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 0 10px; }
.chardata-nav > .btn { display: flex; align-items: center; gap: 6px; text-align: left;
  min-height: 46px; padding: 4px 8px; line-height: 1.2; }
.chardata-nav > .chardata-back { grid-column: 1 / -1; margin: 6px 0 0; }
.chardata-section { margin-top: 18px; }
.sq-totals { margin-bottom: 8px; }
@media (min-width: 966px) {
  .chardata { grid-template-columns: 220px minmax(0, 1fr); }
  .chardata-nav { grid-template-columns: minmax(0, 1fr); }
}
@media (min-width: 1448px) { .chardata { grid-template-columns: 240px minmax(0, 1fr); } }
/* The Squadron table: the Harbor's, with the arrows leading each row and the
   captain's face closing it. The arrow art is one 32px cell, up in its top half
   and down in its bottom, so each button shows its half. The face is the 40x48
   portrait at half size. */
.roster.squadron-table th, .roster.squadron-table td { padding: 3px 3px; }
.roster.squadron-table td.name { min-width: 8em; }
/* Not stretched: the table hands spare width to this column too, and a
   stretched button leaves the art at its left with a gap beside it. */
.squadron-table td.sq-moves { width: 1%; }
.sq-move-pair { display: flex; flex-direction: column; align-items: center; gap: 3px; }
.btn-row.btn-icon.sq-move { padding: 0; }
.sq-move .ui-icon { width: 32px; height: 16px; margin: 0 auto; object-fit: none; object-position: top; }
.sq-move-down .ui-icon { object-position: bottom; }
.sq-move-gap { display: block; width: 36px; height: 20px; }
/* The row under the squadron: Sort Cargo and Distribute Crew in their columns. */
.sq-tools td { padding-top: 6px; }
.sq-tools .btn-row { white-space: normal; line-height: 1.15; max-width: 6em; padding: 2px 4px; font-size: 0.75rem; letter-spacing: 0; }
/* An escort's captain: her face in a small button frame, which opens Reassign Captain. */
.btn-row.btn-icon.sq-captain { padding: 1px; }
.sq-captain .sq-face { border: 0; }
/* A ship's section under the table, opened by her name. */
.sq-detail:not(:empty) { margin-top: 14px; padding-top: 14px; border-top: var(--divider-heavy); }
.sq-detail .facility-buttons, .commodore-buttons { margin-top: 14px; }
.sq-detail .facility-buttons { margin-bottom: 14px; }
/* A ship's sheet: her hull art stands in for a portrait. A roll is how far her
   build sits from a stock hull, coloured by whether it helps. */
.ship-sheet { grid-template-columns: 112px minmax(0, 1fr); }
.ship-sheet .hull-art { width: 112px; height: 112px; }
.stat-grid-cell .roll { font-weight: normal; font-size: 0.6875rem; }
.stat-grid-cell .roll-up { color: var(--success); }
.stat-grid-cell .roll-down { color: var(--danger); }
.qty-input.sq-name-input { width: 100%; }
/* The Mates table: the Squadron's, a face leading each row and the role closing it. */
.mate-face { display: block; }
.mates-table td.mate-role { text-align: left; }
.mates-table .mate-role .btn-row { white-space: nowrap; padding: 2px 6px; font-size: 0.75rem; letter-spacing: 0; }
/* Scrap's Warning mark is 32px art; keep its button as tall as Cancel. */
.harbor-dialog .btn .ui-icon-32 { margin: -8px 0; vertical-align: middle; }
.sq-face { display: block; width: 20px; height: 24px; margin: 0 auto;
  border: 1px solid var(--parchment-dark); image-rendering: pixelated; }
/* The Harbor's forms float over the page: the table can run long. */
.harbor-dialog { position: fixed; inset: 0; margin: auto; height: fit-content; max-height: 85vh;
  overflow: auto; width: min(var(--card-max), calc(100vw - 32px)); max-width: none; color: var(--ink); }
.harbor-dialog::backdrop { background: rgba(20, 10, 0, 0.45); }
.harbor-picks { display: flex; flex-direction: column; gap: 4px; margin: 8px 0 12px; }
.harbor-pick { display: flex; align-items: center; gap: 8px; font-size: 0.875rem; }
.harbor-pick > span:first-of-type { flex: 1; }
.harbor-select { width: 100%; margin: 4px 0 10px; font-size: 0.875rem; padding: 3px; }
.harbor-table td.yard { white-space: normal; min-width: 13em; font-size: 0.8125rem; text-align: left; }
.btn-row { display: inline-block; width: auto; margin: 0; padding: 2px 8px; font-size: 0.8125rem;
  border-width: 2px; box-shadow: 2px 2px 0 0 var(--parchment-dark); }
.facility-buttons { display: grid; grid-template-columns: repeat(auto-fill, minmax(210px, 1fr)); gap: 0 10px; }
.facility-buttons > .btn { padding-left: 6px; padding-right: 6px; white-space: nowrap; }
.facility-form { margin-top: 6px; padding-top: 12px; border-top: var(--divider-heavy); }
.qty-input { width: 12em; max-width: 100%; font-size: 1rem; padding: 6px 8px; margin: 6px 0 2px;
  border: 2px solid var(--parchment-dark); background: var(--cream-hi); color: var(--ink); }
/* The Guild (webui/player/guild.py): jobs in a two-wide grid, each box with its
   Confirm at the top right, then the campaigns full width. One column on a phone. */
.guild-offers { display: grid; grid-template-columns: minmax(0, 1fr); gap: 10px; margin-bottom: 10px; }
@media (min-width: 640px) { .guild-offers { grid-template-columns: repeat(2, minmax(0, 1fr)); } }
.guild-offers > .box { margin-bottom: 0; }
.guild-empty { font-size: 0.875rem; color: var(--ink); margin-bottom: 10px; }
.guild-active { margin-bottom: 10px; }
.guild-active > .box-text { margin: 2px 0; }
/* A campaign box, at the Guild and the Palace (webui/player/campaigns.py). */
.campaign-goal { display: flex; align-items: center; gap: 8px; font-size: 0.875rem; color: var(--ink);
  line-height: 1.4; margin: 3px 0; }
.campaign-thumb { float: right; width: 64px; height: auto; margin: 0 0 6px 10px;
  border: 2px solid var(--parchment-dark); }
/* The Tavern: crew | art | officers at 1448px+. Narrower, the art and its
   buttons take a row of their own; between 966px and 1448px the buttons and the
   gossip box sit beside the art, and crew and officers share the row below.
   Portraits show at their native 40x48 (80x96 on the sheet). */
.tavern-grid { display: grid; grid-template-columns: minmax(0, 1fr); gap: 16px; margin: 6px 0 4px; }
.tavern-centre { order: -1; }
@media (min-width: 966px) {
  .tavern-grid { grid-template-columns: repeat(2, minmax(0, 1fr)); }
  .tavern-centre { grid-column: 1 / -1; display: grid; grid-template-columns: 475px minmax(0, 1fr); gap: 16px; }
  .tavern-centre .tavern-art { margin-bottom: 0; }
  .tavern-side { display: flex; flex-direction: column; }
  .tavern-side .gossip-box { flex: 1 1 0; min-height: 120px; height: auto; }
}
@media (min-width: 1448px) {
  .tavern-grid { grid-template-columns: minmax(0, 1fr) 475px minmax(0, 1fr); }
  .tavern-centre { grid-column: auto; order: 0; display: block; }
  .tavern-centre .tavern-art { margin-bottom: 10px; }
  .tavern-side { display: block; }
  .tavern-side .gossip-box { height: 260px; }
}
.tavern-art { width: 100%; max-width: 475px; height: auto; margin-bottom: 10px;
  aspect-ratio: 475 / 400; object-fit: cover; }
.tavern-ship { display: flex; align-items: center; gap: 8px; padding: 3px 0;
  border-bottom: 1px solid var(--line-faint); }
.tavern-ship:last-of-type { border-bottom: none; }
.tavern-face { width: 42px; height: 50px; flex: none; border: 1px solid var(--parchment-dark);
  background: var(--tint); image-rendering: pixelated; }
.tavern-face-empty { display: inline-block; }
.tavern-ship-body { flex: 1 1 auto; min-width: 0; }
.tavern-ship-line { display: flex; justify-content: space-between; gap: 8px; font-size: 0.875rem; color: var(--ink); }
.tavern-ship-line > span:last-child { flex: none; }
.tavern-ship-name { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; font-weight: bold; }
.tavern-ship .pixel-bar-wrap { width: 100%; margin: 2px 0 1px; }
.tavern-ship > .btn-row { flex: none; }
.tavern-bulk { margin-top: 10px; }
.tavern-col .btn .ui-icon-32 { height: 18px; }
.tavern-mate { align-items: center; gap: 10px; padding: 6px 8px; margin-bottom: 8px; }
.tavern-mate-text { display: flex; flex-direction: column; gap: 3px; min-width: 0; }
.tavern-mate-name { font-weight: bold; font-size: 0.9375rem; }
.tavern-mate-sub { font-size: 0.8125rem; }
.tavern-mate-sub .ui-icon { height: 16px; }
.tavern-dot { color: var(--ink-mid); padding: 0 2px; }
/* Back Alley and Kontor: text two thirds, art one third. Below 966px the panel
   is one 460px column, so the art goes on top at full width. */
.facility-with-art { display: grid; grid-template-columns: minmax(0, 1fr); gap: 14px; }
.facility-art { order: -1; width: 100%; height: auto; aspect-ratio: 1 / 1; object-fit: cover; }
@media (min-width: 966px) {
  .facility-with-art { grid-template-columns: minmax(0, 2fr) minmax(0, 1fr); gap: 18px; align-items: start; }
  .facility-art { order: 0; max-width: 400px; justify-self: end; }
}
.facility-p { margin-bottom: 8px; white-space: pre-line; }
.facility-rule { border-top: var(--divider-heavy); margin: 10px 0 12px; }
/* The Trade Station: buys left, sells right from 966px, stacked below. Each
   offer is a wide button carrying its terms. */
.hts-columns { display: grid; grid-template-columns: minmax(0, 1fr); gap: 18px; margin-top: 8px; }
@media (min-width: 966px) { .hts-columns { grid-template-columns: repeat(2, minmax(0, 1fr)); } }
.hts-filter { width: 100%; margin: 4px 0 8px; }
.hts-list { display: flex; flex-direction: column; gap: 6px; }
.hts-offer { display: flex; flex-direction: column; align-items: stretch; gap: 2px; text-align: left;
  padding: 6px 10px; margin: 0; }
.hts-line1 { display: flex; justify-content: space-between; gap: 10px; font-size: 0.9375rem; }
.hts-good { font-weight: bold; }
.hts-price { font-variant-numeric: tabular-nums; white-space: nowrap; }
.hts-line2 { line-height: 1.4; }
.hts-line2 .ui-icon { vertical-align: -3px; }
.hts-pending { margin-top: 4px; }
.hts-pending .status-line { margin: 0; }
.hts-pending-row { display: flex; flex-wrap: wrap; align-items: center; justify-content: space-between; gap: 6px 10px; }
.hts-pending .status-note { margin: 4px 0 0; }
/* The fill dialog: lots left, the running total and its buttons top right. The
   lots scroll on their own, since a big squadron can hold a great many. */
.hts-fill-dialog { width: min(760px, calc(100vw - 32px)); }
.hts-fill { display: grid; grid-template-columns: minmax(0, 1fr); gap: 14px; }
.hts-fill-total { order: -1; }
@media (min-width: 700px) {
  .hts-fill { grid-template-columns: minmax(0, 3fr) minmax(0, 2fr); align-items: start; }
  .hts-fill-total { order: 0; }
}
.hts-total-box { margin-bottom: 0; }
.hts-total-box .status-line { margin: 0 0 4px; }
.hts-total-box .ui-icon { vertical-align: -3px; }
.hts-fill-buttons > .btn { min-width: 0; flex: 1 1 auto; }
.hts-lots { display: flex; flex-direction: column; gap: 6px; margin-top: 8px; max-height: 50vh;
  overflow-y: auto; padding-right: 4px; }
.hts-lot { display: flex; align-items: center; gap: 10px; padding: 6px 10px; cursor: pointer; flex: none;
  border: 2px solid var(--parchment-dark); background: rgba(255, 255, 255, 0.18); font-size: 0.875rem; color: var(--ink); }
.hts-lot input { position: absolute; opacity: 0; pointer-events: none; }
.hts-lot:has(input:checked) { background: var(--parchment-mid); box-shadow: inset 0 0 0 2px var(--gold-title); }
.hts-lot:has(input:focus-visible) { outline: 2px solid var(--ink); outline-offset: 2px; }
.hts-lot-size { font-weight: bold; min-width: 3.5em; font-variant-numeric: tabular-nums; }
.hts-lot-body { flex: 1; display: flex; flex-direction: column; }
.hts-lot-body .status-note { margin: 0; }
.hts-lot-break { font-size: 0.75rem; color: var(--danger-dark); white-space: nowrap; }
/* The Item Shop: the shelf reads as Discord's, each item a tight three-line block. */
.shop-item { padding: 6px 0; border-bottom: var(--rule); }
.shop-item:last-child { border-bottom: none; }
.shop-item p { margin: 0; }
.shop-item-line { font-size: 0.9375rem; color: var(--ink); }
.shop-purse { margin-top: 10px; }
.shop-select { width: 20em; }
.shop-buy-row { display: flex; flex-wrap: wrap; align-items: center; gap: 6px 10px; margin-top: 8px; }
.shop-buy-row .qty-input { margin: 0; }
.shop-buy-row .btn { width: auto; min-width: 8em; margin: 0; padding-block: 5px; }
/* The Palace: the court runs down the right of the sphere, investment and
   policies, stacking under them on a narrow screen; relations span the foot. */
.palace-cols { display: grid; grid-template-columns: minmax(0, 1fr); gap: 4px 24px; }
@media (min-width: 966px) { .palace-cols { grid-template-columns: minmax(0, 2fr) minmax(0, 3fr); } }
.palace-section { margin-top: 10px; }
.palace-section p { margin: 2px 0; font-size: 0.9375rem; color: var(--ink); }
.palace .faction-flag { height: 16px; vertical-align: -0.2em; margin-right: 0.25em; }
.palace-table th, .palace-table td { vertical-align: top; }
.palace-table.palace-relations { width: auto; }
.palace-table.palace-relations th, .palace-table.palace-relations td { white-space: nowrap; overflow-wrap: normal; padding-inline: 6px 14px; }
.palace-table.palace-relations th.name, .palace-table.palace-relations td.name { width: auto; }
.palace-table th.words, .palace-table td.words { text-align: left; }
.palace-campaigns { margin-top: 14px; }
.palace-court td.words { white-space: normal; }
.palace-table td.num { text-align: right; font-variant-numeric: tabular-nums; }
.palace-court td.name { font-weight: bold; }
.palace-court td.words { font-size: 0.8125rem; color: var(--ink-mid); }
.palace .facility-buttons { margin-top: 12px; }
/* The Patron: the patrons down the left, each row a button, and the picked
   patron on the right. Two columns only once the parchment widens past 460px. */
.patron-cols { display: grid; grid-template-columns: minmax(0, 1fr); gap: 12px 24px; margin-top: 8px; }
@media (min-width: 966px) { .patron-cols { grid-template-columns: minmax(0, 2fr) minmax(0, 3fr); } }
.patron-list { display: flex; flex-direction: column; gap: 8px; }
.patron-pick { flex-direction: column; gap: 2px; padding: 6px 10px; }
.patron-pick-name { font-weight: bold; font-size: 0.9375rem; }
.patron-pick-title { font-size: 0.8125rem; }
.patron .faction-flag { height: 14px; vertical-align: -0.2em; margin-right: 0.2em; }
#patron-pane { scroll-margin-top: 12px; }
.patron-head { margin-bottom: 6px; }
.patron-head::after { content: ""; display: block; clear: both; }
.patron-face { float: right; width: 80px; height: 96px; margin: 0 0 6px 12px; }
.patron-name { font-size: 1.25rem; font-weight: bold; color: var(--ink); }
.patron-title { font-size: 0.9375rem; color: var(--gold-title); margin-bottom: 6px; }
.patron-dialogue { font-style: italic; font-size: 0.9375rem; color: var(--ink); line-height: 1.45; margin: 4px 0; }
.patron-leanings { margin: 4px 0; }
.patron .facility-buttons { margin-top: 4px; }
/* Patron and report parchments share one #facility-panel, so both close together. */
.facility-stack { width: 100%; }
.facility-stack > .parchment { max-width: none; }
.report-picks { display: flex; flex-direction: column; gap: 6px; margin: 8px 0 12px; }
.report-pick { display: flex; flex-wrap: wrap; align-items: center; gap: 2px 8px; font-size: 0.9375rem; color: var(--ink); cursor: pointer; }
.report-pick-name { font-weight: bold; }
.trainer-student { display: flex; align-items: center; gap: 10px; margin: 6px 0; font-size: 0.9375rem; color: var(--ink); }
.trainer-select { width: auto; margin: 0 0 8px; font-size: 0.875rem; }
.trainer-skill { margin: 10px 0; }
.trainer-confirm { border-top: var(--divider-light); margin-top: 8px; padding-top: 6px; }
/* The Bank. The account stays a ledger's width, so on a wide panel each figure
   still reads against its label. */
.bank-account { max-width: 28rem; }
.bank-form { margin-top: 10px; }
/* The Lodge: a captain is portrait | name and flagship | Buy a Drink. The board
   is its own edit field. */
.lodge-captains { margin: 6px 0 14px; }
.lodge-captain { display: flex; align-items: center; gap: 10px; padding: 6px 0; font-size: 0.875rem;
  color: var(--ink); border-bottom: var(--rule); }
.lodge-captain-text { flex: 1; min-width: 0; line-height: 1.4; }
.lodge-captain > .btn-row { flex: none; }
.lodge-board-text { display: block; width: 100%; box-sizing: border-box; margin: 6px 0 10px; padding: 8px 10px;
  font-family: ui-monospace, monospace; font-size: 0.875rem; line-height: 1.45; color: var(--ink);
  background: var(--tint); border: 2px solid var(--parchment-dark); resize: vertical; }
.lodge-board > .btn { width: auto; }
/* Gossip lives only on the page; hidden until the first round. */
.gossip-box { height: 220px; overflow-y: auto; padding: 6px 10px; background: var(--tint);
  border: 2px solid var(--parchment-dark); box-shadow: inset 2px 2px 0 0 var(--line-soft);
  scrollbar-color: var(--parchment-dark) var(--tint); }
.gossip-box:empty { display: none; }
.gossip-entry { padding: 6px 0; border-bottom: 1px solid var(--line-faint); }
.gossip-entry:last-child { border-bottom: none; }
.gossip-head { margin-bottom: 2px; }
.gossip-line { font-size: 0.875rem; font-style: italic; color: var(--ink); line-height: 1.45; margin: 2px 0; }
.gossip-refusal { font-style: normal; }
/* The Market: Buy and Sell side by side only at the widest layout, where both
   tables fit without scrolling. */
.market-halves { margin-bottom: 14px; }
.market-halves .table-scroll { margin-bottom: 4px; }
.market-table td.name { min-width: 8em; }
.roster.market-table tbody tr.tier-row td { text-align: left; font-weight: bold; background: none;
  padding-top: 10px; border-bottom: var(--divider-light); }
.market-table td.trade { display: flex; gap: 6px; align-items: center; }
.qty-row { width: 5em; margin: 0; padding: 2px 6px; font-size: 0.875rem; }
.mini-icon .ui-icon { height: 16px; vertical-align: -0.15em; }
.pl-dot { display: inline-block; width: 12px; height: 12px; border-radius: 50%;
  border: 1px solid var(--ink); vertical-align: middle; }
.pl-gain { background: #3f7a2c; }
.pl-loss { background: #9b2a22; }
.lot-note { font-style: italic; }
.market-foot { margin: 4px 0 0; }
.sell-confirm { margin-top: 10px; }
.readout-gap { height: 8px; }
.appraise-form { display: flex; flex-wrap: wrap; gap: 8px; align-items: center; }
.appraise-form .qty-input { width: auto; margin: 0; }
/* The Shipyard: a worn stat's title names the stock figure and what the wear
   took off the price. Most stats roll a little, so only wear past 1 sigma
   (wear_band 2 and 3) is tinted; tinting every cell would say nothing. */
/* Sized to its content: a full-width name column strands the stats at the far
   edge of a wide parchment. */
.roster.shipyard-table { width: auto; }
.shipyard-table th.name, .shipyard-table td.name { width: auto; white-space: nowrap; padding-right: 16px; }
.shipyard-table td.wear { cursor: help; }
.shipyard-table td.wear-2 { background: rgba(201, 137, 10, 0.42) !important; font-weight: bold; }
.shipyard-table td.wear-3 { background: rgba(184, 50, 50, 0.38) !important; font-weight: bold; }
.name-input { width: 20em; }
/* Remodel: a label, a box and its range on one line each; the readout under them
   is refetched as the boxes change. */
.remodel-fields { display: grid; grid-template-columns: auto 7em 1fr; gap: 4px 10px;
  align-items: center; margin: 8px 0; }
.remodel-field { display: contents; }
.remodel-label { font-weight: bold; }
.remodel-input { width: 100%; margin: 0; }
.remodel-field .status-note { margin: 0; }
@media (max-width: 480px) { .remodel-fields { grid-template-columns: auto 6em; }
  .remodel-field .status-note { grid-column: 1 / -1; margin-bottom: 6px; } }
/* Change Rigging: the ship and port picks side by side, the rig pick heading the New column. */
.rigging-pickers { display: flex; flex-wrap: wrap; gap: 6px 24px; margin-bottom: 8px; }
.rigging-picker { display: flex; flex-direction: column; gap: 2px; }
.rigging-picker select { margin: 0; }
.rigging-table select { margin: 0; max-width: 100%; }
/* New Ships: the hull icon leads the name, and Cancel Build sits beside the
   line naming the build it cancels. */
.shipyard-table td.name .ui-icon-32 { margin: -6px 6px -6px 0; }
.build-status { display: flex; flex-wrap: wrap; align-items: center; gap: 4px 12px; margin: 4px 0 10px; }
.build-status .status-line { margin: 0; flex: 1 1 18em; }
/* The order confirmation: the hull at 3x on its sea beside one odds meter per stat. */
.build-card { display: grid; grid-template-columns: minmax(0, 1fr); gap: 12px 20px; margin: 10px 0 12px; }
@media (min-width: 640px) { .build-card { grid-template-columns: 132px minmax(0, 1fr); } }
.build-portrait { display: flex; flex-direction: column; align-items: center; gap: 4px;
  font-size: 0.8125rem; color: var(--ink); text-align: center; }
.hull-art { width: 96px; height: 96px; image-rendering: pixelated; image-rendering: crisp-edges;
  border: 2px solid var(--parchment-dark); padding: 4px; background: #004fb8
  url(/static/frames/ShipBGSea.png) center / 96px 96px no-repeat; background-origin: content-box; }
.build-price { font-size: 1rem; font-weight: bold; }
.build-name { font-size: 1.25rem; color: var(--ink); }
.build-desc { font-style: italic; margin: 2px 0 8px; }
.meter-label { font-size: 0.8125rem; font-weight: bold; color: var(--gold-title); margin-bottom: 2px; }
.meter-row { display: grid; grid-template-columns: 32px 7.5em minmax(0, 1fr); align-items: end;
  gap: 0 8px; padding: 2px 0; border-bottom: var(--rule); }
.meter-row:last-of-type { border-bottom: none; }
.meter-row .stat-name { font-size: 0.8125rem; color: var(--ink-mid); padding-bottom: 5px; }
.meter-row .ui-icon-32 { align-self: center; }
/* Dark columns are the 1-sigma band (about 7 in 10 builds), gold the rarer tail. */
.odds-meter { width: 100%; max-width: 440px; height: 40px; display: block; overflow: visible; }
.odds-meter text { font: 12px sans-serif; fill: var(--ink); }
.odds-meter text.cap { fill: var(--gold-title); }
.odds-meter text.edge { fill: var(--ink-mid); }
.odds-bar { fill: var(--parchment-dark); }
.odds-bar.tail { fill: var(--gold-bright); }
.odds-base { stroke: var(--parchment-dark); stroke-width: 1; }
/* Parts: a band along the base for the floor the parts raise, and a picker per
   part slot beside the meters on a wide screen, under them on a narrow one. */
.odds-parts { fill: var(--success); }
.part-gain { color: var(--success); }
.part-penalty { color: var(--danger); }
.build-main { container-type: inline-size; }
.build-body { display: grid; grid-template-columns: minmax(0, 1fr); gap: 12px 28px; }
@container (min-width: 900px) {
  .build-body { grid-template-columns: minmax(0, 40em) minmax(17em, 24em); }
}
.part-row { display: grid; grid-template-columns: 7em 32px minmax(0, 1fr); align-items: center;
  gap: 0 8px; padding: 3px 0; }
.part-row .part-slot { font-size: 0.8125rem; color: var(--ink-mid); }
.part-row .qty-input { width: 100%; margin: 0; }
.part-row .part-mods { grid-column: 3; display: flex; flex-wrap: wrap; gap: 2px 12px; font-size: 0.8125rem; }
.part-row .part-mod { display: inline-flex; align-items: center; gap: 2px; }
.rig-row { display: flex; flex-wrap: wrap; align-items: center; gap: 4px 10px; }
.rig-row .qty-input { width: auto; margin: 0; }
.name-input + .status-line { margin-top: 8px; }
/* The investment screen: Discord's inline fields side by side, the rest stacked. */
.invest-row { display: flex; flex-wrap: wrap; gap: 0 32px; }
.invest-section { margin: 0 0 10px; font-size: 0.875rem; }
.invest-label { font-weight: bold; color: var(--gold-title); }
/* The stance in force is pressed and can't be clicked: it should read pressed,
   not faded like a button that is off. */
.btn[aria-pressed="true"]:disabled { opacity: 1; cursor: default; box-shadow: none !important; }
.btn-danger[aria-pressed="true"] { background: #8e2626; transform: translate(3px, 3px); box-shadow: none; }
.console-title { font-size: 1.125rem; color: var(--ink); margin-bottom: 10px;
  padding-bottom: 6px; border-bottom: var(--divider-heavy); }
.hud-row { display: flex; align-items: center; gap: 12px; padding: 6px 0;
  font-size: 1.125rem; color: var(--ink); }
.hud-row:last-of-type { margin-bottom: 14px; }
/* Load over capacity, stacked as a fraction the way the sketch draws it. */
.hud-fraction { display: inline-flex; flex-direction: column; line-height: 1.3; }
.hud-fraction > span + span { border-top: 2px solid var(--ink); }
.status-label { font-size: 0.875rem; color: var(--heading); margin: 12px 0 4px; }
.status-line { margin-bottom: 4px; }
.hud-warnings { margin-top: 6px; }
.status-note { margin: 8px 0 12px; }
.status-ship { display: flex; flex-wrap: wrap; justify-content: space-between; gap: 4px 10px;
  padding: 4px 0; font-size: 0.875rem; color: var(--ink);
  border-bottom: var(--rule); }
.status-close { margin-top: 14px; }
.console-actions > .btn:last-child { margin-bottom: 0; }
/* Two facility buttons to a row, so a busy port's list stays about as tall as
   the left parchment. */
.facility-grid { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 0 10px; }
.facility-grid > .btn { padding-left: 6px; padding-right: 6px; white-space: nowrap; }
/* The shore party's orders: Rest across the top, the four others in pairs. */
.mode-grid > .btn:first-child { grid-column: 1 / -1; }
/* Food and Water borrow the 32px supply art; keep those rows as tall as the rest. */
.mode-grid .ui-icon-32 { margin: -8px 0; vertical-align: middle; }
/* The leaderboard and Captain's log (webui/shell.py _log_dock). The buttons
   sit at the top right of the page's column; each panel drops from them and
   floats over the page, sized like one column, with its own scroll so Close
   stays in view. */
body { --log-inset: max(var(--gutter), calc((100vw - var(--card-max)) / 2)); }
@media (min-width: 966px) {
  body:has(.page-grid, .console-page) { --log-inset: calc((100vw - var(--wide-2)) / 2); }
}
@media (min-width: 1448px) {
  body:has(.page-grid, .console-page) { --log-inset: calc((100vw - var(--wide-3)) / 2); }
}
.log-dock { position: absolute; top: 12px; right: var(--log-inset); z-index: 2;
  display: flex; gap: 10px; }
/* A guest's dock joins the page flow as a header strip, so the warning beside
   the log button never sits over the page title (WEB_ACCOUNTS.md §5.4). */
.log-dock.has-guest { position: static; display: flex; align-items: center;
  justify-content: flex-end; gap: 10px; width: 100%;
  max-width: calc(100vw - 2 * var(--log-inset)); margin-bottom: 12px; }
.guest-banner { display: flex; align-items: center; gap: 6px; min-width: 0;
  padding: 2px 8px; background: var(--cream); color: var(--danger-dark);
  border: 2px solid var(--parchment-dark); box-shadow: 3px 3px 0 0 var(--shadow);
  font-size: 0.8125rem; font-weight: bold; line-height: 1.3; }
.guest-link-btn { width: auto; margin: 0; padding: 4px 12px; font-size: 0.8125rem; white-space: nowrap; }
.log-button { position: relative; display: block; padding: 0; border: 0;
  background: none; cursor: pointer; line-height: 0; }
.log-dot { position: absolute; top: -3px; right: -3px; width: 10px; height: 10px;
  border-radius: 50%; background: var(--danger); box-shadow: 0 0 0 2px #fff; }
.log-panel { margin: 0; inset: auto; top: 52px; right: var(--log-inset);
  width: min(var(--card-max), calc(100vw - 32px)); max-height: calc(100vh - 72px);
  overflow: hidden; color: var(--ink); }
.log-panel:popover-open { display: flex; flex-direction: column; }
.log-list { flex: 1 1 auto; min-height: 0; overflow-y: auto; margin-bottom: 12px; }
.log-entries { list-style: none; }
/* Fixed date and icon columns, the date wide enough for "30 Sep X9:", so every
   entry's text starts at the same place. */
.log-entry { position: relative; display: grid; grid-template-columns: 5.5em 19px minmax(0, 1fr);
  gap: 8px; padding: 6px 0 6px 16px; font-size: 0.875rem; line-height: 1.4;
  border-bottom: var(--rule); }
.log-entry:last-child { border-bottom: none; }
.log-entry.is-unread::before { content: ""; position: absolute; left: 0; top: 0.75em;
  width: 8px; height: 8px; border-radius: 50%; background: var(--danger); }
.log-date { color: var(--ink-mid); white-space: nowrap; }
.log-text { overflow-wrap: break-word; }
/* Button art, so it draws like a button's: 19px and smoothed. */
.log-icon .ui-icon-16 { height: 19px; image-rendering: auto; vertical-align: -4px; }
.log-empty { font-size: 0.875rem; color: var(--ink-mid); }
.log-close { flex: 0 0 auto; margin-bottom: 0; }
/* The leaderboard panel, laid out as Discord's /leaderboard: text left,
   portrait right, rules between sections. */
.board-title { font-size: 1.25rem; margin-bottom: 6px; }
.board-heading { font-size: 1rem; margin-bottom: 6px; }
.board-subtitle { font-size: 0.875rem; margin-bottom: 8px; }
.board-rule { border: 0; border-top: var(--rule); margin: 8px 0; }
.board-row { display: flex; justify-content: space-between; align-items: flex-start;
  gap: 12px; padding: 4px 0; font-size: 0.875rem; line-height: 1.4; }
.board-text { min-width: 0; overflow-wrap: break-word; }
.board-line { font-size: 0.875rem; line-height: 1.4; margin: 4px 0; }
.board-you { margin-top: 2px; font-size: 0.8125rem; color: var(--ink-mid); }
@media (max-width: 499px) {
  .log-panel { right: 16px; }
}

/* Kunda (WEST_AFRICA §10.1). Art is drawn at 1x and shown at 2x. Per-family tint
   and pattern, and the meter's fill steps, come from /kunda/emblems.css. */
.kunda-columns { display: grid; grid-template-columns: minmax(0, 1fr); gap: 0 var(--card-gap); }
@media (min-width: 966px) {
  .kunda-columns { grid-template-columns: repeat(2, minmax(0, 1fr)); }
  .kunda-next { border-left: var(--divider-light); padding-left: var(--card-gap); }
}
@media (max-width: 965px) {
  .kunda-next { border-top: var(--divider-light); padding-top: 8px; margin-top: 8px; }
}
.kunda-season { margin: 4px 0 2px; font-size: 1rem; }
.kunda-slot { display: flex; gap: 10px; align-items: flex-start; margin: 10px 0; }
.kunda-crest { flex: none; text-align: center; }
.kunda-family { font-weight: bold; margin-bottom: 4px; }
.kunda-standing { font-size: 0.875rem; margin-top: 4px; }
.kunda-info { flex: 0 1 auto; min-width: 0; display: flex; flex-wrap: wrap; gap: 0 12px; }
.kunda-list { flex: 0 1 auto; min-width: 7em; font-size: 0.875rem; }
.kunda-list h4 { margin: 22px 0 4px; font-size: 0.875rem; }
.kunda-list ul { list-style: none; }
.kunda-list ul + h4 { margin-top: 8px; }
.kunda-locked p { font-size: 0.875rem; color: var(--ink-mid); font-style: italic; }
.kunda-locked { align-items: center; }

/* The emblem: a raised canvas frame with the family's pattern inside. Pressed
   and flat (next season, not a button) both use the lowered frame. */
.kunda-emblem { position: relative; display: block; width: 96px; height: 112px; padding: 0;
  border: 0; background: url("/static/kunda/canvas_frame_up.png") 0 0 / 96px 112px no-repeat;
  image-rendering: pixelated; cursor: pointer; }
.kunda-emblem .kunda-pattern { position: absolute; left: 4px; top: 4px; width: 80px; height: 96px;
  background-color: #888; background-size: 80px 96px; background-blend-mode: multiply;
  image-rendering: pixelated; }
.kunda-emblem:active, .kunda-emblem.is-flat {
  background-image: url("/static/kunda/canvas_frame_down.png"); }
.kunda-emblem:active .kunda-pattern, .kunda-emblem.is-flat .kunda-pattern { left: 8px; top: 8px; }
.kunda-emblem.is-flat { cursor: default; filter: saturate(0.45) opacity(0.8); }
.kunda-emblem.is-locked .kunda-pattern {
  background: url("/static/kunda/locked_slot.png") 0 0 / 80px 96px; }
.kunda-emblem:focus-visible { outline: 2px solid var(--ink); outline-offset: 2px; }

/* The appetite meter: tube caps and a repeating body, with the fill behind the
   glass. The fill runs inside the tube's walls (2px in, 4px off each end). */
.kunda-meter { position: relative; flex: none; display: flex; flex-direction: column;
  width: 16px; height: 112px; margin-top: 22px; isolation: isolate; image-rendering: pixelated; }
.kunda-meter-top { height: 18px; background: url("/static/kunda/meter_top.png") 0 0 / 16px 18px; }
.kunda-meter-body { flex: 1; background: url("/static/kunda/meter_body.png") 0 0 / 16px 8px repeat-y; }
.kunda-meter-bottom { height: 14px; background: url("/static/kunda/meter_bottom.png") 0 0 / 16px 14px; }
/* Blue is what the caravan will still take; green, on the table only, is what
   the offer there would use of it. */
.kunda-meter-fill, .kunda-meter-pending { position: absolute; left: 2px; bottom: 4px; width: 12px;
  height: 0; }
.kunda-meter-fill { z-index: -1; background: #3b6ea5; }
.kunda-meter-pending { z-index: -2; background: #5a9a4a; }

/* The barter table (WEST_AFRICA §10.3): four columns at full width, two pairs
   in the middle, and Yours then Theirs stacked on a phone. */
.barter-head { display: flex; gap: 12px; align-items: flex-start; margin-bottom: 8px; }
.barter-head-text { flex: 1; min-width: 0; }
.barter-head > .btn { flex: none; width: auto; }
.kunda-emblem.is-flat.is-table { filter: none; }
.kunda-speech { margin: 0 0 8px; padding: 6px 10px; border-left: 3px solid var(--line-soft);
  font-style: italic; }
.barter-columns { display: grid; grid-template-columns: minmax(0, 1fr); gap: 8px var(--card-gap); }
@media (min-width: 700px) {
  .barter-columns { grid-template-columns: repeat(2, minmax(0, 1fr)); }
}
@media (min-width: 1200px) {
  .barter-columns { grid-template-columns: repeat(4, minmax(0, 1fr)); }
}
@media (max-width: 699px) {
  .barter-your-offer { order: 1; }
  .barter-your-goods { order: 2; }
  .barter-their-offer { order: 3; }
  .barter-their-goods { order: 4; }
}
.barter-col h4 { margin: 4px 0; font-size: 0.875rem;
  border-bottom: var(--divider-light); }
.barter-row { display: flex; flex-wrap: wrap; align-items: center; justify-content: space-between;
  gap: 2px 6px; padding: 3px 0; font-size: 0.875rem; }
.barter-good { display: flex; flex-direction: column; min-width: 0; }
.barter-line .barter-good { flex: 1; }
.barter-value { font-variant-numeric: tabular-nums; }
.barter-row .pl-dot { margin-left: 6px; }
.barter-control { display: flex; gap: 4px; align-items: center; }
.barter-empty { font-size: 0.875rem; color: var(--ink-mid); font-style: italic; }
.btn-icon { padding: 2px 4px; }
.tag-sought { color: #1e5a2a; font-weight: bold; }
.tag-wanted { color: #24507a; font-weight: bold; }
.tag-accepted { color: var(--ink); }
.tag-no_interest { color: var(--ink-mid); opacity: 0.7; }
.tag-ducats { color: #7a5a10; font-weight: bold; }
.barter-foot { display: flex; gap: 16px; align-items: flex-start; margin-top: 10px;
  border-top: var(--divider-light); padding-top: 8px; }
.barter-foot .kunda-meter { margin-top: 0; }
.barter-summary { flex: 1; min-width: 0; }
.barter-ask { text-decoration: underline dotted; cursor: help; }

/* A family's name opens its card: who it is, its rivals, what it trades. */
.family-link { padding: 0; border: 0; background: none; font: inherit; color: inherit;
  text-decoration: underline dotted 2px; text-underline-offset: 3px; cursor: help; }
.family-link:focus-visible { outline: 2px solid var(--ink); outline-offset: 2px; }
.kunda-card { position: fixed; inset: 0; margin: auto; height: fit-content; max-height: 85vh;
  overflow: auto; width: fit-content; max-width: calc(100vw - 32px); color: var(--ink); }
.kunda-card::backdrop { background: rgba(20, 10, 0, 0.25); }
.kunda-card-head { display: flex; flex-wrap: wrap; gap: 4px 12px; align-items: baseline;
  margin-bottom: 8px; }
.kunda-card-head .kunda-family { margin: 0; font-size: 1.125rem; }
.kunda-card-head .kunda-standing { margin: 0; }
.kunda-card-body { display: flex; gap: 16px; align-items: flex-start; }
.kunda-card-body .kunda-crest { text-align: left; }
.kunda-card .kunda-list h4:first-child { margin-top: 0; }
.kunda-card .kunda-crest .kunda-list h4 { margin-top: 8px; }
.barter-verdict { display: flex; flex-wrap: wrap; gap: 6px 12px; align-items: center; }
.barter-verdict .btn { width: auto; }
.barter-refusals, .barter-effects ul { list-style: none; font-size: 0.875rem; }
.barter-effects h4 { margin: 8px 0 2px; font-size: 0.875rem; }
/* The landing page (webui/landing.py, WEB_ACCOUNTS.md §5.1). Sign-in sits in
   a strip at the top right; the picker and the about card stack on a phone,
   picker first, and stand side by side from 966px with the picker right. */
.landing-top { width: 100%; max-width: var(--wide-2); display: flex;
  justify-content: flex-end; min-height: 40px; }
.landing-corner-btn { width: auto; margin-bottom: 0; }
.landing-hero { text-align: center; margin: 8px 0 22px; }
.landing-title { font-size: 2.25rem; color: #fff; letter-spacing: 0.03em;
  text-shadow: 3px 3px 0 rgba(0,0,0,0.5); margin-bottom: 14px; }
.landing-links { display: flex; flex-wrap: wrap; justify-content: center; gap: 12px; }
.landing-links > .btn { width: auto; margin-bottom: 0; }
/* Discord's own button: its symbol beside the label, on Blurple. */
.btn-discord-brand { display: inline-flex; align-items: center; justify-content: center;
  gap: 10px; border-color: #3b4ac0; }
.discord-symbol { height: 18px; width: auto; flex-shrink: 0; image-rendering: auto; }
.landing-grid { display: grid; width: 100%; max-width: var(--card-max);
  grid-template-columns: minmax(0, 1fr); gap: var(--card-gap); align-items: start; }
.landing-grid > .parchment { max-width: none; margin-bottom: 0; }
.landing-grid p { color: var(--ink); font-size: 0.9375rem; line-height: 1.55;
  margin-bottom: 10px; }
.landing-about figure { margin: 14px 0 4px; }
.landing-about figure img { display: block; width: 100%; height: auto;
  border: 2px solid var(--parchment-dark); image-rendering: auto; }
.landing-about figcaption { font-size: 0.8125rem; color: var(--ink-mid);
  text-align: center; margin-top: 4px; }
.landing-beta { font-style: italic; color: var(--ink-mid); }
.faction-row { display: grid; grid-template-columns: 48px minmax(0, 1fr) auto;
  gap: 4px 12px; align-items: center; padding: 10px 0; border-top: var(--rule); }
.faction-row-flag .faction-flag { height: 32px; margin-right: 0; }
.faction-row .faction-name { font-size: 1.0625rem; color: var(--heading); margin-bottom: 2px; }
.faction-row p { font-size: 0.875rem; margin-bottom: 2px; }
.faction-row-join > .btn { width: auto; min-width: 8em; margin-bottom: 0; white-space: nowrap; }
/* Too narrow for three columns: the button drops under the text. */
@media (max-width: 520px) {
  .faction-row-flag { align-self: start; padding-top: 4px; }
  .faction-row-join { grid-column: 2 / -1; }
  .faction-row-join > .btn { width: 100%; }
}
@media (min-width: 966px) {
  .landing-grid { max-width: var(--wide-2); grid-template-columns: repeat(2, minmax(0, 1fr));
    grid-template-areas: "about play"; }
  .landing-play { grid-area: play; }
  .landing-about { grid-area: about; }
}
/* The signup page: the picker alone, centered. */
.landing-grid.landing-solo { max-width: var(--card-max); grid-template-columns: minmax(0, 1fr);
  grid-template-areas: none; margin-top: 40px; }
.landing-solo .landing-play { grid-area: auto; }
