/* The Activity's own look, and the first time this project has had one. The bot
   inherited Discord's typography and was budgeted to 28 columns inside a
   codeblock; none of that applies here. What carries over is the palette
   convention — RARITY_COLOR's white/green/blue/purple/orange — because a player
   arriving from anywhere else already knows what blue means. */

/* SAFE AREAS COME FROM DISCORD FIRST AND `env()` SECOND, AND BOTH HALVES ARE
   LOAD-BEARING. Discord injects `--discord-safe-area-inset-*` into the frame on
   mobile, which is the only source that knows about ITS chrome — the drag handle
   above the activity and the system nav below it — where `env()` knows only about
   the notch. `env()` is the fallback for iOS and for opening the page in a plain
   browser during development, and 0px is the fallback for a desktop client that
   injects neither. Written once here as four variables so no rule has to repeat
   the three-deep fallback. */
:root {
  --safe-top: var(--discord-safe-area-inset-top, env(safe-area-inset-top, 0px));
  --safe-right: var(--discord-safe-area-inset-right, env(safe-area-inset-right, 0px));
  --safe-bottom: var(--discord-safe-area-inset-bottom, env(safe-area-inset-bottom, 0px));
  --safe-left: var(--discord-safe-area-inset-left, env(safe-area-inset-left, 0px));

  color-scheme: dark;

  --bg: #101918;
  --panel: #1b2725;
  --panel-raised: #283631;
  --line: #465247;
  --ink: #f0ecda;
  --ink-dim: #b4bbae;

  --gold: #d8a54a;
  --hp: #c34a4a;
  --mana: #4a7fc3;
  --xp: #7a5fc3;

  /* The rarity ladder, unchanged from combat/ansi.js. */
  --common: #e8e8ef;
  --uncommon: #4fbf6a;
  --rare: #4a8fd8;
  --epic: #a05fd8;
  --legendary: #d8842a;

  --warrior: #d8a54a;
  --rogue: #df8fab;
  --mage: #78cbd9;
}

/* The existing 34 KB font is local and non-blocking. Limit it to its actual
   glyph coverage so player names and punctuation use the readable fallback. */
@font-face {
  font-family: 'Sigil Pixel';
  src: url('/assets/fonts/m5x7/m5x7.ttf') format('truetype');
  font-display: swap;
  unicode-range: U+0020-007E, U+00B0, U+00B7, U+00D7;
}

* {
  box-sizing: border-box;
}

html,
body {
  /* The frame is the viewport on mobile and the page must not rubber-band past
     it: an overscroll on iOS detaches the content from Discord's own scroll and
     reads as the Activity coming loose. */
  overscroll-behavior: none;
}

body {
  margin: 0;
  /* Long descriptions retain a system face; the pixel face marks headings. */
  font-family:
    'Inter',
    system-ui,
    -apple-system,
    'Segoe UI',
    sans-serif;
  background: radial-gradient(ellipse at top, #2d4036, var(--bg) 650px);
  color: var(--ink);
  -webkit-font-smoothing: antialiased;

  /* A LONG-PRESS ON A PHONE MUST NOT SELECT TEXT OR FLASH A GREY BOX. Both are
     browser defaults that read as a broken tap rather than as a deliberate
     gesture. Re-enabled for the one element where selecting text is the point. */
  -webkit-tap-highlight-color: transparent;
  -webkit-user-select: none;
  user-select: none;
  /* iOS inflates font sizes on rotation unless told not to. */
  -webkit-text-size-adjust: 100%;
}

.notice,
.stats {
  -webkit-user-select: text;
  user-select: text;
}

#app {
  max-width: 1120px;
  margin: 0 auto;
  /* THE GUTTERS ARE THE SAFE AREA PLUS THE DESIGN'S OWN 16px, NOT THE LARGER OF
     THE TWO. A landscape phone puts the notch inside the left inset, so a
     max() would leave the first column of text against it. The bottom is the
     generous one: it clears the home-gesture strip AND gives the last card room
     to sit above Discord's own edge. */
  padding: calc(20px + var(--safe-top)) calc(16px + var(--safe-right))
    calc(48px + var(--safe-bottom)) calc(16px + var(--safe-left));
}

