/*
  The site's whole stylesheet but for the home page's own (css/home.css). Two layers, in this
  order: tokens and utilities that say nothing about what they are styling, then the handful
  of components that recur.

  WHY NOT TAILWIND OR BOOTSTRAP. Tailwind is a build step, and a build step here means
  node and npm in a repository that has neither and a Dockerfile that installs neither
  - for a site of thirteen pages. Bootstrap without a build is a CDN link, which is a
  third party in the request path of the page a player types a password into. What both
  are actually worth is the part copied below: one spacing scale, one colour set, and
  layout written in the markup instead of a new class name per arrangement. That part
  costs about two hundred lines. The fonts are self-hosted for the same reason.

  The scale is 4px and every gap, pad and margin utility is a step on it, so vertical
  rhythm holds without anyone maintaining it.

  The look is the game client's, measured in doc/ClientTheme.md and drawn by
  Client/src/theme.c, carried to the web rather than copied: its slate and well surfaces,
  the fret band's teals, the gold rim, the muted cream serif for what reads as interface
  and a plain sans for what reads as data, the navy button that turns cyan when pressed,
  and the 45-degree chamfer on every corner. doc/WebsiteTheme.md has the rules.
*/

/* --- fonts ----------------------------------------------------------------------- */

/* Marcellus is a Roman-capital serif with one weight, which keeps the client's rule that
   every serif is regular. Geist is variable, so one file is every weight the site uses.
   Both are OFL, latin only, and served from here (fonts/README.md). */
@font-face {
  font-family: "Marcellus";
  src: url("../fonts/marcellus-latin-400.woff2") format("woff2");
  font-weight: 400;
  font-style: normal;
  font-display: swap;
}
@font-face {
  font-family: "Geist";
  src: url("../fonts/geist-latin-variable.woff2") format("woff2");
  font-weight: 100 900;
  font-style: normal;
  font-display: swap;
}

/* --- tokens ---------------------------------------------------------------------- */

/*
  One theme, dark, and no light variant. The client has none: every window is a slate field
  in a dark frame, the lighting on the home page only reads against the dark, and a light
  site would be a different game's. The names are the old ones, so every rule that read a
  token still reads the right one.
*/
:root {
  color-scheme: dark;

  --space-1: .25rem;
  --space-2: .5rem;
  --space-3: .75rem;
  --space-4: 1rem;
  --space-5: 1.5rem;
  --space-6: 2rem;

  /* Radii are the size of the chamfer where the browser can cut one (corner-shape: bevel),
     and a small rounding where it cannot. The client cuts 3 pixels off a button and 7 off a
     window; these are those, at the web's sizes. */
  --radius: 5px;
  --radius-lg: 10px;

  /* Surfaces, darkest first: the ground behind everything, the well a value sits in, the
     card, and a card raised under the pointer. All from theme.c's GROUND, WELL and SLATE,
     pulled towards one another so a long page is not a patchwork. */
  --bg: #070c11;
  --well: #08131b;
  --surface: #0e1a23;
  --surface-2: #13232e;
  --border: #1c3140;
  --border-strong: #2c4757;

  --text: #dde2e3;
  --muted: #92a2a9;
  /* The client's cream, TH_CREAM, for headings and button labels. A shade brighter than the
     game's #C8BB94, which is drawn 15 pixels high on a navy face; at 30 on the page it is
     the same weight to the eye. */
  --cream: #d9cba2;

  /* The one accent: the gold line of the client's frames (#CEBE7B). Links, focus and the
     marks that say "this one". */
  --accent: #cebe7b;
  --accent-hover: #e2d4aa;
  --accent-text: #15110a;
  --focus: #e2d4aa;
  --bronze: #5d4a26;

  /* The client's pressed button, top to bottom, which the site's buttons turn when held. */
  --press-hi: #39d7ef;
  --press-lo: #08515a;

  --ok-bg: #12301f;
  --ok-text: #9fe8a5;
  --bad-bg: #3a1610;
  --bad-text: #ffab94;
  /* TH_WARN, the client's own error colour. */
  --danger: #ff7050;
  --danger-hover: #ff8a6e;

  --font: 16px/1.55 "Geist", system-ui, -apple-system, "Segoe UI", sans-serif;
  --serif: "Marcellus", "Times New Roman", Times, serif;
  --mono: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;

  --ease-out: cubic-bezier(.16, 1, .3, 1);

  /* The client's fret band: concentric diamonds on a 12-pixel lattice with a dot at each
     centre, in the dark teals of DialogQuery.dds. Drawn here rather than taken from the
     client's art, which is not ours to ship. */
  /* The gear window's slots (site.css, "The gear window"): the game's painted frame, four
     one-pixel lines around a field, measured off its gear tab. */
  --slot-field: #213042;
  --slot-rim: #adc3b5;
  --slot-mid: #394647;
  --slot-ink: #000810;

  --fret: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='12' height='12'%3E%3Crect width='12' height='12' fill='%230b1821'/%3E%3Cpath d='M6 .5 11.5 6 6 11.5.5 6z' fill='none' stroke='%231c3544'/%3E%3Cpath d='M6 3.5 8.5 6 6 8.5 3.5 6z' fill='none' stroke='%23142836'/%3E%3Ccircle cx='6' cy='6' r='.9' fill='%23315163'/%3E%3C/svg%3E");
}

/* --- reset ----------------------------------------------------------------------- */

* { box-sizing: border-box; }

/* The ground is the html's, and the body's glow does not repeat: a page shorter than the
   window otherwise drew a second glow below it with a hard edge. The scroll padding keeps a
   field reached by Tab from landing under the sticky nav. */
html { background: var(--bg); scrollbar-color: var(--border-strong) var(--well); scroll-padding-top: 5rem; }

body {
  font: var(--font);
  margin: 0;
  color: var(--text);
  /* A little of the slate field's light at the top, where the nav sits, and the ground
     below it. */
  background: radial-gradient(1100px 520px at 50% -260px, rgb(46 75 90 / .38), transparent 70%) no-repeat;
  -webkit-font-smoothing: antialiased;
  text-rendering: optimizeLegibility;
}

h1, h2, h3, p, figure { margin: 0; }
h1, h2, h3 { font-weight: 400; text-wrap: balance; }
p { text-wrap: pretty; }

a { color: var(--accent); text-underline-offset: .18em; text-decoration-thickness: 1px; }
a:hover { color: var(--accent-hover); }