.status {
  color: var(--ink-dim);
  text-align: center;
  padding: 48px 16px;
  font-size: 15px;
}

.status.error {
  color: var(--hp);
}

header.account {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 12px;
  padding-bottom: 14px;
  margin-bottom: 18px;
  border-bottom: 1px solid var(--line);
}

header.account h1 {
  font-family: 'Sigil Pixel', monospace;
  font-size: 48px;
  font-weight: 650;
  margin: 0;
  letter-spacing: 0.06em;
  color: var(--gold);
}

header.account .purse {
  color: var(--gold);
  font-variant-numeric: tabular-nums;
  font-weight: 600;
  font-size: 15px;
}

.roster {
  display: grid;
  gap: 14px;
  /* One column on a phone, two where there is room. An Activity is played in a
     voice channel on both. */
  grid-template-columns: 1fr;
}

@media (min-width: 620px) {
  .roster {
    grid-template-columns: 1fr 1fr;
  }
}

.card {
  background: linear-gradient(145deg, #24322d, var(--panel));
  border: 1px solid var(--line);
  border-radius: 3px;
  padding: 16px;
  box-shadow:
    inset 0 0 0 3px #121e1b,
    0 5px 12px #0003;
  min-width: 0;
}

.card header {
  display: flex;
  align-items: center;
  flex-wrap: wrap;
  gap: 8px;
  margin-bottom: 14px;
}

.card .name {
  font-size: 17px;
  font-weight: 650;
  letter-spacing: -0.01em;
}

.card .klass {
  font-size: 12px;
  text-transform: uppercase;
  letter-spacing: 0.08em;
  font-weight: 600;
}

.card .klass[data-class='warrior'] {
  color: var(--warrior);
}
.card .klass[data-class='rogue'] {
  color: var(--rogue);
}
.card .klass[data-class='mage'] {
  color: var(--mage);
}

.card .doing {
  margin-left: auto;
  font-size: 11px;
  text-transform: uppercase;
  letter-spacing: 0.07em;
  color: var(--ink-dim);
  background: var(--panel-raised);
  border-radius: 999px;
  padding: 3px 9px;
}

/* The bars the PNG pipeline used to draw. Here they are two divs, which is the
   single clearest illustration of what the port buys: no palette grid, no dirty
   rectangles, no re-encode per beat. */
.meter {
  margin-bottom: 9px;
}

.meter .row {
  display: flex;
  justify-content: space-between;
  font-size: 11px;
  color: var(--ink-dim);
  margin-bottom: 4px;
  font-variant-numeric: tabular-nums;
}

.meter .track {
  height: 7px;
  border-radius: 4px;
  background: var(--panel-raised);
  overflow: hidden;
}

.meter .fill {
  height: 100%;
  border-radius: 4px;
  transition: width 220ms ease-out;
}

.meter[data-kind='hp'] .fill {
  background: var(--hp);
}
.meter[data-kind='mana'] .fill {
  background: var(--mana);
}
.meter[data-kind='xp'] .fill {
  background: var(--xp);
}

/* Every stat for every class, unconditionally — `/character info`'s rule, and
   the reason is unchanged: a zero that is shown reads as "cannot have it", a
   zero that is hidden reads as "the class lacks the stat". */
.stats {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: 2px 16px;
  margin-top: 14px;
  padding-top: 13px;
  border-top: 1px solid var(--line);
  font-size: 12.5px;
}

.stats div {
  display: flex;
  justify-content: space-between;
  padding: 2.5px 0;
}

.stats .label {
  color: var(--ink-dim);
}

.stats .value {
  font-variant-numeric: tabular-nums;
  font-weight: 600;
}

.empty {
  color: var(--ink-dim);
  font-size: 14px;
  line-height: 1.55;
}

/* The hunt panel: what a character is doing, how close it is to the cap, and
   the one thing to do about it. */
.hunt {
  margin-top: 13px;
  padding-top: 13px;
  border-top: 1px solid var(--line);
}

.hunt .row {
  display: flex;
  justify-content: space-between;
  gap: 10px;
  font-size: 12px;
  color: var(--ink-dim);
  margin-bottom: 6px;
}

.hunt .track {
  height: 7px;
  border-radius: 4px;
  background: var(--panel-raised);
  overflow: hidden;
  margin-bottom: 11px;
}

.hunt .fill {
  height: 100%;
  border-radius: 4px;
  background: var(--uncommon);
  transition: width 220ms ease-out;
}

/* PAST THE CAP NOTHING MORE IS BANKED, so a capped hunt is waste that has
   already started rather than an activity still running. Red says that in the
   one glance the panel gets. */
.hunt.capped .fill {
  background: var(--hp);
}

.hunt.capped .row {
  color: var(--hp);
}

button.primary {
  width: 100%;
  padding: 9px 14px;
  border: 1px solid var(--line);
  border-radius: 8px;
  background: var(--panel-raised);
  color: var(--ink);
  font: inherit;
  font-size: 13.5px;
  font-weight: 600;
  cursor: pointer;
  /* 44px is Apple's floor for a comfortable touch target and Android's is 48dp;
     44 clears both once the row's own padding is counted, and an Activity is
     played on a phone as often as not. */
  min-height: 44px;
  /* No double-tap-to-zoom delay: a 300ms wait on the only button on the card
     reads as the tap not having registered. */
  touch-action: manipulation;
  transition:
    background 120ms ease,
    border-color 120ms ease;
}

/* HOVER ONLY WHERE THERE IS A POINTER. On a touch screen `:hover` sticks after
   a tap, so the last button pressed stays lit as though still focused — which is
   exactly the "controls work as expected for the platform" failure. The pressed
   state below is what a phone gets instead, and it is immediate. */
@media (hover: hover) and (pointer: fine) {
  button.primary:hover:not([disabled]) {
    background: #313140;
    border-color: #454555;
  }
}

button.primary:active:not([disabled]) {
  background: #3a3a4a;
  transform: translateY(1px);
}

button.primary[disabled] {
  opacity: 0.55;
  cursor: default;
}

/* What the last write said. The server composes these sentences — they are the
   same strings the slash commands reply with — so the client never writes its
   own account of what happened. */
.notice {
  margin: 18px 0 0;
  padding: 11px 14px;
  border-radius: 9px;
  background: var(--panel);
  border: 1px solid var(--line);
  border-left: 3px solid var(--uncommon);
  font-size: 13px;
  line-height: 1.5;
  white-space: pre-wrap;
}

.notice.bad {
  border-left-color: var(--hp);
}

/* ── The fight ────────────────────────────────────────────────────────────────
   What replaced the PNG pipeline. The bot composes a palette image per beat —
   hand-rolled colour-type-3 encoder, cached base plane, reported dirty
   rectangles, a bitmap font for the damage numbers — because uploading a picture
   is its only way to show one. Here it is divs, repainted for free. */
.fight {
  /* Still a positioning context for anything the fight block owns, but the sound
     controls are no longer among them: a slider cannot be rebuilt every beat and
     survive a drag, so `.audio-bar` is mounted on the body and floats over the
     frame instead. See `audioBar` in `client/main.js`. */
  position: relative;
  margin-top: 13px;
  padding-top: 13px;
  border-top: 1px solid var(--line);
}

/* THE SOUND CONTROLS. Fixed to the frame rather than to the fight block, because
   `render` replaces the fight block on every beat and a drag does not survive
   being removed from the document. Bottom-right rather than the stage corner
   they used to sit in: nothing else is down there, it clears the header and the
   purse, and on a phone it is the corner a thumb already rests in. */
.audio-bar {
  position: fixed;
  right: calc(8px + var(--safe-right));
  bottom: calc(8px + var(--safe-bottom));
  z-index: 3;
  display: flex;
  align-items: center;
  gap: 8px;
  padding: 4px 8px;
  background: rgb(22 22 28 / 82%);
  border: 1px solid var(--line);
  border-radius: 8px;
}

/* Hidden while no fight is on the stage. `display: none` rather than an opacity,
   so it takes no taps meant for the page under it. The element itself is never
   removed — that is the whole point of it living out here. */
.audio-bar.hidden {
  display: none;
}

/* THE MUTE: legible over whatever the backdrop is doing, and big enough to hit.
   An Activity runs in a voice channel, so this gets a real 44px target rather
   than the icon-sized one a desktop game would use. */
.audio-bar .mute {
  /* 44px, not the icon-sized control a desktop game would use: `client-mobile
     .test.js` asserts the floor for every button here, and a control the player
     reaches for mid-fight on a phone is the last one to make an exception of. */
  min-height: 44px;
  padding: 0 12px;
  font-size: 11px;
  line-height: 1;
  color: var(--ink);
  background: transparent;
  border: 1px solid var(--line);
  border-radius: 6px;
  cursor: pointer;
  touch-action: manipulation;
}

.audio-bar .mute.off {
  opacity: 0.72;
  text-decoration: line-through;
}

/* THE VOLUME. The same 44px rule as the button: a slider is if anything harder
   to grab than a button, so the TRACK gets the height and the visible bar is
   drawn inside it. `-webkit-appearance: none` is what lets the thumb be styled
   at all in Chromium, which is what Discord's client is. */
.audio-bar .volume {
  -webkit-appearance: none;
  appearance: none;
  width: 96px;
  min-height: 44px;
  background: transparent;
  cursor: pointer;
  touch-action: manipulation;
}

.audio-bar .volume::-webkit-slider-runnable-track {
  height: 4px;
  background: var(--line);
  border-radius: 2px;
}

.audio-bar .volume::-moz-range-track {
  height: 4px;
  background: var(--line);
  border-radius: 2px;
}

/* The thumb is 18px drawn and the whole 44px track is grabbable, so the small
   dot costs nothing in reach. The negative margin centres it on a 4px track,
   which `-webkit-slider-thumb` does not do for you. */
.audio-bar .volume::-webkit-slider-thumb {
  -webkit-appearance: none;
  appearance: none;
  width: 18px;
  height: 18px;
  margin-top: -7px;
  background: var(--ink);
  border: none;
  border-radius: 50%;
}

.audio-bar .volume::-moz-range-thumb {
  width: 18px;
  height: 18px;
  background: var(--ink);
  border: none;
  border-radius: 50%;
}

.fight .sides {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: 14px;
  margin-bottom: 10px;
}

.fight .who {
  font-size: 12px;
  font-weight: 600;
  color: var(--ink-dim);
  margin-bottom: 5px;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}

.fight .bar {
  height: 8px;
  border-radius: 4px;
  background: var(--panel-raised);
  overflow: hidden;
}

.fight .bar .fill {
  height: 100%;
  background: var(--uncommon);
  /* Faster than the beat period, so the bar has settled before the next event
     lands. A transition longer than a beat reads as lag rather than motion. */
  transition: width 180ms linear;
}

.fight .bar.foe .fill {
  background: var(--hp);
}

.fight .num {
  margin-top: 4px;
  font-size: 11px;
  font-variant-numeric: tabular-nums;
  color: var(--ink-dim);
}

/* THE FLINCH. A scene shows both figures on one frame, which is why the bot
   draws the blow and the flinch together; with no figures to draw, the side that
   took the hit is what moves. */
.fight .side.hit {
  animation: flinch 200ms ease-out;
}

@keyframes flinch {
  0% {
    transform: translateX(0);
  }
  40% {
    transform: translateX(3px);
  }
  100% {
    transform: translateX(0);
  }
}

@media (prefers-reduced-motion: reduce) {
  .fight .side.hit {
    animation: none;
  }
  .fight .bar .fill {
    transition: none;
  }
}

/* ── The scene ───────────────────────────────────────────────────────────── */

/* THE SAME ART THE BOT DRAWS, AT THE SAME PROPORTIONS. `aspect-ratio` pins the
   stage to the bot's 720x380 canvas, which is what lets `scene.js` place every
   figure as a FRACTION of that canvas using the manifest's own anchor and scale.
   Sized by percentage rather than by pixels, so the picture is whatever width the
   phone gives the card and the geometry never has to be re-derived. */
.stage {
  position: relative;
  aspect-ratio: 720 / 380;
  width: 100%;
  overflow: hidden;
  border-radius: 8px;
  border: 1px solid var(--line);
  background: #80c2ce; /* layer5's own top row, so a slow backdrop is not black */
  margin-bottom: 9px;
}

/* Pixel art, scaled up. Smoothing softens every edge in it, which is the same
   reason the bot's compositor places layers by corner instead of resampling. */
.stage img {
  image-rendering: pixelated;
  -webkit-user-select: none;
  user-select: none;
  pointer-events: none;
}

/* A FLOATING NUMBER, AND IT IS THE ONE THING HERE THE BOT CANNOT DO CHEAPLY:
   its version is a 13x15 bitmap font recoloured by an exact three-colour palette
   swap, with a FIXED advance so a number does not slide sideways when the damage
   crosses 99. `tabular-nums` buys the same property for free. The palettes are
   the scene's own: orange for a crit, purple for poison (never green — green is
   what every game a player arrived from uses for healing), blue for a ward
   absorb, and the coolest, flattest one for a dodge, which is the only popup
   that is a word. */
.stage .popup {
  position: absolute;
  top: 12%;
  transform: translateX(-50%);
  font-size: clamp(16px, 5.5vw, 30px);
  font-weight: 800;
  font-variant-numeric: tabular-nums;
  letter-spacing: 0.02em;
  color: var(--ink);
  text-shadow:
    0 2px 0 rgba(0, 0, 0, 0.55),
    0 0 6px rgba(0, 0, 0, 0.4);
  animation: rise 420ms ease-out both;
}

.stage .popup.crit {
  color: var(--legendary);
  font-size: clamp(20px, 7vw, 38px);
}
.stage .popup.poison {
  color: #bb66dd;
}
.stage .popup.absorbed {
  color: #66aaee;
}
.stage .popup.burn {
  color: #ee8844;
}
.stage .popup.dodge {
  color: #cdd6e0;
  font-size: clamp(13px, 4vw, 22px);
  letter-spacing: 0.08em;
}

@keyframes rise {
  from {
    opacity: 0;
    transform: translate(-50%, 10px);
  }
  to {
    opacity: 1;
    transform: translate(-50%, -6px);
  }
}

@media (prefers-reduced-motion: reduce) {
  .stage .popup {
    animation: none;
  }
}

.fight .narration {
  font-size: 12.5px;
  line-height: 1.5;
  color: var(--ink-dim);
  /* Fixed height, so the card does not jump as lines arrive and leave — the
     same reason the bot's damage numbers use a FIXED advance rather than a
     proportional one. */
  min-height: 74px;
}

.fight .payout {
  margin-top: 8px;
  font-size: 12.5px;
  font-weight: 600;
  color: var(--gold);
}

.fight .tally {
  margin: 9px 0 11px;
  font-size: 11.5px;
  color: var(--ink-dim);
  font-variant-numeric: tabular-nums;
}

/* ── Choosing a creature ─────────────────────────────────────────────────── */
.picker {
  margin-top: 13px;
  padding-top: 13px;
  border-top: 1px solid var(--line);
}

.picker .row {
  font-size: 12px;
  color: var(--ink-dim);
  margin-bottom: 8px;
}

.picker .monsters {
  display: grid;
  gap: 6px;
  margin-bottom: 10px;
}

button.monster {
  display: flex;
  justify-content: space-between;
  align-items: center;
  gap: 10px;
  min-height: 44px;
  padding: 8px 12px;
  border: 1px solid var(--line);
  border-radius: 8px;
  background: var(--panel-raised);
  color: var(--ink);
  font: inherit;
  font-size: 13px;
  cursor: pointer;
  touch-action: manipulation;
}

/* LOCKED IS SHOWN, NOT HIDDEN. A ladder with holes in it does not read as a
   ladder: a player should see what is next rather than wonder if it exists. */
button.monster[disabled] {
  opacity: 0.4;
  cursor: default;
}

button.monster .mlevel {
  font-variant-numeric: tabular-nums;
  color: var(--ink-dim);
  font-size: 12px;
}

button.primary.subtle {
  background: transparent;
  font-weight: 500;
  color: var(--ink-dim);
  margin-top: 6px;
}

.creation {
  display: grid;
  gap: 12px;
  margin-top: 18px;
}

.creation h2 {
  margin: 0;
  font-size: 18px;
}

.creation input,
.creation select {
  width: 100%;
  min-width: 0;
  min-height: 44px;
  padding: 8px;
  border: 1px solid var(--line);
  border-radius: 8px;
  background: var(--panel-raised);
  color: var(--ink);
  font: inherit;
}

nav {
  display: flex;
  gap: 8px;
  flex-wrap: wrap;
  margin-bottom: 18px;
}

nav [aria-current='page'] {
  color: var(--gold);
  border-color: var(--gold);
}

.muted {
  color: var(--ink-dim);
}

.equipment,
.abilities {
  margin: 16px 0;
}

.equipment .row {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 12px;
  padding: 6px 0;
}

.ability {
  padding: 12px 0;
  border-bottom: 1px solid var(--line);
}

summary {
  cursor: pointer;
  padding: 12px 0;
}

select {
  max-width: 100%;
  min-height: 44px;
  background: var(--panel-raised);
  color: var(--ink);
  border: 1px solid var(--line);
  border-radius: 8px;
  padding: 8px;
  font: inherit;
}

button.primary + button.primary {
  margin-top: 6px;
}

h2,
h3,
.card .name,
.character-tabs button,
nav button.primary {
  font-family: 'Sigil Pixel', monospace;
  font-weight: normal;
}
h2 {
  font-size: 32px;
  margin: 18px 0 12px;
}
h3,
.card .name {
  font-size: 24px;
}
h3 {
  margin: 8px 0;
}
p {
  line-height: 1.55;
}
button,
input,
select {
  touch-action: manipulation;
}
button:focus-visible,
input:focus-visible,
select:focus-visible,
summary:focus-visible {
  outline: 3px solid #f0c978;
  outline-offset: 3px;
}
button {
  cursor: pointer;
}
button:disabled {
  cursor: default;
  opacity: 0.55;
}
button.primary {
  border-radius: 3px;
}
nav {
  display: grid;
  grid-template-columns: repeat(4, minmax(0, 1fr));
  gap: 6px;
  padding: 6px;
  background: #101b18;
  border: 1px solid var(--line);
  border-radius: 3px;
}
nav button.primary.subtle {
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 9px;
  font-size: 24px;
  margin: 0;
  padding: 10px 4px;
  color: var(--ink-dim);
}
nav button[aria-current='page'] {
  background: #354238;
  color: #ffe1a1;
  border-color: var(--gold);
  box-shadow: inset 0 -3px var(--gold);
}
.item-icon {
  width: 32px;
  height: 32px;
  object-fit: contain;
  image-rendering: pixelated;
  flex-shrink: 0;
}
.item-heading {
  display: flex;
  align-items: center;
  gap: 12px;
  min-height: 48px;
}
.bag-grid {
  position: relative;
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(64px, 1fr));
  gap: 8px;
  margin: 16px 0;
}
.paper-doll {
  position: relative;
  width: min(100%, 360px);
  aspect-ratio: 300 / 330;
  margin: 16px auto;
  background: linear-gradient(180deg, var(--panel), #101918 94%, #283631 94%);
  border: 1px solid var(--line);
}
.paper-doll-figure {
  position: absolute;
  image-rendering: pixelated;
  pointer-events: none;
}
.paper-doll-slot {
  position: absolute;
  width: 24%;
  min-height: 64px;
  padding: 4px;
  background: var(--panel);
  border: 1px solid var(--line);
  color: var(--ink);
  font: inherit;
  cursor: pointer;
  touch-action: manipulation;
}
.paper-doll-slot span,
.paper-doll-slot small {
  display: block;
  font-size: 11px;
  line-height: 1.3;
}
.paper-doll-slot small {
  color: var(--ink-dim);
  overflow: hidden;
  white-space: nowrap;
  text-overflow: ellipsis;
}
.paper-doll-slot:focus-visible,
.paper-doll-slot[aria-pressed='true'] {
  outline: 2px solid var(--gold);
  outline-offset: 2px;
}
.paper-doll-slot[data-gear-slot='helm'] {
  left: 3%;
  top: 2%;
}
.paper-doll-slot[data-gear-slot='weapon'] {
  left: 3%;
  top: 26%;
}
.paper-doll-slot[data-gear-slot='offhand'] {
  left: 3%;
  top: 50%;
}
.paper-doll-slot[data-gear-slot='boots'] {
  left: 3%;
  top: 74%;
}
.paper-doll-slot[data-gear-slot='armor'] {
  right: 3%;
  top: 14%;
}
.paper-doll-slot[data-gear-slot='trinket'] {
  right: 3%;
  top: 38%;
}
.paper-doll-slot[data-gear-slot='ring'] {
  right: 3%;
  top: 62%;
}
.paper-doll-slot[data-rarity='uncommon'] {
  border-color: var(--uncommon);
}
.paper-doll-slot[data-rarity='rare'] {
  border-color: #87baf0;
}
.paper-doll-slot[data-rarity='epic'] {
  border-color: #cd9bf1;
}
.paper-doll-slot[data-rarity='legendary'] {
  border-color: #f4b767;
}
.bag-item {
  min-width: 0;
}
.bag-cell {
  min-width: 0;
}
.bag-select {
  width: 24px;
  height: 24px;
  margin: 0 0 4px;
  accent-color: var(--gold);
}
.bag-actions {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 8px;
  margin-top: 16px;
}
.bag-icon {
  position: relative;
  display: flex;
  align-items: center;
  justify-content: center;
  min-height: 76px;
  padding: 12px 4px 20px;
  border: 1px solid var(--line);
  background: var(--panel);
  cursor: pointer;
  list-style: none;
  touch-action: manipulation;
}
.bag-icon::-webkit-details-marker {
  display: none;
}
.bag-icon:focus-visible,
.bag-item[open] > .bag-icon {
  outline: 2px solid var(--gold);
  outline-offset: 2px;
}
.bag-icon[data-rarity='uncommon'] {
  border-color: var(--uncommon);
}
.bag-icon[data-rarity='rare'] {
  border-color: #87baf0;
}
.bag-icon[data-rarity='epic'] {
  border-color: #cd9bf1;
}
.bag-icon[data-rarity='legendary'] {
  border-color: #f4b767;
}
.bag-icon .item-icon {
  width: 40px;
  height: 40px;
}
.bag-icon-name {
  font-size: 11px;
  overflow-wrap: anywhere;
  text-align: center;
}
.bag-level {
  position: absolute;
  right: 4px;
  top: 2px;
  font-size: 11px;
  color: var(--ink-dim);
}
.bag-status {
  position: absolute;
  bottom: 2px;
  font-size: 10px;
  color: var(--gold);
}
.bag-item-card {
  position: absolute;
  left: 0;
  width: min(360px, 100%);
  z-index: 5;
  overflow-wrap: anywhere;
}
.item-heading .item-icon {
  width: 48px;
  height: 48px;
  padding: 8px;
  background: #111d19;
  border: 1px solid var(--line);
}
.item-heading[data-rarity='uncommon'] h3 {
  color: var(--uncommon);
}
.item-heading[data-rarity='rare'] h3 {
  color: #87baf0;
}
.item-heading[data-rarity='epic'] h3 {
  color: #cd9bf1;
}
.item-heading[data-rarity='legendary'] h3 {
  color: #f4b767;
}
.coin-icon {
  display: inline-block;
  width: 32px;
  height: 32px;
  background: url('/assets/Gandalf%20Assets/GandalfHardcore%20Emojis%20and%20Icons/Coin.png') 0 0 /
    384px 32px;
  image-rendering: pixelated;
  flex-shrink: 0;
}
.purse {
  display: flex;
  align-items: center;
  white-space: nowrap;
}
.camp-banner {
  padding: 24px;
  margin-bottom: 20px;
  border: 1px solid #718068;
  background:
    linear-gradient(90deg, #12241aee, #12241a88),
    url('/assets/backgrounds/pine-forest/layer1.png') center 65% / cover,
    #405c4c;
  border-radius: 3px;
}
.camp-banner h2 {
  font-size: 40px;
  margin: 4px 0;
  color: #ffe1a1;
}
.camp-banner p {
  max-width: 540px;
  margin: 6px 0;
  font-size: 14px;
}
.camp-banner .eyebrow {
  letter-spacing: 0.18em;
  font-size: 11px;
  color: #d4ddbc;
}
.portrait {
  display: block;
  width: 100%;
  image-rendering: pixelated;
  object-fit: cover;
}
.character-card > .portrait {
  height: auto;
  aspect-ratio: 720 / 380;
  margin-bottom: 16px;
  border: 1px solid var(--line);
}

img[hidden] {
  display: none;
}
.character-card > header .name {
  overflow-wrap: anywhere;
}
.character-card .hunt > button:first-of-type {
  background: #53613a;
  border-color: #9d9e60;
  color: #fff4ce;
}
.character-tabs {
  display: grid;
  grid-template-columns: repeat(3, minmax(0, 1fr));
  gap: 4px;
  margin-top: 18px;
}
.character-tabs button,
.class-choices button,
.character-choice {
  min-height: 44px;
  border: 1px solid var(--line);
  border-radius: 3px;
  background: #16221e;
  color: var(--ink-dim);
  padding: 8px;
  font: inherit;
}
.character-tabs button {
  font-family: 'Sigil Pixel', monospace;
  font-size: 24px;
  padding: 8px 2px;
}
[aria-pressed='true'] {
  border-color: var(--gold) !important;
  color: #ffe1a1 !important;
  background: #364132 !important;
}
.class-choices,
.character-choices {
  display: grid;
  grid-template-columns: repeat(3, minmax(0, 1fr));
  gap: 10px;
}
.class-choices button {
  display: flex;
  flex-direction: column;
  gap: 8px;
  text-align: left;
}
.class-choices .portrait {
  height: 100px;
}
.class-choices strong {
  font-family: 'Sigil Pixel', monospace;
  font-size: 24px;
}
.class-choices small {
  font-size: 12px;
  line-height: 1.5;
}
.character-choices {
  margin: 16px 0;
}
.character-choice {
  overflow: hidden;
  overflow-wrap: anywhere;
}
.character-choice .portrait {
  height: 80px;
  margin-bottom: 8px;
}
.character-choice small {
  display: block;
  margin-top: 5px;
}
.equipment {
  display: grid;
  grid-template-columns: repeat(2, minmax(0, 1fr));
  gap: 10px;
}
.equipment .row {
  flex-wrap: wrap;
  border: 1px solid var(--line);
  padding: 12px;
  background: var(--panel);
}
.equipment .row span {
  flex: 1;
  font-size: 13px;
}
.skills {
  display: flex;
  flex-wrap: wrap;
  gap: 8px;
  margin: 16px 0;
}
.skills button {
  min-height: 44px;
  margin: 0;
  padding: 8px 12px;
  border: 1px solid var(--line);
  background: var(--panel);
  color: var(--ink);
  font: inherit;
  font-size: 13px;
}
.materials,
.crops,
.profession {
  margin-top: 20px;
}
.materials p {
  display: inline-block;
  margin: 5px 16px 5px 0;
  font-size: 13px;
}
.roster {
  margin: 16px 0;
  align-items: start;
}
.notice {
  margin: 0 0 16px;
}
.ability p,
.talents p {
  font-size: 13px;
}
.creation h2 {
  font-size: 32px;
}
@media (max-width: 480px) {
  nav button.primary.subtle {
    flex-direction: column;
    gap: 3px;
    font-size: 20px;
  }
  nav .item-icon {
    width: 24px;
    height: 24px;
  }
  .camp-banner {
    padding: 16px;
  }
  .camp-banner h2 {
    font-size: 32px;
  }
  .class-choices {
    gap: 6px;
  }
  .class-choices .portrait {
    height: 76px;
  }
  .class-choices small {
    font-size: 11px;
  }
  .equipment {
    grid-template-columns: 1fr;
  }
  header.account h1 {
    font-size: 40px;
  }
  header.account .purse {
    font-size: 13px;
  }
}
@media (prefers-reduced-motion: reduce) {
  *,
  *::before,
  *::after {
    animation: none !important;
    transition: none !important;
  }
}