::selection { background: rgb(206 190 123 / .35); color: #fff; }

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

/* --- layout utilities ------------------------------------------------------------ */

/*
  Stack is the workhorse: it owns the space between its children, so nothing needs a
  margin of its own and two stacked components never double their gaps up.
*/
.stack { display: flex; flex-direction: column; }
.row   { display: flex; flex-direction: row; align-items: center; flex-wrap: wrap; }

.gap-1 { gap: var(--space-1); }
.gap-2 { gap: var(--space-2); }
.gap-3 { gap: var(--space-3); }
.gap-4 { gap: var(--space-4); }
.gap-5 { gap: var(--space-5); }
.gap-6 { gap: var(--space-6); }

/* Pushes everything after it to the far end of a row. */
.spacer { margin-left: auto; }

.grow { flex: 1 1 0; }
.nowrap { white-space: nowrap; }

/*
  Columns that collapse to one on a narrow phone. The basis is the width below which a
  column is not worth keeping beside its neighbour.
*/
.split > * { flex: 1 1 11rem; }

.items-baseline { align-items: baseline; }

.mt-2 { margin-top: var(--space-2); }
.mt-4 { margin-top: var(--space-4); }
.mt-5 { margin-top: var(--space-5); }
.mt-6 { margin-top: var(--space-6); }

.container {
  width: 100%;
  max-width: 56rem;
  margin: 0 auto;
  padding: var(--space-6) var(--space-4);
}

/* The narrow column the signed-out pages sit in. */
.container-sm { max-width: 32rem; }

/*
  The same width, but centred inside a full-width container rather than being one.
  The forms that are the whole of their page sit in this - see _Centered.cshtml.
*/
.narrow { width: 100%; max-width: 32rem; margin: 0 auto; }

.text-center { text-align: center; }

/* --- typography ------------------------------------------------------------------ */

/* Serif for what a player reads as interface - headings, buttons - and the sans for what
   they read as data, the rule the client keeps between its baked lettering and its Arial. */
.title    { font-family: var(--serif); font-size: clamp(1.6rem, 1.2rem + 1.2vw, 2.1rem); line-height: 1.15; color: var(--cream); }
.subtitle { font-family: var(--serif); font-size: 1.3rem; line-height: 1.25; color: var(--cream); }
.muted    { color: var(--muted); }
.text-sm  { font-size: .9rem; }

.hint { font-size: .8rem; color: var(--muted); }
.sub  { font-size: .95rem; color: var(--muted); }

.error { font-size: .85rem; color: var(--danger); font-weight: 500; }

/* What a label adds about its field - optional, roughly - in the label's line but not its
   weight. */
.label-aside { font-weight: 400; color: var(--muted); }

/*
  A one-line pointer that sits above a form and sends some readers elsewhere before they
  start: the bug report's "slow or will not start? send a performance report instead". A
  well with the fret band's gold diamond, so it reads as an aside rather than as the form.
*/
.note {
  position: relative;
  padding: var(--space-3) var(--space-4) var(--space-3) calc(var(--space-4) + 1.4rem);
  font-size: .925rem;
  background: var(--well);
  border: 1px solid var(--border);
  border-radius: var(--radius);
  corner-shape: bevel;
}
.note::before {
  content: "";
  position: absolute;
  left: var(--space-4);
  top: 1.15rem;
  width: .55rem;
  height: .55rem;
  border: 1.5px solid var(--accent);
  transform: rotate(45deg);
}

/* A realm's "What do you intend on doing?", whole on its page: the owner's own line breaks
   kept, and a long unbroken word wrapped rather than widening the page on a phone. */
.realm-intent { white-space: pre-line; overflow-wrap: anywhere; max-width: 70ch; }
/* Its count on the forms is written by js/realm-intent.js; without the script it is empty
   and takes no room. Past a limit the box is edged as the count is coloured. */
.intent-count:empty { display: none; }
textarea[aria-invalid="true"], textarea[aria-invalid="true"]:hover { border-color: var(--danger); }

/* Numbered steps a player follows in order, spaced as the stack around them is. */
.steps { margin: 0; padding-left: 1.4em; display: flex; flex-direction: column; gap: var(--space-2); }
.steps li::marker { font-family: var(--serif); color: var(--accent); }

/* --- more detail ----------------------------------------------------------------- */

/*
  The rest of an explanation, folded under its one-line lead: pages say what a thing is in a
  sentence and keep the rules and edge cases a click away. A native <details> rather than an
  info icon opening a modal: it works with no script, opens by keyboard, reads in order to a
  screen reader, and traps nothing. Sparingly: only for what a player needs and would not
  guess - privacy, what is lost, when a change takes effect. Anything obvious from the page,
  or not worth knowing, is cut rather than folded.

    <p class="hint">One sentence.</p>
    <details class="more"><summary>More</summary><p>The rest.</p></details>
*/
details.more { font-size: .85rem; color: var(--muted); }
details.more > summary {
  display: inline-flex;
  align-items: center;
  gap: .4rem;
  font-weight: 500;
  color: var(--accent);
  cursor: pointer;
  list-style: none;
}
details.more > summary::-webkit-details-marker { display: none; }
details.more > summary::before {
  content: "i";
  display: inline-grid;
  place-items: center;
  width: 1.05rem;
  height: 1.05rem;
  font: italic 600 .7rem/1 var(--serif);
  color: var(--accent);
  border: 1px solid rgb(206 190 123 / .6);
  border-radius: 50%;
}
details.more > summary:hover { color: var(--accent-hover); }
details.more[open] > summary { margin-bottom: var(--space-2); }
details.more > :not(summary) { max-width: 68ch; margin: 0 0 var(--space-2); }
details.more ul { padding-left: 1.2em; }
/* In a centred empty-state card the lead is centred, so what opens under it is too. */
.empty details.more > :not(summary) { margin-inline: auto; }

/* --- visibility ------------------------------------------------------------------ */

/*
  Invisible but still occupying its space, so hiding it cannot make the rest of a long
  form jump upwards under the cursor that is about to click the next thing. Also drops
  the element out of the tab order, which opacity:0 does not.
*/
.invisible { visibility: hidden; }

/* The off hand while a two-handed weapon is picked (js/loadout.js). Removed rather than
   made invisible: it sits below the main hand that hid it, so nothing moves under the
   pointer, and .stack sets a display that would otherwise win over [hidden]. */
.gear-slot[hidden] { display: none; }

/* --- card ------------------------------------------------------------------------ */

/* A slate field with a lit top edge, the client's barely-there bevel. */
.card {
  background: linear-gradient(180deg, rgb(46 75 90 / .16), transparent 7rem), var(--surface);
  border: 1px solid var(--border);
  border-radius: var(--radius-lg);
  corner-shape: bevel;
  box-shadow: inset 0 1px 0 rgb(226 212 170 / .06);
  padding: var(--space-5);
}

/*
  A card in the client's dialog frame: a gold rim, the fret band, then the field - for the
  forms that are the whole of their page (_Centered.cshtml), which is what a dialog is in
  the game. Only there, as the client keeps its heavy frame for one window at a time.
*/
.frame {
  padding: 7px;
  background: var(--fret);
  border: 1px solid #a8955c;
  border-radius: 14px;
  corner-shape: bevel;
  box-shadow: 0 0 0 1px #2a2112, 0 0 0 2px #05080b, 0 30px 80px -30px rgb(0 0 0 / .8);
}
.frame > .card {
  border-color: #05101a;
  border-radius: 9px;
  box-shadow: inset 0 1px 0 rgb(226 212 170 / .08), 0 0 0 1px rgb(49 81 99 / .5);
}

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

label { display: block; font-weight: 600; font-size: .9rem; }

/* A value sits in a well, as in the client's chat input: sunk, a thin bronze line, lit
   along the bottom edge. */
input, select, textarea {
  width: 100%;
  padding: var(--space-2) var(--space-3);
  font: inherit;
  border: 1px solid #46606d;
  border-radius: var(--radius);
  corner-shape: bevel;
  background: var(--well);
  color: var(--text);
  /* The lit lower edge is a shadow rather than a border colour: a bevelled corner draws a
     per-side border colour across the wrong side, so the top of every box came out lit. */
  box-shadow: inset 0 2px 6px rgb(0 0 0 / .35), inset 0 -1px 0 rgb(120 150 164 / .45);
}

input::placeholder, textarea::placeholder { color: #6d7f88; }

input:hover, select:hover, textarea:hover { border-color: #7c6a44; }

input:focus, select:focus, textarea:focus {
  outline: 2px solid var(--focus);
  outline-offset: 1px;
}

/* The drop arrow is the client's, a pointed gold wedge, since the stock one is the only
   Windows part left on a page otherwise drawn in the game's colours. */
select {
  appearance: none;
  padding-right: 2.25rem;
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='10' height='6'%3E%3Cpath d='M0 0h10L5 6z' fill='%23cebe7b'/%3E%3C/svg%3E");
  background-repeat: no-repeat;
  background-position: right .85rem center;
}
select[multiple] { background-image: none; padding-right: var(--space-3); }
optgroup, option { background: var(--well); color: var(--text); }

input[type=checkbox], input[type=radio], input[type=range] { accent-color: var(--accent); box-shadow: none; }
input[type=range] { padding: 0; border: 0; background: none; }

input[type=file] { padding: var(--space-2); }
input[type=file]::file-selector-button {
  margin-right: var(--space-3);
  padding: var(--space-1) var(--space-3);
  font: inherit;
  color: var(--text);
  background: var(--surface-2);
  border: 1px solid var(--border-strong);
  border-radius: var(--radius);
  cursor: pointer;
}

/* Label, control, hint and error as one stacked unit - see _Field.cshtml. */
.field { display: flex; flex-direction: column; gap: var(--space-1); }

fieldset {
  margin: 0;
  padding: var(--space-2) var(--space-4) var(--space-4);
  border: 1px solid var(--border-strong);
  border-radius: var(--radius-lg);
  corner-shape: bevel;
}

legend { padding: 0 var(--space-2); font-family: var(--serif); font-size: 1.05rem; color: var(--cream); }

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

/*
  The client's main-bar button: a navy face, lit at the top and darkest just below the
  middle, with a pale band near the foot that reads as glass catching light; platinum over
  bronze round it, lit from the top left; a cream serif label. Held, it turns the client's
  cyan with dark lettering, as the game's own do - the one place the cyan appears.
*/
.btn {
  display: inline-block;
  padding: var(--space-2) var(--space-5);
  font: inherit;
  font-family: var(--serif);
  font-size: 1rem;
  letter-spacing: .02em;
  line-height: 1.4;
  text-align: center;
  text-decoration: none;
  white-space: nowrap;
  color: var(--cream);
  border: 1px solid #9c8f68;
  border-radius: var(--radius);
  corner-shape: bevel;
  background: linear-gradient(180deg, #1a5378 0%, #0b3150 38%, #04213a 58%, #0a3654 76%, #3a6677 88%, #0f4262 100%);
  /* The client's platinum-over-bronze bevel, lit from the top left, as inset shadows over
     one border colour: a bevelled corner paints per-side colours across the wrong sides. */
  box-shadow: 0 0 0 1px #04080b, inset 1px 1px 0 rgb(214 215 198 / .55), inset -1px -1px 0 rgb(148 121 74 / .6);
  text-shadow: 0 1px 0 rgb(0 0 0 / .8);
  cursor: pointer;
  transition: filter .15s, transform .1s;
}

.btn:hover { color: #efe4c4; filter: brightness(1.2) saturate(1.1); }

.btn:active {
  color: #04141c;
  text-shadow: none;
  background: linear-gradient(180deg, var(--press-hi), #21a6bc 45%, var(--press-lo));
  filter: none;
  transform: translateY(1px);
}

.btn:disabled, .btn[aria-disabled=true] { filter: grayscale(1) brightness(.7); cursor: not-allowed; }

/* Full width and a little taller: the one action a form exists to perform. */
.btn-block { display: block; width: 100%; padding: var(--space-3); }

/* A way out or a second choice: no face, a slate rim. */
.btn-quiet {
  color: var(--text);
  background: transparent;
  border-color: var(--border-strong);
  box-shadow: none;
  text-shadow: none;
}
.btn-quiet:hover { color: var(--cream); background: var(--surface-2); border-color: var(--bronze); filter: none; }

.btn-danger {
  color: #ffe4dc;
  background: linear-gradient(180deg, #8a2e1c 0%, #5a1a0e 55%, #7a2a18 88%, #5a1a0e 100%);
  border-color: #a8604a;
}
.btn-danger:hover { filter: brightness(1.15); color: #fff; }

/* --- banner ---------------------------------------------------------------------- */

.banner {
  padding: var(--space-3) var(--space-4);
  border: 1px solid;
  border-radius: var(--radius);
  corner-shape: bevel;
  font-size: .95rem;
}
.banner-ok  { background: var(--ok-bg);  color: var(--ok-text);  border-color: #25583a; }
.banner-bad { background: var(--bad-bg); color: var(--bad-text); border-color: #6e2c1e; }

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

/*
  Kept at the top as the page scrolls, over a blur of what is under it, with the fret band
  as its lower edge: the one strip of the client's frame every page carries.
*/
.nav {
  position: sticky;
  top: 0;
  z-index: 20;
  display: flex;
  align-items: center;
  gap: var(--space-2) var(--space-5);
  flex-wrap: wrap;
  min-height: 3.75rem;
  padding: var(--space-2) max(var(--space-4), calc((100% - 72rem) / 2 + 2rem));
  background: rgb(7 12 17 / .78);
  backdrop-filter: blur(12px) saturate(1.2);
  border-bottom: 1px solid var(--border);
}
.nav::after {
  content: "";
  position: absolute;
  inset: auto 0 -5px;
  height: 4px;
  background: var(--fret) 0 -4px / 12px 12px;
  opacity: .6;
  pointer-events: none;
}

.nav a { color: var(--text); text-decoration: none; font-size: .95rem; }
.nav a:hover { color: var(--accent-hover); }
.nav .brand {
  display: inline-flex;
  align-items: center;
  gap: .6rem;
  font-family: var(--serif);
  font-size: 1.2rem;
  letter-spacing: .02em;
  color: var(--cream);
}
/* The mark: one of the fret band's diamonds, in gold. */
.nav .brand::before {
  content: "";
  width: .7rem;
  height: .7rem;
  border: 1.5px solid var(--accent);
  box-shadow: inset 0 0 0 2px var(--bg), inset 0 0 0 4px rgb(206 190 123 / .7);
  transform: rotate(45deg);
}
.nav form { margin: 0; }
.nav .btn { padding: var(--space-1) var(--space-4); font-size: .95rem; }
.nav a.btn { color: var(--cream); }
.nav a.btn-quiet { color: var(--text); }

/*
  The bar's menus - Play, Learn, Support - and the links that stand beside them. Each menu is
  a <details> (js/nav.js makes them a set), its summary drawn as a borderless button in the
  interface serif with the client's gold wedge, its panel a raised card of links that each say
  in a line what is behind them. The page being read is marked in gold, and so is the menu
  holding it, with an underline rather than a box so the mark reads on a wrapped phone bar.
*/
.nav { column-gap: var(--space-2); }
.nav .brand { margin-right: var(--space-3); }
.nav .menu { position: relative; }
.nav-account { display: flex; align-items: center; gap: var(--space-2); margin-left: auto; }
.nav summary,
.nav > a:not(.brand, .btn),
.nav-account > a:not(.btn) {
  display: inline-flex;
  align-items: center;
  gap: .45rem;
  min-height: 2.4rem;
  padding: 0 var(--space-3);
  font-family: var(--serif);
  font-size: 1rem;
  letter-spacing: .02em;
  color: var(--text);
  border: 1px solid transparent;
  border-radius: var(--radius);
  corner-shape: bevel;
  cursor: pointer;
  list-style: none;
  user-select: none;
}
.nav summary::-webkit-details-marker { display: none; }
.nav summary::after {
  content: "";
  width: .38rem;
  height: .38rem;
  margin-top: -.2rem;
  border-right: 1.5px solid var(--accent);
  border-bottom: 1.5px solid var(--accent);
  transform: rotate(45deg);
  transition: transform .15s var(--ease-out);
}
.nav summary:hover, .nav > a:not(.brand, .btn):hover, .nav-account > a:not(.btn):hover { color: var(--accent-hover); }
.nav summary:focus-visible { outline: 2px solid var(--focus); outline-offset: 1px; }
.nav .menu[open] > summary { color: var(--accent-hover); background: var(--surface); border-color: var(--border-strong); }
.nav .menu[open] > summary::after { margin-top: .2rem; transform: rotate(-135deg); }

.nav summary.here,
.nav > a[aria-current=page] {
  color: var(--accent-hover);
  text-decoration: underline 2px var(--accent);
  text-underline-offset: .5em;
}

.menu-panel {
  position: absolute;
  top: calc(100% + var(--space-2));
  left: 0;
  z-index: 30;
  display: flex;
  flex-direction: column;
  gap: 2px;
  width: max-content;
  min-width: 17rem;
  max-width: min(22rem, calc(100vw - 2 * var(--space-4)));
  padding: var(--space-2);
  background: var(--surface);
  border: 1px solid var(--border-strong);
  border-radius: var(--radius-lg);
  corner-shape: bevel;
  box-shadow: 0 18px 40px rgb(0 0 0 / .6);
}
.nav .menu-panel a {
  display: flex;
  flex-direction: column;
  gap: 1px;
  padding: var(--space-2) var(--space-3);
  border-radius: var(--radius);
  corner-shape: bevel;
}
.nav .menu-panel a:hover, .nav .menu-panel a:focus-visible { background: var(--surface-2); }
.nav .menu-panel a:focus-visible { outline: 2px solid var(--focus); outline-offset: -2px; }
.menu-panel b { font-family: var(--serif); font-weight: 400; font-size: 1rem; color: var(--cream); }
.menu-panel span { font-size: .85rem; color: var(--muted); }
.nav .menu-panel a[aria-current=page] b { color: var(--accent-hover); }
.nav .menu-panel a[aria-current=page] { box-shadow: inset 2px 0 0 var(--accent); }
.menu-panel hr { width: auto; margin: var(--space-1) var(--space-2); border: 0; border-top: 1px solid var(--border); }

/* The last menu's panel opens leftwards from its button's right edge: opened rightwards it
   ran past the window between a phone's width and about 700px, clipped and scrolling the
   page sideways. */
.nav .menu:last-of-type .menu-panel { left: auto; right: 0; }

/* Narrower than the signed-in bar's one row (835px for staff with a 15-letter name), the
   bar is two rows: the brand and the account (Log out, or Log in and Register), a
   zero-height break, then the menus - rather than the account wrapping under the menus, or
   three or four ragged rows on a phone. Two rows are not kept at the top as the page
   scrolls: they are too much of a small window, and the scroll padding that keeps a jump
   target clear is for one. */
@media (max-width: 54rem) {
  .nav { position: relative; }
  html { scroll-padding-top: 0; }
  .nav::before { content: ""; order: 2; flex-basis: 100%; height: 0; }
  .nav-account { order: 1; }
  .nav > .menu, .nav > a:not(.brand) { order: 3; }
}

/* It fits a 360px phone only drawn tighter, and without the character's name, which Play's
   "Your character" leads to anyway: a long name pushed Log out onto a row of its own. */
@media (max-width: 30rem) {
  .nav { column-gap: var(--space-1); }
  .nav .brand { margin-right: 0; font-size: 1.05rem; }
  .nav-account > a:not(.btn) { display: none; }
  .nav .btn { padding: var(--space-1) var(--space-3); }
  .nav summary, .nav > a:not(.brand, .btn) { padding: 0 var(--space-2); }
}

/* On a phone a menu opens as a full-width sheet under the whole bar rather than under its
   button. */
@media (max-width: 40rem) {
  .nav .menu { position: static; }
  .nav .menu-panel,
  .nav .menu:last-of-type .menu-panel { left: var(--space-4); right: var(--space-4); width: auto; max-width: none; top: calc(100% + var(--space-1)); }
}

/* --- tables ---------------------------------------------------------------------- */

/*
  A table wider than the phone it is on scrolls inside its own box rather than pushing
  the whole page sideways.
*/
.scroll { overflow-x: auto; }

table {
  width: 100%;
  border-collapse: collapse;
  font-size: .925rem;
  font-variant-numeric: tabular-nums;
}

th, td {
  padding: var(--space-3);
  text-align: left;
  border-bottom: 1px solid var(--border);
  vertical-align: top;
}

th { font-weight: 600; color: var(--muted); font-size: .75rem; letter-spacing: .06em; text-transform: uppercase; white-space: nowrap; }
th a { color: inherit; }
thead th { border-bottom: 1px solid var(--border-strong); }
tbody tr:last-child td { border-bottom: 0; }
tbody tr:hover td { background: rgb(46 75 90 / .12); }
td.name { font-weight: 600; color: var(--cream); }
td.name > a { color: inherit; text-decoration: none; }
td.name > a:hover { color: var(--accent-hover); text-decoration: underline; }

.empty { padding: var(--space-6); text-align: center; color: var(--muted); }

/* --- tags ------------------------------------------------------------------------ */

.tag {
  display: inline-block;
  padding: 0 var(--space-2);
  font-size: .75rem;
  font-weight: 600;
  line-height: 1.7;
  border: 1px solid var(--border-strong);
  border-radius: 3px;
  corner-shape: bevel;
  background: var(--surface-2);
  color: var(--muted);
}
.tag-live    { background: var(--ok-bg);  color: var(--ok-text);  border-color: #25583a; }
.tag-closed  { background: var(--bad-bg); color: var(--bad-text); border-color: #6e2c1e; }
.tag-private { background: rgb(206 190 123 / .14); color: var(--accent); border-color: rgb(206 190 123 / .45); }
.tag-slots   { background: var(--surface-2); color: var(--cream); border-color: var(--border-strong); }
.search .input-count { flex: 0 0 6rem; }
.search label { align-self: center; }

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

/*
  A post on /updates and its own page: the title in the serif, the date under it, then the
  post's Markdown as UpdateMarkdown renders it. The body's own headings are the release
  notes' Added, Fixed and the rest, so they sit a step below the post's title.
*/
.post { display: flex; flex-direction: column; gap: var(--space-3); }
.post-title { font-family: var(--serif); font-size: 1.4rem; line-height: 1.25; color: var(--cream); }
.post-title > a { color: inherit; text-decoration: none; }
.post-title > a:hover { color: var(--accent-hover); text-decoration: underline; }

.prose { display: flex; flex-direction: column; gap: var(--space-3); overflow-wrap: anywhere; }
.prose > *, .prose li > p { margin: 0; }
.prose h1, .prose h2, .prose h3, .prose h4 { font-family: var(--serif); color: var(--cream); margin-top: var(--space-2); }
.prose h1, .prose h2 { font-size: 1.2rem; }
.prose h3, .prose h4 { font-size: 1.05rem; }
.prose ul, .prose ol { padding-left: var(--space-5); display: flex; flex-direction: column; gap: var(--space-1); }
.prose code { font: .9em var(--mono); padding: 0 .3em; background: var(--well); border: 1px solid var(--border); border-radius: 3px; }
.prose pre { padding: var(--space-3); overflow-x: auto; background: var(--well); border: 1px solid var(--border); border-radius: var(--radius); }
.prose pre code { padding: 0; border: 0; background: none; }
.prose blockquote { margin: 0; padding-left: var(--space-3); border-left: 2px solid var(--border-strong); color: var(--muted); }
.prose hr { width: 100%; border: 0; border-top: 1px solid var(--border); }
/* The wiki's tables: a table wider than the phone scrolls in its own box, as .scroll does.
   Its cells break only between words: the prose's overflow-wrap: anywhere let a column
   shrink below its longest word, which split "Characters" into "Charac" and "ters". */
.prose table { display: block; overflow-x: auto; }
.prose th, .prose td { overflow-wrap: normal; }
/* A command in a cell stays on one line, so its column is as wide as the longest:
   "/removebot {name}" broke after the command, splitting it from its argument. */
.prose td code { white-space: nowrap; }
/* No row lit under the pointer, as the site's list tables do: a wiki table is
   reference text, and a lit row reads as one to click. */
.prose tbody tr:hover td { background: none; }

/*
  The wiki (doc/Wiki.md): its front page is a set of tiles, and each page has the others
  listed down its left side, which moves above it on a phone.
*/
.wiki-tiles { margin: 0; padding: 0; list-style: none; }
.wiki-tiles > li { display: flex; }
.wiki-tiles .tile { flex: 1; }

.wiki-layout { display: grid; grid-template-columns: 12rem minmax(0, 1fr); gap: var(--space-6); align-items: start; }
.wiki-pages { display: flex; flex-direction: column; gap: var(--space-2); font-size: .925rem; }
.wiki-pages ul { margin: 0; padding: 0; list-style: none; display: flex; flex-direction: column; gap: 2px; }
.wiki-pages a { display: block; padding: var(--space-1) var(--space-3); color: var(--text); text-decoration: none; border-radius: var(--radius); corner-shape: bevel; }
.wiki-pages a:hover { color: var(--accent-hover); background: var(--surface); }
.wiki-pages a[aria-current=page] { color: var(--accent-hover); background: var(--surface); box-shadow: inset 2px 0 0 var(--accent); }
.wiki-pages .wiki-home { font-family: var(--serif); font-size: 1.05rem; color: var(--cream); }
.wiki-article { max-width: 68ch; }

@media (max-width: 48rem) {
  .wiki-layout { grid-template-columns: minmax(0, 1fr); gap: var(--space-5); }
  .wiki-pages ul { flex-direction: row; flex-wrap: wrap; }
}

/*
  The wiki's embed view (?embed=1, _Embed.cshtml), in the game's help window: the site's look
  with no bar, the page list in a row across the top at any width, and the game's scroll bar,
  a gold thumb on the fret band's dark teal with no arrows (ClientTheme.md). Chromium draws
  ::-webkit-scrollbar only where scrollbar-color and scrollbar-width are both auto, and
  scrollbar-color is inherited, so the site's own on html is put back to auto here rather than
  overridden element by element. Nothing the wiki draws sets scrollbar-width.
*/
html.embed { scrollbar-color: auto; scroll-padding-top: 0; }
html.embed ::-webkit-scrollbar { width: 14px; height: 14px; }
html.embed ::-webkit-scrollbar-track,
html.embed ::-webkit-scrollbar-corner { background: #142836; }
html.embed ::-webkit-scrollbar-thumb { background: var(--accent); border: 3px solid #142836; }
html.embed ::-webkit-scrollbar-thumb:hover { background: var(--accent-hover); }
html.embed ::-webkit-scrollbar-button { display: none; }
.embed .container { padding-top: var(--space-4); }
.embed .wiki-layout { grid-template-columns: minmax(0, 1fr); gap: var(--space-5); }
.embed .wiki-pages { padding-bottom: var(--space-3); border-bottom: 1px solid var(--border); }
.embed .wiki-pages ul { flex-direction: row; flex-wrap: wrap; }

/* The Discord channels a visitor added on /updates, one row each with its Remove button. */
.channel-list { list-style: none; margin: 0; padding: 0; }
.channel-list > li { flex-wrap: wrap; padding: var(--space-2) 0; border-top: 1px solid var(--border); }
/* The channel, its tag and Remove stay on one line on a phone, so the reason a channel
   stopped is never split from it by the button. */
.channel-list > li > .row { flex-wrap: nowrap; }
.channel-list > li > .row > a { overflow-wrap: anywhere; }

/* --- page header ----------------------------------------------------------------- */

/* A page title with its actions on the same line, wrapping under it on a phone. */
.head {
  display: flex;
  align-items: center;
  gap: var(--space-3);
  flex-wrap: wrap;
}

/* --- tiles ----------------------------------------------------------------------- */

/*
  The admin hub's cards: a link or a small form each, as many to a row as fit and one to a
  row on a phone. A tile that is a link keeps the text colour rather than turning gold, and
  shows it is one by its border on hover.
*/
.tiles { display: grid; grid-template-columns: repeat(auto-fill, minmax(16rem, 1fr)); gap: var(--space-4); }
.tile { display: flex; flex-direction: column; gap: var(--space-2); color: var(--text); text-decoration: none; }
a.tile { transition: border-color .2s, transform .2s var(--ease-out); }
a.tile:hover { color: var(--text); border-color: rgb(206 190 123 / .55); transform: translateY(-2px); }
.tile-name { font-weight: 600; color: var(--cream); }
.tile-count { color: var(--muted); }
.tile-due { color: var(--bad-text); font-weight: 600; }
/* A tile's field and its button share a line when the tile is wide enough and stack when it
   is not, and sit at the tile's foot so a row of tiles lines its forms up. */
.tile-form { display: flex; flex-wrap: wrap; gap: var(--space-2); margin-top: auto; }
.tile-form > .grow { min-width: 11rem; }

/* --- loadouts ------------------------------------------------------------------- */

/*
  The character page's builds, a card each, as many to a row as fit and one to a row on a
  phone: name, rebirth path, and Select and Edit on one line, where a table row of four
  buttons wrapped onto two. The build worn at the next login is edged in gold.
*/
.loadout-list {
  list-style: none;
  margin: 0;
  padding: 0;
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(17rem, 1fr));
  gap: var(--space-4);
}
.loadout-card { display: flex; flex-direction: column; gap: var(--space-3); padding: var(--space-4) var(--space-5); }
.loadout-card.is-selected { border-color: rgb(206 190 123 / .55); }
/* The build's face, then its name and tag, which wrap between themselves and not under it. */
.loadout-card-head { display: flex; align-items: center; gap: var(--space-3); }
.loadout-card-title { display: flex; align-items: center; flex-wrap: wrap; gap: var(--space-2); min-width: 0; }
.loadout-name { font-family: var(--serif); font-size: 1.25rem; color: var(--cream); text-decoration: none; }
.loadout-name:hover { color: var(--accent-hover); text-decoration: underline; }
.loadout-path { font-size: .95rem; color: var(--text); }
.loadout-card-foot { display: flex; flex-wrap: wrap; gap: var(--space-2); margin-top: auto; }
.loadout-card-foot form { margin: 0; }

/* --- search and pager ------------------------------------------------------------ */

/* A text box and its button, side by side, the box taking whatever is left. */
.search { display: flex; gap: var(--space-2); }
.search input { flex: 1 1 auto; }
.search button { flex: 0 0 auto; width: auto; }
.search select { flex: 0 0 auto; width: auto; }

.pager {
  display: flex;
  align-items: center;
  justify-content: center;
  gap: var(--space-4);
  font-size: .9rem;
  color: var(--muted);
}

/* --- inline code ----------------------------------------------------------------- */

/*
  Something a player has to type exactly - a join code, or a command such as /rules.
  Monospace so that a character they would mistype is told apart from its neighbours.
*/
.slug {
  font-family: var(--mono);
  font-size: .9em;
  padding: .1em .4em;
  border-radius: 3px;
  background: var(--well);
  border: 1px solid var(--border);
  color: var(--accent-hover);
  /* A command is typed as one thing; split over two lines it reads as two. */
  white-space: nowrap;
}

.slug-lg { font-size: 1.5rem; letter-spacing: .1em; }

/* --- checkboxes ------------------------------------------------------------------ */

/* A checkbox reads as one line with its wording, not as a labelled form field. */
.check { display: flex; align-items: flex-start; gap: var(--space-2); font-weight: 400; }
.check input { width: auto; margin: .3rem 0 0; flex: 0 0 auto; }

/* --- portraits ------------------------------------------------------------------- */

/*
  The picker. auto-fill rather than a fixed column count, so the same grid is four
  across on a phone and fourteen on a desktop without a breakpoint deciding where the
  line is. The tiles are the client art's own 64px and are not stretched - a face drawn
  for that size and scaled up is the one thing a portrait grid cannot get away with.
*/
.portraits {
  display: grid;
  grid-template-columns: repeat(auto-fill, 64px);
  gap: var(--space-2);
  justify-content: center;
}

/*
  A scrolling box with a stop, so the grid does not run to a page of its own. The height
  is in rows and not in pixels: 5 tiles plus their gaps, which is enough to show that
  there is more below without the choosing itself needing a scroll on a laptop.
*/
.scroll-y {
  overflow-y: auto;
  max-height: calc((64px * 5) + (var(--space-2) * 4) + (var(--space-4) * 2));
}

.portrait {
  padding: 0;
  width: 64px;
  height: 64px;
  border: 2px solid transparent;
  border-radius: var(--radius);
  background: none;
  cursor: pointer;
  overflow: hidden;
  line-height: 0;
}
.portrait img { display: block; border-radius: calc(var(--radius) - 2px); }
.portrait:hover { border-color: var(--border-strong); }

/* The one being worn, marked in the accent so it is findable in a grid of 90. */
.portrait.is-chosen { border-color: var(--accent); }

/* The one beside the name. A link, so the whole tile is the way to the picker. */
.portrait-link { display: block; line-height: 0; flex: 0 0 auto; }
.portrait-img {
  display: block;
  width: 64px;
  height: 64px;
  border-radius: var(--radius);
  border: 1px solid #a8955c;
  box-shadow: 0 0 0 1px #05080b;
}
.portrait-empty { background: var(--surface-2); }
/* A build's face on its card in the list, where the one beside the page's name is full size. */
.portrait-small { width: 40px; height: 40px; }

/* A loadout page's head (Views/Loadout.cshtml): its portrait, which opens the faces its body
   may choose in a popover anchored under it, and its name, a field drawn as the title until
   it is pointed at or clicked (js/loadout-head.js). */
.loadout-head { align-items: flex-start; }
.portrait-button {
  flex: 0 0 auto;
  position: relative;
  padding: 0;
  border: 0;
  background: none;
  cursor: pointer;
  line-height: 0;
  anchor-name: --portrait;
  border-radius: var(--radius);
}
.portrait-button::after {
  content: "";
  position: absolute;
  inset: 0;
  border-radius: var(--radius);
  box-shadow: inset 0 0 0 2px transparent;
  transition: box-shadow .15s;
}
.portrait-button:hover::after, .portrait-button:focus-visible::after { box-shadow: inset 0 0 0 2px var(--accent); }
.portrait-button:focus-visible { outline: 2px solid var(--focus); outline-offset: 3px; }

.portrait-popover {
  width: min(30rem, calc(100vw - 2rem));
  padding: var(--space-4);
  color: var(--text);
  border: 1px solid #a8955c;
}
.portrait-popover::backdrop { background: rgb(3 6 9 / .35); }
.portrait-popover .hint { margin: 0 0 var(--space-3); }
.portrait-popover .portraits { margin: 0; }
/* Under the portrait where the browser can anchor it; centred as a dialog where it cannot. */
@supports (anchor-name: --portrait) {
  .portrait-popover {
    position-anchor: --portrait;
    inset: auto;
    top: anchor(bottom);
    left: anchor(left);
    margin: var(--space-2) 0 0;
    position-try-fallbacks: flip-block, flip-inline;
  }
}

.loadout-name { min-width: 0; }
.loadout-name .title { margin: 0; }
.loadout-name .hint, .loadout-name .error { margin: var(--space-1) 0 0; }
.loadout-name .hint:empty { display: none; }
.title-field {
  width: 100%;
  max-width: 24ch;
  padding: .05em .3em;
  margin-left: -.3em;
  font: inherit;
  color: inherit;
  background: transparent;
  border: 1px solid transparent;
  border-radius: var(--radius);
  box-shadow: none;
  cursor: text;
  transition: border-color .15s, background-color .15s;
}
.title-field:hover { border-color: var(--border-strong); }
.title-field:focus { background: var(--well); border-color: var(--accent); outline: none; }

/* Leaving with changes unsaved (js/unsaved.js). */
.unsaved-lead { margin: 0 0 var(--space-2); font-family: var(--serif); font-size: 1.15rem; color: var(--cream); }

/* --- a performance report (Views/DiagnosticsReport.cshtml) ------------------------- */

/* Tables of a few columns set side by side: the report is many short tables, and one per
   row would be a page of mostly margin. */
.items-start { align-items: flex-start; }
table.compact { width: auto; font-size: .85rem; }
table.compact td, table.compact th { padding: var(--space-1) var(--space-2); }
.kv-grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(9rem, 1fr)); gap: var(--space-3); }

.findings { margin: 0; padding-left: var(--space-5); }
.finding { margin: var(--space-1) 0; }
.finding-warn { color: var(--bad-text); font-weight: 600; }

/* The charts take the page's own colours. */
.chart { width: 100%; max-width: 612px; height: auto; display: block; }
.chart .axis { stroke: var(--border-strong); stroke-width: 1; }
.chart .bar { fill: var(--accent); }
.chart .bar-slow { fill: var(--danger); }
.chart .bar-tail { fill: var(--bad-text); }
.chart .tick { fill: var(--muted); font-size: 10px; }
.chart .line { fill: none; stroke: var(--accent); stroke-width: 1.5; }
.chart .stall { fill: var(--danger); }

tr.flag td { background: var(--bad-bg); color: var(--bad-text); }
tr.notable td { background: var(--surface-2); }
tr.highlight td { background: var(--ok-bg); color: var(--ok-text); font-weight: 600; }
code.hash { font-size: .7rem; color: var(--muted); word-break: break-all; }
pre.log { font-size: .75rem; overflow-x: auto; max-height: 30rem; white-space: pre; }
details.card > summary { cursor: pointer; font-weight: 600; }
details.card[open] > summary { margin-bottom: var(--space-3); }
/* An ini value can be one very long line - realms-graphics.ini's Light= list is seventy
   folder names - and without this it widens the whole page. */
table.ini { max-width: 100%; }
table.ini td { overflow-wrap: anywhere; }

/* --- realm rules (Views/Shared/_AdvancedRules.cshtml, js/rules.js) ---------------- */

/* Searches and tabs hide rows, maps and panels with the hidden attribute, and .check and
   .setting set a display that would otherwise win over the browser's own [hidden]. */
.rules [hidden], .rules-save [hidden] { display: none; }
.rules .sr-only {
  position: absolute; width: 1px; height: 1px; overflow: hidden; clip-path: inset(50%); white-space: nowrap;
}

.rules { display: flex; flex-direction: column; gap: var(--space-4); }
.rules-head { display: flex; flex-wrap: wrap; align-items: center; gap: var(--space-2) var(--space-3); }
.rules-head p.hint { flex: 1 1 20rem; }
.rules-head a.hint { flex: 0 0 auto; color: var(--accent); }

/* Without script the tabs are a row of links to the panels stacked under them; rules.js
   marks the root .is-tabbed and shows one panel at a time. The row scrolls sideways on a
   phone rather than wrapping, so the panel under it does not jump as the count badges come
   and go. */
.rules-tabs {
  display: flex;
  gap: var(--space-1);
  overflow-x: auto;
  scrollbar-width: none;
  border-bottom: 1px solid var(--border-strong);
}

/* A row of tabs wider than its place (js/rules.js, strip): it scrolls with no bar, which
   would sit between the tabs and their panel, so an arrow at each end it can scroll toward
   says there is more, and the row fades out under the arrow rather than being cut off
   mid-word. */
.tab-strip { position: relative; display: flex; align-items: flex-end; min-width: 0; }
.tab-strip > :not(.tab-scroll) { flex: 1 1 auto; min-width: 0; }
.tab-strip.can-on > :not(.tab-scroll) { mask-image: linear-gradient(90deg, #000 calc(100% - 3.5rem), transparent calc(100% - 1.25rem)); }
.tab-strip.can-back > :not(.tab-scroll) { mask-image: linear-gradient(270deg, #000 calc(100% - 3.5rem), transparent calc(100% - 1.25rem)); }
.tab-strip.can-back.can-on > :not(.tab-scroll) {
  mask-image: linear-gradient(90deg, transparent 1.25rem, #000 3.5rem, #000 calc(100% - 3.5rem), transparent calc(100% - 1.25rem));
}
.tab-scroll {
  position: absolute;
  z-index: 1;
  top: 0;
  bottom: 1px;
  width: 1.75rem;
  padding: 0;
  font: 1.35rem/1 var(--serif);
  color: var(--accent);
  background: none;
  border: 0;
  cursor: pointer;
}
.tab-scroll:first-child { left: 0; text-align: left; }
.tab-scroll:last-child { right: 0; text-align: right; }
.tab-scroll:hover { color: var(--accent-hover); }
.tab-scroll:disabled { display: none; }

.rules-tab {
  display: inline-flex;
  align-items: center;
  gap: var(--space-2);
  padding: var(--space-2) var(--space-3);
  font-family: var(--serif);
  white-space: nowrap;
  color: var(--muted);
  text-decoration: none;
  border-bottom: 2px solid transparent;
  margin-bottom: -1px;
}
.rules-tab:hover { color: var(--cream); }
/* The gold edge of a changed row, on the tab that holds one. */
.rules-tab.is-changed::before { content: ""; width: 2px; height: 1.1em; background: var(--accent); }
.rules-tab[aria-selected=true] { color: var(--cream); border-bottom-color: var(--accent); }
.rules-tab-count {
  min-width: 1.35rem;
  padding: 0 var(--space-1);
  font: 600 .7rem/1.35rem "Geist", system-ui, sans-serif;
  text-align: center;
  color: var(--accent-text);
  background: var(--accent);
  border-radius: 3px;
  corner-shape: bevel;
}

.rules-panel { display: flex; flex-direction: column; gap: var(--space-4); }
.rules-panel-title { font-family: var(--serif); font-size: 1.3rem; color: var(--cream); }
/* The selected tab names the panel, so its own heading is kept for a screen reader only. */
.rules.is-tabbed .rules-panel-title {
  position: absolute; width: 1px; height: 1px; overflow: hidden; clip-path: inset(50%); white-space: nowrap;
}
.rules.is-tabbed .rules-panel:not(.is-active) { display: none; }

[data-requires][data-locked="true"] { display: none; }
.rules-group { display: flex; flex-direction: column; gap: var(--space-2); }
.rules-group + .rules-group { padding-top: var(--space-4); border-top: 1px solid var(--border); }
.rules-group-title { font-family: var(--serif); font-size: 1.05rem; color: var(--cream); }
.rules-group-title .setting-note { display: inline; margin-left: var(--space-2); font-family: var(--font); }
.rules-toolbar { display: flex; flex-wrap: wrap; align-items: center; gap: var(--space-2) var(--space-3); }
.rules-toolbar input[type=search] { flex: 1 1 14rem; width: auto; }
.rules-toolbar select { width: auto; }

/* One number: its label, the box with its unit, and the game's own value with a reset once
   it is changed. A changed row is marked down its left edge as well as on the box, so a
   long panel can be scanned for what differs. */
.setting {
  display: grid;
  grid-template-columns: minmax(0, 1fr) 15rem 8rem;
  align-items: center;
  gap: var(--space-1) var(--space-4);
  padding: var(--space-1) var(--space-2);
  margin: 0 calc(-1 * var(--space-2));
  border-left: 2px solid transparent;
}
.setting.is-changed { border-left-color: var(--accent); background: rgb(206 190 123 / .05); }
.setting-label { font-weight: 500; font-size: .9rem; }
.setting-note { display: block; font-weight: 400; font-size: .75rem; color: var(--muted); }
/* The prefix always takes its slot, empty or "x", so every box on a panel starts at the
   same place whatever its unit. */
.setting-control { display: inline-flex; align-items: center; gap: var(--space-1); font-size: .85rem; color: var(--muted); }
.setting-prefix { width: 1ch; text-align: right; }
.setting-input { width: 6.5rem; padding: var(--space-1) var(--space-2); text-align: right; font-variant-numeric: tabular-nums; }
.setting.is-changed .setting-input { border-color: var(--accent); }
.setting-input:user-invalid, .setting-input.is-invalid, .setting.is-changed .setting-input.is-invalid { border-color: var(--danger); }
.setting-normal { display: flex; align-items: center; justify-content: space-between; gap: var(--space-2); font-size: .8rem; color: var(--muted); }
.setting-reset {
  min-height: 1.5rem;
  padding: var(--space-1) var(--space-2);
  margin: calc(-1 * var(--space-1)) calc(-1 * var(--space-2)) calc(-1 * var(--space-1)) 0;
  font: inherit;
  color: var(--accent);
  background: none;
  border: 0;
  cursor: pointer;
  text-decoration: underline;
  text-underline-offset: .18em;
}
.setting-reset:hover { color: var(--accent-hover); }
.setting-error { grid-column: 1 / -1; font-size: .8rem; color: var(--danger); }

.setting-switch { padding: var(--space-1) 0; }

/* A tick that differs from a new realm's carries the same edge a changed row does. The edge
   is always there, clear, so a tick does not shift when it is lit. */
.rules .check { padding-left: var(--space-2); margin-left: calc(-1 * var(--space-2) - 2px); border-left: 2px solid transparent; }
.rules .check.is-changed { border-left-color: var(--accent); }

/* The Save bar stays over the foot of the page, so a box reached with Tab is scrolled clear
   of it rather than focused underneath it; on the create form, which has no bar, the same
   room keeps a refused box's reason on screen under it. */
html:has(.rules) { scroll-padding-bottom: 6rem; }

/* A buff's levels are rows like any other; the ones below where this realm starts the buff
   are drawn only once the tick above asks for them, or where one holds a value. */
.buff-level:not(.is-reachable):not(:has(.is-changed)) { display: none; }
/* Which level the realm starts the buff at, said on the row: with the tick on it is no
   longer the first one drawn. */
.buff-level.is-start .setting-note::after { content: " - where this realm starts it"; }
.rules-panel:has([data-buff-all]:checked) .buff-level { display: block; }

/* The monsters: with script, a row of map tabs over a framed panel for the one picked
   rather than a list beside the panel, which reads as two columns of the same thing, in a
   .tab-strip. Without script, or while a search is typed, every map is one under the other. */
.spawn-browser { display: grid; gap: var(--space-4); }
.spawn-browser.is-browsing { gap: 0; }
.spawn-maps {
  display: flex;
  gap: 2px;
  margin: 0;
  padding: 0;
  list-style: none;
  overflow-x: auto;
  scrollbar-width: none;
}
.spawn-maps li { flex: 0 0 auto; }
.spawn-maps button {
  display: flex;
  align-items: baseline;
  gap: var(--space-2);
  padding: var(--space-2) var(--space-3);
  font: inherit;
  font-size: .85rem;
  white-space: nowrap;
  color: var(--muted);
  background: none;
  border: 1px solid transparent;
  border-bottom: 0;
  border-radius: var(--radius) var(--radius) 0 0;
  cursor: pointer;
}
.spawn-maps button:hover { color: var(--cream); background: rgb(19 35 46 / .5); }
.spawn-maps button[aria-selected=true] {
  color: var(--cream);
  background: var(--surface-2);
  border-color: var(--border-strong);
  box-shadow: inset 0 2px 0 var(--accent);
}
.spawn-maps .count { font-size: .7rem; color: var(--muted); font-variant-numeric: tabular-nums; }
.spawn-browser.is-browsing .spawn-detail {
  padding: var(--space-4);
  background: var(--surface-2);
  border: 1px solid var(--border-strong);
  border-radius: 0 0 var(--radius-lg) var(--radius-lg);
  margin-top: -1px;
}
.spawn-browser.is-browsing .spawn-map:not(.is-active) { display: none; }
.spawn-browser.is-browsing .spawn-map { padding-top: 0; border-top: 0; }
.spawn-detail { display: flex; flex-direction: column; gap: var(--space-4); min-width: 0; }
.spawn-map-head { display: flex; flex-wrap: wrap; align-items: baseline; gap: var(--space-2) var(--space-3); }
/* All, None and Guards only stay together, under the map's name where there is no room beside it. */
.spawn-set { display: flex; gap: var(--space-2); margin-left: auto; }
.spawn-kinds { display: grid; grid-template-columns: repeat(auto-fill, minmax(12rem, 1fr)); gap: var(--space-2) var(--space-4); }
.spawn-kinds .check { font-size: .9rem; }
.spawn-kinds .check .hint { display: block; }

/* --- a realm's permissions (Views/Permissions.cshtml) ----------------------------- */

.grants-player > summary { display: flex; flex-wrap: wrap; align-items: baseline; gap: var(--space-1) var(--space-3); }
.grants { display: grid; grid-template-columns: repeat(auto-fill, minmax(14rem, 1fr)); gap: var(--space-2) var(--space-4); }
.grants .check { font-size: .9rem; }

/* The rules page's Save: in view while the form is, with what has not been saved yet. */
.rules-save {
  position: sticky;
  bottom: 0;
  z-index: 5;
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--space-2) var(--space-4);
  margin: 0 calc(-1 * var(--space-5)) calc(-1 * var(--space-5));
  padding: var(--space-3) var(--space-5);
  background: rgb(14 26 35 / .94);
  backdrop-filter: blur(8px);
  border-top: 1px solid var(--border-strong);
  border-radius: 0 0 var(--radius-lg) var(--radius-lg);
}
.rules-save-status { margin-right: auto; font-size: .9rem; color: var(--muted); }
.rules-save-status.is-dirty { color: var(--cream); }

.btn-small { padding: var(--space-1) var(--space-3); font-size: .85rem; }

@media (max-width: 40rem) {
  .setting { grid-template-columns: minmax(0, 1fr) auto; }
  .setting-normal { font-size: .75rem; }
  .setting-normal { grid-column: 1 / -1; justify-content: flex-start; }
  .rules-save { margin: 0 calc(-1 * var(--space-4)) calc(-1 * var(--space-4)); padding: var(--space-2) var(--space-4) var(--space-3); }
  /* What is unsaved on a line of its own, then the buttons across the bar. */
  .rules-save-status { flex: 1 1 100%; }
  .rules-save .btn { flex: 1 1 0; }
  .spawn-set { margin-left: 0; }
}

/* --- garment colours ------------------------------------------------------------- */

/* The loadout form's colour editor (js/recolour.js, doc/GarmentRecolours.md). .field and
   .check set a display that would otherwise win over the browser's own [hidden] rule, as
   on the rules page. */
.recolour [hidden] { display: none; }
/* The loadout page's skill and look colour pickers (js/skill-tints.js): .stack on each group
   sets a display that would otherwise win over the browser's own [hidden] rule, so a skill the
   build's weapons cannot cast, or a disguise its classes cannot take, stayed on screen. */
.skill-tints [hidden] { display: none; }
/* A garment upload's bodies not added yet (js/garment-variations.js): .stack and .row set a
   display that would otherwise win over the browser's own [hidden] rule. */
.variations [hidden] { display: none; }
.garment-upload[hidden] { display: none; }
.maker-files[hidden] { display: none; }
.maker-files .label { font-weight: 600; font-size: .9rem; }
.maker-files ul { margin: 0; padding-left: 1.2em; }
.maker-files ul:empty { display: none; }
.maker-how summary { cursor: pointer; }
.maker-how ul { margin: .25rem 0; }
.maker-how p { margin: 0; }
/* The line between the folder and the pickers it fills, as the pickers' own heading. */
.maker-or { margin: var(--space-2) 0 0; font-weight: 600; font-size: .9rem; }

/* The editor's controls, one under the other. The picture they change was its first
   column; it is the gear window's character now (.gear-model). */
.recolour-editor {
  display: flex;
  flex-direction: column;
  gap: var(--space-4);
  padding: var(--space-4);
  background: var(--well);
  border: 1px solid var(--border);
  border-radius: var(--radius-lg);
  corner-shape: bevel;
}

/* --- a loadout's build ------------------------------------------------------------- */

/* The whole build on one form (Views/Shared/_LoadoutBuild.cshtml): the rebirth path, then the
   look and gear and the points once all three classes are picked. Here rather than in
   js/gear-window.js, so it holds on a page without script, which asks for the page again
   with the path. While a new path's gear is being fetched, the last path's is dimmed and
   cannot be picked from. */
.loadout-build [hidden] { display: none; }
.loadout-build:has([data-path-part]:invalid) [data-needs-path] { display: none; }
.loadout-build:not(:has([data-path-part]:invalid)) [data-before-path] { display: none; }
.loadout-build[data-path-loading] [data-needs-path] { opacity: .45; pointer-events: none; transition: opacity .2s; }

/* What the path decides - its slots and their panels - in boxes of their own, so a change of
   path can swap them; drawn as if the boxes were not there, so each slot is still placed on
   the window's field. */
.gear-class { display: contents; }

/* --- the gear window -------------------------------------------------------------- */

/*
  A player's loadout form (Views/Shared/_GearWindow.cshtml): the game's Status window on its
  gear tab, drawn here - the gold rim, the fret band, the slate field, each slot's sunk frame
  and the tooltip - with the client's own pictures in the slots (img/gear, img/items). The
  panel a slot opens sits beside the window, so the character can be seen changing while its
  colours are picked; on a phone it goes under it.

  Every place in the window is the game's own, in the gear tab's pixels (Conquer.exe dialog
  135, 206 by 332): --x, --y and --w on [data-at], turned into shares of the field so the
  window scales as one picture and nothing in it drifts.
*/
.gear-body { align-items: flex-end; }
.gear-body .field { flex: 1 1 8rem; max-width: 11rem; }
.gear-body .hint { margin: 0 0 .55rem; flex: 1 1 14rem; }

.gear-layout {
  display: grid;
  grid-template-columns: minmax(0, 24rem) minmax(0, 1fr);
  gap: var(--space-5);
  align-items: start;
}
/* Kept in view while a long panel scrolls beside it, and no taller than the screen it keeps
   to, so a laptop's shows the bottom row too. Over the panels, so a tooltip that runs past
   the window is drawn over what is beside it rather than under it. */
.gear-side {
  position: sticky;
  top: 4.5rem;
  z-index: 2;
  width: min(100%, calc((100dvh - 7rem) * 206 / 332));
}
@media (max-width: 52rem) {
  .gear-layout { grid-template-columns: minmax(0, 1fr); }
  .gear-side { position: relative; top: auto; width: 100%; max-width: 24rem; margin-inline: auto; }
}
.gear-viewer-hint { margin: 0; text-align: center; font-size: .8rem; }

/* The window is a .frame, the site's dialog frame: the client's own Status window is one, and
   it is the only frame on its page. */

/* The tab's field: the game's slate, darker at the foot, with the soft light where the
   character stands. */
.gear-field {
  position: relative;
  aspect-ratio: 206 / 332;
  border: 1px solid var(--slot-ink);
  border-radius: 6px;
  corner-shape: bevel;
  box-shadow: inset 0 1px 0 rgb(226 212 170 / .08), 0 0 0 1px rgb(49 81 99 / .5);
  background:
    radial-gradient(42% 30% at 40% 50%, rgb(86 140 162 / .22), transparent 75%),
    linear-gradient(180deg, #2b4b5d, #23404f 60%, #1b3441);
}

.gear-field [data-at] {
  position: absolute;
  left: calc(var(--x) / 206 * 100%);
  top: calc(var(--y) / 332 * 100%);
  width: calc(var(--w) / 206 * 100%);
  aspect-ratio: 1;
}
[data-at="helmet"]   { --x: 153; --y: 14;  --w: 40; }
[data-at="necklace"] { --x: 153; --y: 67;  --w: 40; }
[data-at="ring"]     { --x: 153; --y: 116; --w: 40; }
[data-at="weaponR"]  { --x: 129; --y: 170; --w: 65; }
[data-at="armor"]    { --x: 129; --y: 245; --w: 65; }
[data-at="weaponL"]  { --x: 55;  --y: 249; --w: 65; }
[data-at="garment"]  { --x: 7;   --y: 164; --w: 40; }
[data-at="talisman"] { --x: 7;   --y: 217; --w: 40; }
[data-at="boots"]    { --x: 7;   --y: 269; --w: 40; }
/* The game stands the character with its feet at (80, 225), in the open middle of the tab. */
.gear-field .gear-model { --x: 22; --y: 8; --w: 116; aspect-ratio: 116 / 232; }

/* A slot: the game's painted frame, four one-pixel lines - dark, pale, mid, dark - around a
   field holding the empty slot's own silhouette until something is in it. */
.gear-slot {
  display: block;
  padding: 0;
  font: inherit;
  color: inherit;
  background: var(--slot-field);
  border: 1px solid var(--slot-rim);
  box-shadow: 0 0 0 1px var(--slot-ink), inset 0 0 0 1px var(--slot-mid), inset 0 0 0 2px var(--slot-ink);
  cursor: pointer;
  z-index: 1;
}
.gear-slot:hover, .gear-slot:focus-visible { border-color: var(--accent-hover); }
.gear-slot:focus-visible { outline: 2px solid var(--focus); outline-offset: 2px; }
.gear-slot[aria-expanded="true"] {
  border-color: var(--accent);
  box-shadow: 0 0 0 1px var(--slot-ink), 0 0 10px 1px rgb(206 190 123 / .45),
              inset 0 0 0 1px var(--slot-mid), inset 0 0 0 2px var(--slot-ink);
}
/* A garment the body picked cannot wear (js/look.js): the error's colour on its rim. */
.gear-slot.gear-misfit { border-color: var(--danger); }
.gear-art { position: absolute; inset: 2px; display: grid; place-items: center; overflow: hidden; }
.gear-art > * { grid-area: 1 / 1; }
.gear-art img { width: 100%; height: 100%; object-fit: contain; }
.gear-art [hidden] { display: none; }
.gear-empty .gear-art { background: var(--empty) center / 100% 100% no-repeat; }
.gear-word { padding: 2px; font-size: .62rem; line-height: 1.15; text-align: center; color: var(--cream); }

[data-at="helmet"],   [data-empty="helmet"]   { --empty: url("../img/gear/helmet.webp"); }
[data-at="necklace"], [data-empty="necklace"] { --empty: url("../img/gear/necklace.webp"); }
[data-at="ring"],     [data-empty="ring"]     { --empty: url("../img/gear/ring.webp"); }
[data-at="weaponR"],  [data-empty="weaponR"]  { --empty: url("../img/gear/weaponR.webp"); }
[data-at="armor"],    [data-empty="armor"]    { --empty: url("../img/gear/armor.webp"); }
[data-at="weaponL"],  [data-empty="weaponL"]  { --empty: url("../img/gear/weaponL.webp"); }
[data-at="garment"],  [data-empty="garment"]  { --empty: url("../img/gear/garment.webp"); }
[data-at="boots"],    [data-empty="boots"]    { --empty: url("../img/gear/boots.webp"); }

/* The glow the game draws under a super item: FireLight's eight frames, 100 ms each. */
.gear-glow {
  width: 100%;
  height: 100%;
  background: url("../img/gear/glow-super.webp") 0 0 / 800% 100% no-repeat;
  mix-blend-mode: screen;
  animation: gear-glow .8s steps(8) infinite;
}
@keyframes gear-glow { to { background-position-x: calc(800% / 7); } }
@media (prefers-reduced-motion: reduce) { .gear-glow { animation: none; } }

/* The way to fill an empty slot: a gold plus in its top right corner. */
.gear-plus { display: none; }
.gear-empty .gear-plus {
  display: block;
  position: absolute;
  top: 4px;
  right: 4px;
  width: 11px;
  height: 11px;
  background:
    linear-gradient(var(--accent), var(--accent)) center / 100% 3px no-repeat,
    linear-gradient(var(--accent), var(--accent)) center / 3px 100% no-repeat;
  filter: drop-shadow(0 0 1px #000) drop-shadow(1px 1px 0 #000);
}
.gear-empty:hover .gear-plus { filter: drop-shadow(0 0 1px #000) drop-shadow(0 0 4px rgb(226 212 170 / .8)); }

/* A slot the build cannot fill now - the off hand beside a two-handed weapon, the garment
   before a body - is dimmed, and its tooltip says why. The talisman is the realm's choice, and
   nor is the shield a pure Warrior's two-hander is given (js/gear-window.js, syncHands). */
.gear-blocked { cursor: not-allowed; }
.gear-blocked .gear-art { opacity: .35; }
.gear-blocked .gear-plus, .gear-fixed .gear-plus { display: none; }
.gear-fixed { cursor: default; }

.gear-model { display: grid; place-items: center; z-index: 0; }
.gear-model canvas.garment-viewer {
  width: 100%;
  height: 100%;
  max-width: none;
  aspect-ratio: auto;
  border: 0;
  background: radial-gradient(40% 5% at 50% 95%, rgb(0 0 0 / .4), transparent 70%);
  touch-action: pan-y;
}
.gear-model .gear-model-wait { margin: 0; padding: 0 var(--space-2); font-size: .75rem; text-align: center; }

/* The game's tooltip (Conquer.exe 0x46fa94): a black field let through, a one-pixel
   #CCCCCC line just outside it, the item's icon at the top left and Arial beside it
   with a black shadow, one line every 18 pixels, each coloured for what it says - the
   colours are the game's, data rather than the site's tokens. The game lets 53%
   through, over a world that is mostly dark ground; over the window's slots and the
   page that is too faint to read, so this lets a quarter through. It opens beside the
   slot under the pointer or the focus, on the side of the window with room, and upwards
   from the bottom row. Drawn a size up from the game's 12 pixels, as the window is
   drawn larger. */
.gear-tip {
  display: none;
  position: absolute;
  z-index: 5;
  top: calc(var(--y) / 332 * 100%);
  min-height: 64px;
  padding-right: 8px;
  font: 13px/19px Arial, "Helvetica Neue", Helvetica, sans-serif;
  color: #fff;
  text-shadow: 1px 1px 0 #000;
  white-space: nowrap;
  background: rgb(0 0 0 / .76);
  box-shadow: 0 0 0 1px #ccc;
  pointer-events: none;
}
.gear-slot:hover + .gear-tip,
.gear-slot:focus-visible + .gear-tip,
.gear-slot.gear-asked + .gear-tip { display: flex; align-items: flex-start; }
/* One with no icon - an empty slot, a shut one - is as tall as its lines. */
.gear-tip:not(:has(.tip-icon)) { min-height: 0; }
/* Escape puts the tooltip away until the pointer or the focus moves on (js/gear-window.js). */
.gear-tips-off .gear-tip:not(.gear-held) { display: none !important; }
/* The right-hand column opens to the left, the left-hand column and the off hand to the
   right, over the panel beside the window; the bottom row's grow upwards from the foot of
   their slot. */
#helmet-tip, #necklace-tip, #ring-tip, #weaponR-tip, #armor-tip {
  right: calc(100% - var(--x) / 206 * 100% + 6px);
}
#garment-tip, #talisman-tip, #boots-tip, #weaponL-tip {
  left: calc((var(--x) + var(--w)) / 206 * 100% + 6px);
}
#helmet-tip   { --x: 153; --y: 14; }
#necklace-tip { --x: 153; --y: 67; }
#ring-tip     { --x: 153; --y: 116; }
#weaponR-tip  { --x: 129; --y: 170; }
#garment-tip  { --x: 7;   --w: 40; --y: 164; }
#armor-tip, #weaponL-tip, #talisman-tip, #boots-tip { top: auto; bottom: calc(var(--b) / 332 * 100%); }
#armor-tip    { --x: 129; --b: 22; }
#weaponL-tip  { --x: 55;  --w: 65; --b: 18; }
#talisman-tip { --x: 7;   --w: 40; --b: 75; }
#boots-tip    { --x: 7;   --w: 40; --b: 23; }
.tip-icon { flex: 0 0 auto; width: 64px; height: 64px; }
.tip-text { display: flex; flex-direction: column; padding-left: 5px; }
.gear-tip > .tip-text:only-child { padding: 0 0 0 8px; }
.tip-line { display: block; }
.tip-line[hidden], .gear-tip [hidden] { display: none; }
.tip-gems:empty { display: none; }
.tip-super { color: #ebe57f; }
.tip-elite { color: #e804fd; }
.tip-unique { color: #00ff00; }
.tip-refined { color: #5e87ca; }
.tip-magic { color: #8ccefd; }
.tip-gem { color: #7df983; }
.tip-bonus { color: #c501e7; }
.tip-bless { color: #f87409; }
.tip-enchant { color: #f3e250; }
.tip-blocked { color: #ff0000; }
/* realms.dll's recolour lines, in the pale yellow the game gives a weapon's effect, each colour
   a small solid swatch ringed in the box's own grey (Client/src/recolourui.c). */
.tip-effect { color: #ffff80; }
.tip-swatch {
  display: inline-block;
  width: 9px;
  height: 9px;
  border-radius: 50%;
  box-shadow: 0 0 0 1px #ccc;
  vertical-align: -1px;
}
/* A recoloured garment's icon carries a small colour wheel in its bottom left, as the client
   badges it: a spoke of every hue round a white centre, in a dark ring. */
.gear-recoloured .gear-art::after {
  content: "";
  position: absolute;
  left: 3px;
  bottom: 3px;
  width: 26%;
  aspect-ratio: 1;
  border-radius: 50%;
  background:
    radial-gradient(circle, #fff 0 18%, transparent 60%),
    conic-gradient(red, yellow, lime, cyan, blue, magenta, red);
  box-shadow: 0 0 0 1px #202020;
}
/* What the game would not say: the site's note on what the realm decides. */
.tip-note { white-space: normal; max-width: 16rem; color: #b8c2c6; font-size: 12px; line-height: 16px; margin: 2px 0 4px; }

/* What the open slot holds, at the head of its panel: the same tooltip, standing in the page,
   which is where a phone - with no pointer to hover with - sees it. */
.gear-tip.gear-held {
  display: flex;
  position: static;
  align-items: flex-start;
  white-space: normal;
  pointer-events: auto;
}
.gear-held .tip-note { max-width: none; }

/* On a phone the window is the page's width and a tooltip beside a slot has nowhere to go,
   so one under the pointer or the focus spans the window instead. */
@media (max-width: 30rem) {
  #helmet-tip, #necklace-tip, #ring-tip, #weaponR-tip, #armor-tip,
  #garment-tip, #talisman-tip, #boots-tip, #weaponL-tip { left: 2%; right: 2%; white-space: normal; }
  .tip-icon { width: 40px; height: 40px; }
}

/* --- a slot's panel ----------------------------------------------------------------- */

/* Without script every panel is drawn one under the other, as the form was; with it, only
   the open slot's (js/gear-window.js, which sets [hidden]). */
.gear-panels { display: flex; flex-direction: column; gap: var(--space-4); }
.gear-panel[hidden], .gear-panel-hint[hidden] { display: none; }
/* Scrolled to clear of the bar, which stays at the top of a wide window. */
.gear-panel { scroll-margin-top: 5rem; }
@media (max-width: 54rem) { .gear-panel { scroll-margin-top: var(--space-4); } }
.gear:not(.gear-live) .gear-panel-hint { display: none; }
.gear-panel-hint { margin: 0; }

.gear-options {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(8rem, 1fr));
  gap: var(--space-2);
}
.gear-option, .gear-gem {
  position: relative;
  display: flex;
  align-items: center;
  gap: var(--space-2);
  padding: var(--space-2);
  font-weight: 400;
  background: var(--well);
  border: 1px solid var(--border);
  border-radius: var(--radius);
  corner-shape: bevel;
  cursor: pointer;
  transition: border-color .15s, background-color .15s;
}
.gear-option:hover, .gear-gem:hover { background: var(--surface-2); border-color: var(--border-strong); }
.gear-option input, .gear-gem input {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  margin: 0;
  opacity: 0;
  cursor: pointer;
}
/* Two pixels of ring outside the border, to pick the chosen one out at a glance; one
   is easy to miss beside its neighbours. */
.gear-option:has(input:checked), .gear-gem:has(input:checked) {
  border-color: var(--accent);
  box-shadow: 0 0 0 2px var(--accent);
}
.gear-option:has(input:focus-visible), .gear-gem:has(input:focus-visible) { outline: 2px solid var(--focus); outline-offset: 2px; }
.gear-option-art {
  flex: 0 0 auto;
  width: 2.75rem;
  height: 2.75rem;
  display: grid;
  place-items: center;
  background: var(--slot-field);
  border: 1px solid var(--slot-rim);
  box-shadow: 0 0 0 1px var(--slot-ink), inset 0 0 0 1px var(--slot-mid);
}
.gear-option-art img { width: 100%; height: 100%; object-fit: contain; }
.gear-option-empty { background: var(--slot-field) var(--empty) center / 100% 100% no-repeat; }
.gear-option-name { min-width: 0; font-size: .85rem; line-height: 1.25; overflow-wrap: anywhere; }

/* The garments, as tiles (js/gear-window.js): the list they are drawn from stays the form's
   field, for the scripts that read it, and goes out of sight - kept for a screen reader,
   which is told the tiles are the same question. */
.gear-listed > label, .gear-listed > select {
  position: absolute; width: 1px; height: 1px; margin: -1px; padding: 0; border: 0;
  overflow: hidden; clip: rect(0 0 0 0); clip-path: inset(50%); white-space: nowrap;
}
.gear-garments { max-height: 26rem; overflow-y: auto; padding: 2px; }
/* A heading among a panel's tiles: the garments' groups, the main hand's one- and two-handed. */
.gear-garments-group, .gear-options-group {
  grid-column: 1 / -1;
  margin: var(--space-2) 0 0;
  font-weight: 600;
  font-size: .85rem;
}
.gear-garments-group:first-child, .gear-options-group:first-child { margin-top: 0; }
.gear-options-group .hint { margin-left: var(--space-2); font-weight: 400; }
.gear-garments .gear-option { font: inherit; color: inherit; text-align: left; }
.gear-garments .gear-option[aria-checked="true"] { border-color: var(--accent); box-shadow: 0 0 0 2px var(--accent); }
.gear-garments .gear-option:focus-visible { outline: 2px solid var(--focus); outline-offset: 2px; }
.gear-option-upload {
  background:
    linear-gradient(var(--accent), var(--accent)) center / 40% 3px no-repeat,
    linear-gradient(var(--accent), var(--accent)) center / 3px 40% no-repeat,
    var(--slot-field);
}

.gear-sockets { margin: 0; padding: 0; border: 0; min-width: 0; }
.gear-sockets legend { padding: 0; margin-bottom: var(--space-2); font-weight: 600; font-size: .9rem; }
.gear-gem-grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(6rem, 1fr)); gap: var(--space-1); }
/* Said only where there is a pointer to point with. A touch screen keeps :hover on what was
   last tapped, which would leave a slot's tooltip over the panel the tap opened, whose head
   says the same. */
@media (hover: none) {
  .gear-pointer-only { display: none; }
  .gear-slot:hover:not(:focus-visible) + .gear-tip { display: none; }
}
.gear-gem { padding: var(--space-1) var(--space-2); }
.gear-gem-art { flex: 0 0 auto; width: 1.75rem; height: 1.75rem; display: grid; place-items: center; }
.gear-gem-art img { width: 100%; height: 100%; object-fit: contain; }
.gear-gem-name { font-size: .8rem; }

/* Until a body is picked there is no garment to offer, since which ones there are depends
   on it: the garment's list and the character are out of sight, and a line says why. Here
   rather than in js/look.js, so it holds on a page without script; the script does the same
   with [hidden] for a browser that does not know :has(). */
.look [hidden] { display: none; }
.look:has(select[data-body-part]:invalid) [data-needs-body] { display: none; }
.look:not(:has(select[data-body-part]:invalid)) [data-before-body] { display: none; }

/* The four bodies on the home page's studio: agile before muscular, male before
   female, as buttons two by two under its viewer. A loadout form asks the same as
   two dropdowns above its gear window. */
.body-grid { display: grid; grid-template-columns: 1fr 1fr; gap: .35rem; }
.body-grid button {
  display: block;
  width: 100%;
  padding: .25rem .6rem;
  font: inherit;
  font-size: .8rem;
  text-align: center;
  white-space: nowrap;
  color: var(--muted);
  background: var(--well);
  border: 1px solid var(--border);
  border-radius: 3px;
  corner-shape: bevel;
  cursor: pointer;
  transition: color .2s, border-color .2s;
}
.body-grid button:hover { color: var(--text); }
.body-grid button[aria-checked="true"] { color: var(--accent); border-color: rgb(206 190 123 / .6); }

/* The live garment viewer (js/garment-viewer.js), in a loadout's look and the upload's
   preview. It stands in a pool of mid slate rather than on the checkerboard it had, which
   was there so a garment's dark and light edges both read in a light and a dark theme:
   with one theme, a ground lighter than a black robe and darker than a white trim does
   that, and looks like the game's own dialogs rather than an image editor.
   It draws the garment framed for every turn at once, so it is given a fixed shape; [hidden]
   is restated, as the display set here would otherwise win over it. */
canvas.garment-viewer {
  display: block;
  width: 100%;
  max-width: 22rem;
  aspect-ratio: 3 / 4;
  height: auto;
  touch-action: none;
  cursor: grab;
  border: 1px solid var(--border);
  border-radius: var(--radius);
  corner-shape: bevel;
  background:
    radial-gradient(60% 12% at 50% 90%, rgb(0 0 0 / .45), transparent 70%),
    radial-gradient(75% 60% at 50% 42%, #2a4655, #13232e 60%, #0a141b);
}
canvas.garment-viewer:active { cursor: grabbing; }
.upload-preview[hidden], .upload-preview [hidden] { display: none; }
.upload-preview [aria-pressed=true] { border-color: var(--accent); }
.upload-preview .label { font-weight: 600; font-size: .9rem; }

.recolour-sections { list-style: none; margin: 0; padding: 0; display: flex; flex-direction: column; gap: var(--space-1); }
.recolour-sections li { display: flex; align-items: center; gap: var(--space-2); }
.recolour-section {
  display: flex;
  align-items: center;
  gap: var(--space-2);
  flex: 1 1 auto;
  padding: var(--space-2);
  font: inherit;
  text-align: left;
  color: var(--text);
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: var(--radius);
  corner-shape: bevel;
  cursor: pointer;
  transition: border-color .15s, background-color .15s;
}
.recolour-section:hover { background: var(--surface-2); }
.recolour-section[aria-pressed="true"] { border-color: var(--accent); box-shadow: 0 0 0 1px var(--accent); }
.recolour-reset { padding: 0 var(--space-2); font-size: .75rem; }
.recolour-swatch { flex: 0 0 auto; width: 1.5rem; height: 1.5rem; border: 1px solid var(--border-strong); border-radius: 3px; transition: background-color .3s; }
.recolour-chip { width: 2rem; height: 1rem; border: 1px solid var(--border); border-radius: 3px; }
.recolour-hex { width: 6rem; font-family: var(--mono); }

/* A loadout's skill colours (Views/Shared/_SkillTints.cshtml): each part a row of radio
   swatches, the game's own colours first, struck through, then the twelve of Core's
   TintPalette. The radio is the swatch's own, hidden, so the form posts without script and
   a keyboard moves through a row as through any radio group. */
.skill-tint-parts { align-items: flex-start; }
.skill-tint-parts > .skill-tint { flex: 1 1 18rem; }
/* The preview (js/skill-preview.js): the game's stage at its own pixels, scaled down to fit. */
.skill-preview {
  display: block; width: 100%; max-width: 700px; height: auto;
  border: 1px solid var(--border); border-radius: var(--radius); corner-shape: bevel;
  background: #0a141b;
}
.skill-preview[hidden] { display: none; }
/* The ghost and disguise pickers (_LookTints.cshtml): a card a form, each with its own preview -
   the garment viewer's canvas, slate and hint, as the gear window shows a garment and its
   recolour (js/look-preview.js) - over its swatches. As many cards to a row as fit, one on a
   phone. min-width 0 on the canvas: a canvas's least width is its drawing buffer's, which the
   viewer sizes to what it is shown at, so it would otherwise hold its grid track open. */
.look-tint-grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(min(100%, 17rem), 1fr)); gap: var(--space-4); }
.look-tint { min-width: 0; }
.look-preview-box[hidden] { display: none; }
.look-preview-box > canvas.garment-viewer { width: 100%; max-width: 16rem; min-width: 0; margin-inline: auto; }
.skill-tint { border: 0; margin: 0; padding: 0; min-width: 0; }
.skill-tint > legend { padding: 0; margin-bottom: var(--space-1); font-size: .9rem; font-weight: 600; }
.tint-swatches { display: flex; flex-wrap: wrap; gap: .3rem; }
.tint-swatch {
  position: relative;
  width: 1.5rem; height: 1.5rem;
  border: 1px solid var(--border-strong);
  border-radius: 3px;
  background: var(--tint, transparent);
  cursor: pointer;
}
.tint-swatch.is-default {
  background: linear-gradient(to top right, transparent calc(50% - 1px), var(--muted) 50%, transparent calc(50% + 1px)), var(--surface);
}
.tint-swatch input { position: absolute; inset: 0; opacity: 0; margin: 0; cursor: pointer; }
.tint-swatch:has(input:checked) { box-shadow: 0 0 0 2px var(--surface), 0 0 0 4px var(--accent); }
.tint-swatch:has(input:focus-visible) { outline: 2px solid var(--focus); outline-offset: 4px; }
/* The honeycomb: each cell a keyboard stop, the chosen one ringed in white over a dark
   halo so the ring shows on a white cell as well as a black one. Cells separate on the
   well's own dark, so the lattice reads as the fret band's. */
.honeycomb { display: block; width: 100%; max-width: 22rem; height: auto; }
.honeycomb polygon[role="radio"] { cursor: pointer; stroke: var(--well); stroke-width: 1.2; transition: opacity .15s; }
.honeycomb polygon[role="radio"]:hover { opacity: .8; }
.honeycomb polygon[role="radio"]:focus { outline: none; }
.honeycomb polygon[role="radio"]:focus-visible { stroke: var(--focus); stroke-width: 2; }
.honeycomb-mark { fill: none; stroke: #fff; stroke-width: 2.5; pointer-events: none; filter: drop-shadow(0 0 1.5px #000); }
.honeycomb-mark[hidden] { display: none; }

dialog {
  max-width: 28rem;
  color: var(--text);
  background: var(--surface);
  border: 1px solid #a8955c;
  border-radius: var(--radius-lg);
  corner-shape: bevel;
  box-shadow: 0 0 0 1px #05080b, 0 30px 80px -20px rgb(0 0 0 / .8);
}
dialog::backdrop { background: rgb(3 6 9 / .6); backdrop-filter: blur(2px); }

/* The report page's garments, each a still of its front with a button per body, picked like a
   radio. The still is drawn at twice the size it is shown, on the viewer's slate. */
.garment-picks { display: flex; flex-wrap: wrap; gap: .75rem; }
.garment-pick { position: relative; display: flex; flex-direction: column; align-items: flex-start; gap: .5rem;
                padding: .75rem; cursor: pointer; max-width: 100%;
                border: 1px solid var(--border); border-radius: var(--radius); corner-shape: bevel; background: var(--surface); }
.garment-pick:has(input:checked) { border-color: var(--accent); box-shadow: 0 0 0 2px var(--accent); }
.garment-pick:focus-within { outline: 2px solid var(--accent); outline-offset: 2px; }
.garment-pick label { cursor: pointer; }
.garment-pick img, .garment-edit-bodies img {
  display: block; width: 150px; height: 200px; max-width: 100%; object-fit: contain;
  border-radius: var(--radius);
  background: radial-gradient(75% 60% at 50% 42%, #2a4655, #13232e 60%, #0a141b);
}
.garment-pick input { position: absolute; opacity: 0; pointer-events: none; }
.garment-pick [aria-pressed=true] { border-color: var(--accent); }

/* A garment's edit page (Views/GarmentEdit.cshtml): each body it has with a still of it now,
   drawn as the report page's are, and the effect's inputs shown for the choice that reads
   them, in CSS alone so the form needs no script. A column narrower than 18rem cut the file
   inputs' own "No file chosen". */
.garment-edit-bodies { display: grid; grid-template-columns: repeat(auto-fill, minmax(min(18rem, 100%), 1fr)); gap: var(--space-4); }
.garment-edit-icon { align-items: center; }
.garment-edit-icon img { width: 48px; height: 48px; image-rendering: pixelated; }
.effect-edit:not(:has(input[value=keep]:checked)) [data-effect-keep],
.effect-edit:not(:has(input[value=replace]:checked)) [data-effect-replace] { display: none; }

/* --- tickets --------------------------------------------------------------------- */

/* Read by a screen reader and drawn nowhere: a count's "new", a file input inside its label. */
.visually-hidden {
  position: absolute; width: 1px; height: 1px; margin: -1px; padding: 0; border: 0;
  overflow: hidden; clip: rect(0 0 0 0); clip-path: inset(50%); white-space: nowrap;
}

/* Three tabs that are links, each its own page: the client's tab row, a gold underline on the
   one open. They wrap on a phone: scrolled sideways, the third was cut off with nothing to
   say there was more. */
.seg { display: flex; flex-wrap: wrap; gap: var(--space-1); border-bottom: 1px solid var(--border); }
.seg a {
  display: inline-flex; align-items: center; gap: var(--space-2); white-space: nowrap;
  padding: var(--space-2) var(--space-4); margin-bottom: -1px;
  font-family: var(--serif); color: var(--muted); text-decoration: none;
  border-bottom: 2px solid transparent;
}
.seg a:hover { color: var(--cream); }
.seg a[aria-current=page] { color: var(--cream); border-bottom-color: var(--accent); }
.seg-count {
  min-width: 1.4rem; padding: 0 .35rem; font-family: var(--font); font-size: .75rem; font-weight: 600;
  line-height: 1.4rem; text-align: center; border-radius: 3px; corner-shape: bevel;
  background: var(--accent); color: var(--accent-text);
}

.tag-bug  { background: var(--bad-bg); color: var(--bad-text); border-color: #6e2c1e; }
.tag-idea { background: var(--surface-2); color: var(--cream); border-color: var(--border-strong); }
.tag-new  { background: var(--accent); color: var(--accent-text); border-color: var(--accent); }
/* Statuses by number (Tickets.State): New and Looking into it plain, Planned gold, Done green,
   the two that close a ticket without doing it red-brown. */
.tag-status-2 { background: rgb(206 190 123 / .14); color: var(--accent); border-color: rgb(206 190 123 / .45); }
.tag-status-3 { background: var(--ok-bg); color: var(--ok-text); border-color: #25583a; }
.tag-status-4, .tag-status-5 { background: var(--bad-bg); color: var(--bad-text); border-color: #6e2c1e; }

.ticket-list { list-style: none; margin: 0; padding: 0; display: flex; flex-direction: column; gap: var(--space-3); }
.ticket-row { display: flex; flex-direction: column; gap: var(--space-2); padding: var(--space-4) var(--space-5); }
.ticket-row-head { flex-wrap: wrap; }
.ticket-row-meta { flex-wrap: wrap; align-items: center; }
.ticket-row-title { font-family: var(--serif); font-size: 1.15rem; color: var(--cream); text-decoration: none; min-width: 0; overflow-wrap: anywhere; }
.ticket-row-title:hover { color: var(--accent-hover); text-decoration: underline; }
.ticket-unread { border-color: rgb(206 190 123 / .45); }

.ticket-title { overflow-wrap: anywhere; }
.ticket-subhead { font-family: var(--serif); font-size: 1.05rem; font-weight: 400; color: var(--cream); margin: 0; }
.ticket-search { flex-wrap: wrap; align-items: center; }
.ticket-search input[type=text] { flex: 1 1 14rem; }

/* A post: who and when on one line, then the text as it was typed. */
.post { display: flex; flex-direction: column; gap: var(--space-3); }
.post-head { display: flex; flex-wrap: wrap; align-items: center; gap: var(--space-2); }
.post-head form { margin: 0; }
.post-author { font-weight: 600; color: var(--cream); }
.post-body { white-space: pre-wrap; overflow-wrap: anywhere; }
.post-staff { border-color: rgb(206 190 123 / .35); }
.post-hidden { opacity: .7; border-style: dashed; }

.post-files { display: flex; flex-wrap: wrap; gap: var(--space-3); }
.post-file { margin: 0; display: flex; flex-direction: column; gap: var(--space-1); max-width: min(100%, 26rem); }
.post-file img, .post-file video {
  display: block; max-width: 100%; max-height: 22rem; object-fit: contain;
  border: 1px solid var(--border); border-radius: var(--radius); corner-shape: bevel; background: var(--well);
}
.post-file figcaption { font-size: .85rem; overflow-wrap: anywhere; }

/* An update's files are the post's pictures rather than evidence beside a message, so
   they are drawn larger than a ticket's. */
.update-files .post-file { max-width: min(100%, 40rem); }
.update-files .post-file img, .update-files .post-file video { max-height: 30rem; }
.update-files-edit { border: 0; padding: 0; margin: 0; }
.update-files-edit ul { list-style: none; margin: 0; padding: 0; }
.update-file-edit { display: flex; flex-wrap: wrap; gap: var(--space-3); align-items: flex-start; }
.update-file-thumb img, .update-file-thumb video {
  display: block; width: 8rem; height: 6rem; object-fit: cover;
  border: 1px solid var(--border); border-radius: var(--radius); corner-shape: bevel; background: var(--well);
}
.update-file-fields { flex: 1 1 16rem; }

/* Reactions: small wells, gold-rimmed when they are the reader's own. */
.reactions { display: flex; flex-wrap: wrap; gap: var(--space-1); }
.reactions form { margin: 0; }
.react {
  display: inline-flex; align-items: center; gap: .3rem; min-height: 2rem; padding: 0 .55rem;
  font: inherit; font-size: .9rem; color: var(--text); cursor: pointer;
  border: 1px solid var(--border); border-radius: var(--radius); corner-shape: bevel; background: var(--well);
  transition: border-color .15s;
}
.react:hover { border-color: #7c6a44; }
.react[aria-pressed=true] { border-color: var(--accent); background: rgb(206 190 123 / .12); }
.react-count:empty { display: none; }

/* The attachment picker and each file's upload under it. */
.ticket-files-pick { align-self: flex-start; position: relative; }
/* The input inside is the one that takes focus, and it is hidden, so the label shows it. */
.ticket-files-pick:has(:focus-visible) { outline: 2px solid var(--focus); outline-offset: 2px; }
.plain-list { list-style: none; margin: 0; padding: 0; }
.ticket-uploads { list-style: none; margin: 0; padding: 0; display: flex; flex-direction: column; gap: var(--space-2); }
.ticket-upload { display: flex; flex-wrap: wrap; align-items: center; gap: var(--space-2); font-size: .9rem; }
.ticket-upload-name { overflow-wrap: anywhere; }
.ticket-upload progress { flex: 1 1 8rem; height: .5rem; accent-color: var(--accent); }
.ticket-upload-error { color: var(--danger); }

/* --- a page's tutorial (_TutorialCoach, js/tutorial.js) --- */

/* The button that starts it, at the far end of the page's head, away from the title. */
.tutorial-start { margin-left: auto; }
/* A tip's Close, apart from Back and Next. */
.tutorial-tip-close { margin-left: auto; }

/* A tip, just above the part of the form it is about, with a gold wedge pointing down at it. */
.tutorial-tip {
  position: relative;
  display: flex;
  flex-direction: column;
  gap: var(--space-2);
  margin-bottom: var(--space-2);
  padding: var(--space-3) var(--space-4);
  background: linear-gradient(180deg, rgb(206 190 123 / .10), transparent 5rem), var(--well);
  border: 1px solid var(--accent);
  border-radius: var(--radius);
  corner-shape: bevel;
  box-shadow: 0 10px 30px -18px rgb(0 0 0 / .9);
}
.tutorial-tip::after {
  content: "";
  position: absolute;
  left: var(--space-5);
  bottom: -7px;
  width: 12px;
  height: 12px;
  background: var(--well);
  border-right: 1px solid var(--accent);
  border-bottom: 1px solid var(--accent);
  transform: rotate(45deg);
}
.tutorial-tip p { margin: 0; }
/* .btn sets a display, which would otherwise win over [hidden] on Back and Next. */
.tutorial-tip [hidden] { display: none; }
.tutorial-tip-count { font-size: .75rem; letter-spacing: .04em; text-transform: uppercase; color: var(--accent); }
.tutorial-tip-body { display: flex; flex-direction: column; gap: var(--space-2); font-size: .925rem; }
.tutorial-tip-title { font-family: var(--serif); font-size: 1.1rem; color: var(--cream); margin: 0; }
.tutorial-tabs { margin: 0; display: grid; grid-template-columns: max-content 1fr; gap: var(--space-1) var(--space-3); }
.tutorial-tabs dt { font-family: var(--serif); color: var(--cream); }
.tutorial-tabs dd { margin: 0; color: var(--muted); }
@media (max-width: 30rem) {
  .tutorial-tabs { grid-template-columns: 1fr; }
  .tutorial-tabs dd { margin-bottom: var(--space-1); }
}

/* The part a tip is about, ringed in gold; it breathes once as it is pointed at. */
.tutorial-mark {
  outline: 2px solid var(--accent);
  outline-offset: 6px;
  box-shadow: 0 0 0 8px rgb(206 190 123 / .10), 0 0 28px rgb(206 190 123 / .22);
  animation: tutorial-mark 1.2s var(--ease-out) 1;
}
@keyframes tutorial-mark {
  from { outline-offset: 12px; outline-color: transparent; }
}

/* --- a realm's events (RealmDetail, Realms) ----------------------------------------- */

.event-list { list-style: none; margin: 0; padding: 0; display: flex; flex-direction: column; gap: var(--space-3); }
.event { display: flex; flex-direction: column; gap: var(--space-2); }
.event-name { font-family: var(--serif); font-size: 1.1rem; font-weight: 400; color: var(--cream); }
.event-note { margin: 0; overflow-wrap: anywhere; max-width: 70ch; }
/* The forms in a details.more read at the page's size, not the summary's small print. */
details.event-more > div { font-size: 1rem; color: var(--text); max-width: none; }
/* The host's own actions, not a note to read: no info mark, at the page's size, in gold. */
details.event-more > summary { font-size: 1rem; font-family: var(--serif); color: var(--accent); }
details.event-more > summary::before { content: "+"; font-style: normal; }
details.event-more[open] > summary::before { content: "\2212"; }
/* .field sets a display, which would otherwise win over [hidden] on the Name field. */
.event-form [hidden] { display: none; }
.event-form-row { align-items: flex-end; }
.event-form-row > .field { flex: 1 1 12rem; }
/* The realm list's event line under a realm's name. */
.realm-event { display: flex; flex-wrap: wrap; align-items: center; gap: var(--space-2); margin-top: var(--space-1); }
.realm-event form { display: inline; }

/* What a realm is for and a player is up for (UpForTags): the tags, the filter and the boxes. */
.tag-for { background: var(--surface-2); color: var(--muted); border-color: var(--border-strong); }
.up-for-filter { margin: var(--space-1) 0; }
.up-for-filter a.tag { text-decoration: none; }
.up-for-filter a.tag-on { background: rgb(206 190 123 / .14); color: var(--accent); border-color: rgb(206 190 123 / .45); }
.up-for-tags { display: flex; flex-wrap: wrap; gap: var(--space-2) var(--space-4); }
.realm-tags { align-items: flex-end; }

/* --- motion ---------------------------------------------------------------------- */

@media (prefers-reduced-motion: reduce) {
  *, *::before, *::after {
    transition-duration: .01ms !important;
    animation-duration: .01ms !important;
    animation-iteration-count: 1 !important;
    scroll-behavior: auto !important;
  }
}
