/* ==========================================================================
   Snowbird Hunt — a field journal for an alpine mystery.

   Phone-first. Desktop is not a target; wide screens get the same column,
   centred, so nothing stretches into nonsense.

   The whole interface is one conceit: you are carrying a weathered logbook
   up a mountain. Clues are written into it, and every code you scan lands
   as an inked cancellation stamp, the way a national-park passport works.
   The page fills with evidence you were there.

   Colour comes from the mountain — shadowed snow, spruce, wet rock, and the
   oxidised red of a stamp pad. Stamp red appears ONLY on stamps and on the
   single most important action of a screen. That restraint is what makes a
   stamp feel earned.
   ========================================================================== */

:root {
    --ink: #1b2a33;
    --ink-soft: #3d4d57;
    --rock: #7c746a;
    --paper: #e9e5da;
    --paper-raised: #f2efe6;
    --paper-well: #ddd7c8;

    --snow-shadow: #9fb6c4;
    --spruce: #2e4739;

    /* Reserved for stamps and the one action that matters. */
    --stamp: #b33f2c;

    --rule: #c9c2b1;

    --shell: 30rem;
    --gutter: 1.15rem;
    --bar-height: 4.25rem;
    --tap: 2.75rem;

    --radius: 3px;
    --radius-card: 5px;

    --font-display: "Barlow Condensed", "Arial Narrow", sans-serif;
    --font-body: "Bitter", Georgia, "Times New Roman", serif;
    --font-hand: "Caveat", "Segoe Script", cursive;
}

*,
*::before,
*::after {
    box-sizing: border-box;
}

html {
    -webkit-text-size-adjust: 100%;
}

body {
    margin: 0;
    font-family: var(--font-body);
    font-size: 1rem;
    line-height: 1.55;
    color: var(--ink);
    background-color: var(--paper);
    /* Paper grain, inlined so nothing is fetched on a bad signal. */
    background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='180' height='180'%3E%3Cfilter id='n'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='0.85' numOctaves='3'/%3E%3C/filter%3E%3Crect width='180' height='180' filter='url(%23n)' opacity='0.05'/%3E%3C/svg%3E");
}

main {
    display: block;
    max-width: var(--shell);
    margin: 0 auto;
    padding: 0 var(--gutter) calc(var(--bar-height) + env(safe-area-inset-bottom, 0px) + 1.5rem);
}

img {
    max-width: 100%;
    display: block;
}

/* ==========================================================================
   Type
   ========================================================================== */

h1,
h2,
h3 {
    font-family: var(--font-display);
    font-weight: 600;
    letter-spacing: 0.01em;
    line-height: 1.08;
    margin: 0;
    text-wrap: balance;
}

h1 { font-size: 2.1rem; }
h2 { font-size: 1.35rem; }
h3 { font-size: 1.05rem; }

p { margin: 0 0 0.85rem; }
p:last-child { margin-bottom: 0; }

a {
    color: var(--ink);
    text-underline-offset: 0.18em;
}

/* The printed furniture of the logbook. */
.eyebrow {
    font-family: var(--font-display);
    font-size: 0.75rem;
    font-weight: 600;
    letter-spacing: 0.16em;
    text-transform: uppercase;
    color: var(--rock);
    margin: 0 0 0.3rem;
}

.hand {
    font-family: var(--font-hand);
    font-size: 1.2rem;
    line-height: 1.35;
    color: var(--ink-soft);
}

.lede { font-size: 1.05rem; color: var(--ink-soft); }
.muted { color: var(--rock); }

/* ==========================================================================
   Masthead
   ========================================================================== */

/* The top bar rides along.
   Your points, your coins, your bag and the mute button are all up here, and
   every one of them is something you reach for mid-screen rather than after
   scrolling back. Sticky rather than fixed, so it still takes its own space
   at the top of the page and nothing has to be padded around it. */
.bar {
    position: sticky;
    top: 0;
    z-index: 25;
    max-width: var(--shell);
    margin: 0 auto;
    padding: 1.1rem var(--gutter) 0.9rem;
    display: flex;
    align-items: flex-start;
    justify-content: space-between;
    gap: 1rem;
    /* Opaque, or the page reads straight through it as it scrolls under. */
    background: var(--paper);
    border-bottom: 1px solid var(--rule);
}

.bar .brand {
    font-family: var(--font-display);
    font-size: 1.15rem;
    font-weight: 700;
    letter-spacing: 0.13em;
    text-transform: uppercase;
    text-decoration: none;
    color: var(--ink);
    line-height: 1;
}

.bar .brand span {
    display: block;
    font-size: 0.68rem;
    font-weight: 500;
    letter-spacing: 0.2em;
    color: var(--rock);
    margin-top: 0.28rem;
}

.bar form { margin: 0; }

.bar button {
    background: none;
    border: 0;
    padding: 0.35rem 0;
    min-height: auto;
    font-family: var(--font-display);
    font-size: 0.75rem;
    font-weight: 600;
    letter-spacing: 0.14em;
    text-transform: uppercase;
    color: var(--rock);
    cursor: pointer;
}

.rule {
    max-width: var(--shell);
    margin: 0 auto 1.35rem;
    border: 0;
    border-top: 1px solid var(--rule);
    border-bottom: 1px solid var(--rule);
    height: 3px;
}

.barside {
    display: flex;
    flex-direction: column;
    align-items: flex-end;
    gap: 0.35rem;
}

.barside__admin {
    font-family: var(--font-display);
    font-size: 0.75rem;
    font-weight: 600;
    letter-spacing: 0.14em;
    text-transform: uppercase;
    color: var(--stamp);
    text-decoration: none;
}

/* Always within a thumb's reach, because the first embarrassing chirp in a
   restaurant decides whether this app keeps its sound on for the weekend. */
/* The corner tools: standings, bag, sound. Things you glance at from
   wherever you are, as opposed to the bottom bar, which is places you go. */
.bar__tools {
    display: flex;
    align-items: center;
    /* Three discs 2px apart read as one lumpy control. They are separate
       things and want visible air between them. */
    gap: 0.5rem;
    flex-shrink: 0;
}

/* All three corner tools are the same object: a disc with an icon in it.
   One of them having a background and the others not made the row look
   unevenly spaced even though the boxes were identical. */
.bar__tools .bartool,
.bar__tools .mutebtn {
    position: relative;
    display: grid;
    place-items: center;
    width: 2.4rem;
    height: 2.4rem;
    padding: 0;
    min-height: 0;
    background: var(--paper-raised);
    border: 1px solid var(--line, var(--rule));
    border-radius: 50%;
    box-shadow: 0 1px 2px rgba(31, 26, 20, 0.08);
    color: var(--ink);
    cursor: pointer;
    text-decoration: none;
}

/* No filled pill on the current page. A tinted circle behind one of three
   corner icons read as a button that was stuck down rather than as "you are
   here"; the darker ink says it without the box. */
.bar__tools .bartool[aria-current="page"] {
    color: var(--ink);
}

/* The bag is the one you reach for mid-scene, so it gets a disc to sit on
   and the other two do not. It is the difference between an icon you press
   and an icon you glance at. */
/* The disc is on every tool now, so the bag needs nothing of its own beyond
   somewhere to hang its count. */

/* How much is in there, without opening it. */
.bar__tools .bartool__count {
    position: absolute;
    top: -0.2rem;
    right: -0.2rem;
    min-width: 1.05rem;
    height: 1.05rem;
    padding: 0 0.2rem;
    display: grid;
    place-items: center;
    border-radius: 999px;
    background: var(--stamp);
    color: #fff;
    font-family: var(--font-display);
    font-size: 0.62rem;
    font-weight: 700;
    font-variant-numeric: tabular-nums;
    line-height: 1;
}

/* Filtering the bag. A full bag is thirty tiles, and finding the one potion
   in it meant reading all of them. */
.bagfilter {
    display: flex;
    flex-wrap: wrap;
    gap: 0.35rem;
    margin: 0 0 1rem;
}

.bagfilter__tab {
    min-height: 0;
    padding: 0.35rem 0.7rem;
    background: var(--paper-well);
    border: 1px solid transparent;
    border-radius: 999px;
    color: var(--ink-soft);
    font-family: var(--font-display);
    font-size: 0.72rem;
    font-weight: 600;
    letter-spacing: 0.06em;
    text-transform: uppercase;
    cursor: pointer;
}

.bagfilter__tab span {
    margin-left: 0.25rem;
    opacity: 0.65;
    font-variant-numeric: tabular-nums;
}

.bagfilter__tab.is-on {
    background: var(--ink);
    border-color: var(--ink);
    color: var(--paper);
}

/* Every icon the interface draws for itself. An organizer can upload their
   own for any of these on the Icons page; when they do, the same class lands
   on an <img> instead and the sizing has to hold either way. */
.navico {
    width: 1.25rem;
    height: 1.25rem;
    display: block;
    stroke: currentColor;
    fill: none;
    stroke-width: 1.7;
    stroke-linecap: round;
    stroke-linejoin: round;
}

/* Uploaded artwork is a picture, not a line drawing: it brings its own
   colour, so it must not be handed a stroke, and it is fitted rather than
   stretched. */
.navico--art,
.coin--art,
.pack--art {
    stroke: none;
    fill: none;
    object-fit: contain;
}

.bar__tools .bartool svg,
.bar__tools .bartool .navico,
.mutebtn svg,
.mutebtn .navico {
    width: 1.25rem;
    height: 1.25rem;
    display: block;
    stroke: currentColor;
    fill: none;
    stroke-width: 1.7;
    stroke-linecap: round;
    stroke-linejoin: round;
}

.bar__tools .bartool .navico--art,
.mutebtn .navico--art {
    stroke: none;
}

/* The bag, wherever it is named. */
.sheet__title {
    display: flex;
    align-items: center;
    gap: 0.5rem;
}

.sheet__title .pack {
    width: 1.15em;
    height: 1.15em;
    flex-shrink: 0;
    fill: none;
    stroke: currentColor;
    stroke-width: 1.7;
    stroke-linecap: round;
    stroke-linejoin: round;
}

.mutebtn[aria-pressed="true"] {
    color: var(--stamp);
}

/* Visually hidden, and it has to stay hidden.
 *
 * These are !important because this class is applied to form controls, and
 * the element selectors further down (input[type="file"], select, textarea)
 * are more specific than a single class. A hidden file input was winning
 * `width: 100%` from one of them, sitting 375px wide off the side of the
 * page, and giving the whole profile screen a horizontal scrollbar. A
 * utility whose one job is "take this out of the layout" cannot be
 * overridable by a component rule. */
.sr {
    position: absolute !important;
    width: 1px !important;
    height: 1px !important;
    min-height: 0 !important;
    padding: 0 !important;
    margin: -1px !important;
    overflow: hidden;
    clip: rect(0, 0, 0, 0);
    clip-path: inset(50%);
    white-space: nowrap;
    border: 0 !important;
}

/* ==========================================================================
   Cards
   ========================================================================== */

.card {
    background: var(--paper-raised);
    border: 1px solid var(--rule);
    border-radius: var(--radius-card);
    padding: 1.1rem;
    margin: 0 0 1.1rem;
}

.card > :last-child { margin-bottom: 0; }

/* ==========================================================================
   Buttons
   ========================================================================== */

button,
.btn {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    gap: 0.5rem;
    min-height: var(--tap);
    padding: 0.7rem 1.2rem;
    font-family: var(--font-display);
    font-size: 0.95rem;
    font-weight: 600;
    letter-spacing: 0.1em;
    text-transform: uppercase;
    text-decoration: none;
    color: var(--paper-raised);
    background: var(--ink);
    border: 1px solid var(--ink);
    border-radius: var(--radius);
    cursor: pointer;
    transition: transform 0.12s ease;
}

button:active,
.btn:active { transform: translateY(1px); }

/* The one action that matters. Never more than one per screen. */
.btn--stamp {
    background: var(--stamp);
    border-color: var(--stamp);
}

/* Gaining something is always green, wherever it happens — bought, crafted,
   won off a quest. One colour for one meaning, so "you now have this" is
   recognised before the label is read. */
.btn--get {
    background: var(--spruce);
    border-color: var(--spruce);
    color: #fff;
}

.btn--quiet {
    background: transparent;
    color: var(--ink);
    border-color: var(--rule);
}

.btn--block {
    display: flex;
    width: 100%;
}

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

/* ==========================================================================
   Forms
   ========================================================================== */

label {
    display: block;
    font-family: var(--font-display);
    font-size: 0.78rem;
    font-weight: 600;
    letter-spacing: 0.13em;
    text-transform: uppercase;
    color: var(--rock);
    margin: 0.9rem 0 0.35rem;
}

/* input:not([type]) matters more than it looks. An <input> with no type
   attribute behaves as a text box, but an attribute selector only matches an
   attribute that is literally written out — so input[type="text"] skips it and
   the field renders completely unstyled. Twenty inputs in this app omit the
   attribute, including the login username, the code-entry box and the setup
   display name, so leaving it out means a raw browser input sitting directly
   beside a styled one. */
input:not([type]),
input[type="text"],
input[type="password"],
input[type="number"],
input[type="file"],
textarea,
select {
    width: 100%;
    min-height: var(--tap);
    padding: 0.6rem 0.7rem;
    font-family: var(--font-body);
    font-size: 1rem;
    color: var(--ink);
    background: var(--paper);
    border: 1px solid var(--rule);
    border-radius: var(--radius);
}

textarea {
    min-height: 6rem;
    line-height: 1.5;
    resize: vertical;
}

input:focus,
textarea:focus,
select:focus {
    border-color: var(--ink-soft);
    outline: 2px solid var(--stamp);
    outline-offset: 1px;
}

.checkline {
    display: flex;
    align-items: center;
    gap: 0.6rem;
    margin: 0.9rem 0 0;
    font-family: var(--font-body);
    font-size: 0.95rem;
    letter-spacing: 0;
    text-transform: none;
    color: var(--ink);
}

.checkline input {
    width: 1.15rem;
    height: 1.15rem;
    min-height: 0;
    accent-color: var(--stamp);
}

.error {
    margin: 0.4rem 0 0;
    font-size: 0.9rem;
    color: var(--stamp);
}

.status {
    margin: 0 0 1rem;
    padding: 0.7rem 0.85rem;
    font-size: 0.95rem;
    background: var(--paper-raised);
    border-left: 3px solid var(--spruce);
    border-radius: var(--radius);
}

/* ==========================================================================
   THE SIGNATURE — cancellation stamps

   Every found location leaves one: slightly rotated, edges eroded by a noise
   mask so no two look machine-perfect, multiplied into the paper so the
   grain shows through the ink.
   ========================================================================== */

.stamp {
    --tilt: -5deg;
    position: relative;
    display: flex;
    flex-direction: column;
    align-items: center;
    justify-content: center;
    gap: 0.1rem;
    padding: 0.7rem 0.55rem 0.6rem;
    text-align: center;
    color: var(--stamp);
    border: 2.5px solid currentColor;
    border-radius: 4px;
    transform: rotate(var(--tilt));
    mix-blend-mode: multiply;
    opacity: 0.9;
    /* Erode the ink so the impression reads as pressed, not printed. */
    -webkit-mask-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='140' height='140'%3E%3Cfilter id='e'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='0.5' numOctaves='4'/%3E%3CfeColorMatrix type='matrix' values='0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 -1.7 1.3'/%3E%3C/filter%3E%3Crect width='140' height='140' filter='url(%23e)'/%3E%3C/svg%3E");
    mask-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='140' height='140'%3E%3Cfilter id='e'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='0.5' numOctaves='4'/%3E%3CfeColorMatrix type='matrix' values='0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 -1.7 1.3'/%3E%3C/filter%3E%3C/svg%3E");
    -webkit-mask-size: 140px;
    mask-size: 140px;
}

.stamp::before {
    content: "";
    position: absolute;
    inset: 3px;
    border: 1px solid currentColor;
    border-radius: 2px;
    opacity: 0.55;
}

.stamp__name {
    font-family: var(--font-display);
    font-size: 0.95rem;
    font-weight: 700;
    letter-spacing: 0.08em;
    text-transform: uppercase;
    line-height: 1.05;
}

.stamp__bird {
    width: 1.4rem;
    height: auto;
    margin: 0.12rem 0;
    opacity: 0.85;
}

.stamp__date {
    font-family: var(--font-display);
    font-size: 0.65rem;
    font-weight: 600;
    letter-spacing: 0.16em;
    text-transform: uppercase;
}

/* Rotations vary so a filled page never looks gridded. */
.stamp--a { --tilt: -6deg; }
.stamp--b { --tilt: 4deg; }
.stamp--c { --tilt: -2.5deg; }
.stamp--d { --tilt: 5.5deg; }
.stamp--e { --tilt: -4deg; }

/* The moment of the find: the stamp comes down. */
.stamp--strike {
    animation: strike 520ms cubic-bezier(0.2, 1.5, 0.35, 1) both;
}

@keyframes strike {
    0%   { opacity: 0;    transform: rotate(var(--tilt)) scale(2.1); }
    55%  { opacity: 0.95; transform: rotate(var(--tilt)) scale(0.95); }
    75%  {                transform: rotate(var(--tilt)) scale(1.03); }
    100% { opacity: 0.9;  transform: rotate(var(--tilt)) scale(1); }
}

/* ==========================================================================
   The hunt grid — clue cards that become stamps
   ========================================================================== */

.grid {
    display: grid;
    grid-template-columns: repeat(2, 1fr);
    gap: 0.75rem;
    margin: 0 0 1.4rem;
}

.spot {
    position: relative;
    display: flex;
    flex-direction: column;
    justify-content: center;
    min-height: 8.5rem;
    padding: 0.8rem 0.7rem;
    border-radius: var(--radius-card);
    text-decoration: none;
    color: inherit;
}

/* Unfound: a clue pressed into the page, waiting to be answered. */
.spot--open {
    background: var(--paper-well);
    border: 1.5px dashed var(--rock);
    box-shadow: inset 0 1px 3px rgba(27, 42, 51, 0.07);
}

.spot__clue {
    font-size: 0.92rem;
    line-height: 1.4;
    color: var(--ink-soft);
}

.spot__index {
    font-family: var(--font-display);
    font-size: 0.68rem;
    font-weight: 600;
    letter-spacing: 0.18em;
    text-transform: uppercase;
    color: var(--rock);
    margin-bottom: 0.35rem;
}

/* Found: the stamp sits straight on the page, no card around it. */
.spot--found {
    background: transparent;
    border: 0;
    align-items: center;
}

/* Sealed: the card is right there in the grid, so a hunter can see the whole
   shape of the adventure from the first minute. What it hides is the clue —
   never the fact that something is waiting. */
.spot--sealed {
    align-items: center;
    justify-content: center;
    gap: 0.45rem;
    text-align: center;
    color: var(--rock);
    border: 1px solid var(--rule);
    background: repeating-linear-gradient(
        -45deg,
        var(--paper-well),
        var(--paper-well) 7px,
        var(--paper) 7px,
        var(--paper) 14px
    );
}

.spot--sealed .spot__index { margin: 0; }

/* A wax seal pressed over the card. */
.spot__seal {
    display: flex;
    align-items: center;
    justify-content: center;
    width: 2.6rem;
    height: 2.6rem;
    border-radius: 50%;
    color: var(--stamp);
    border: 2px solid currentColor;
    opacity: 0.55;
}

.spot__seal svg {
    width: 1.15rem;
    height: 1.15rem;
    stroke: currentColor;
    fill: none;
    stroke-width: 1.9;
    stroke-linecap: round;
    stroke-linejoin: round;
}

.spot__stage {
    font-family: var(--font-display);
    font-size: 0.66rem;
    font-weight: 600;
    letter-spacing: 0.16em;
    text-transform: uppercase;
    color: var(--rock);
}

/* ==========================================================================
   Side quests — film frames waiting to be exposed
   ========================================================================== */

.quest {
    display: block;
    text-decoration: none;
    color: inherit;
}

.quest__frame {
    position: relative;
    aspect-ratio: 1 / 1;
    overflow: hidden;
    display: flex;
    align-items: center;
    justify-content: center;
    padding: 0.7rem;
    text-align: center;
    background: var(--paper-well);
    border: 1px solid var(--rule);
    border-radius: var(--radius);
}

.quest__frame img {
    width: 100%;
    height: 100%;
    object-fit: cover;
}

.quest--todo .quest__frame {
    border-style: dashed;
    border-color: var(--rock);
}

.quest__task {
    font-size: 0.88rem;
    line-height: 1.35;
    color: var(--ink-soft);
}

.quest__title {
    font-family: var(--font-display);
    font-size: 0.9rem;
    font-weight: 600;
    letter-spacing: 0.05em;
    text-transform: uppercase;
    margin: 0.45rem 0 0;
    line-height: 1.15;
}

.quest__points {
    font-family: var(--font-display);
    font-size: 0.72rem;
    font-weight: 600;
    letter-spacing: 0.12em;
    color: var(--rock);
}

.quest__done {
    position: absolute;
    right: 0.4rem;
    bottom: 0.4rem;
    padding: 0.12rem 0.3rem;
    font-family: var(--font-display);
    font-size: 0.62rem;
    font-weight: 700;
    letter-spacing: 0.14em;
    text-transform: uppercase;
    color: var(--stamp);
    background: var(--paper-raised);
    border: 1.5px solid currentColor;
    border-radius: 2px;
    transform: rotate(-4deg);
}

.quest__revoked {
    font-size: 0.85rem;
    color: var(--stamp);
    margin: 0.35rem 0 0;
}

/* ==========================================================================
   Tally — quiet. The stamps are the hero, not a big number.
   ========================================================================== */

.tally {
    display: flex;
    align-items: baseline;
    gap: 1.4rem;
    padding: 0.7rem 0 1.1rem;
}

.tally__figure {
    font-family: var(--font-display);
    font-size: 1.7rem;
    font-weight: 700;
    line-height: 1;
}

.tally__label {
    display: block;
    margin-top: 0.15rem;
    font-family: var(--font-display);
    font-size: 0.7rem;
    font-weight: 600;
    letter-spacing: 0.15em;
    text-transform: uppercase;
    color: var(--rock);
}

/* ==========================================================================
   Find screens
   ========================================================================== */

.find {
    text-align: center;
    padding: 2rem 0 1rem;
}

.find__stamp {
    width: 11rem;
    margin: 0 auto 1.6rem;
}

.find__stamp .stamp__name { font-size: 1.25rem; }
.find__stamp .stamp__bird { width: 2rem; }

.find__story {
    margin: 0 0 1.3rem;
    padding: 1.1rem 1.15rem;
    text-align: left;
    font-size: 1.05rem;
    line-height: 1.6;
    color: var(--ink-soft);
    background: var(--paper-raised);
    border-left: 3px solid var(--stamp);
    border-radius: var(--radius);
}

.find__award {
    font-family: var(--font-display);
    font-size: 1.05rem;
    font-weight: 600;
    letter-spacing: 0.1em;
    text-transform: uppercase;
    color: var(--stamp);
}

.find__total {
    color: var(--rock);
    margin-bottom: 1.5rem;
}

.find__seal {
    width: 4.5rem;
    margin: 0 auto 1.2rem;
    opacity: 0.5;
}

/* ==========================================================================
   The book
   ========================================================================== */

.plate { margin: 0 0 1.6rem; }

/* Text that belongs to the picture above it, tucked up against it rather
   than floating a screen's worth of gap away. */
.plate + .passage--under {
    margin-top: -1rem;
}

.plate img {
    width: 100%;
    border: 1px solid var(--rule);
    border-radius: var(--radius);
}

.plate__caption {
    margin: 0.5rem 0 0.15rem;
    font-family: var(--font-hand);
    font-size: 1.25rem;
    line-height: 1.3;
    color: var(--ink-soft);
}

.plate__task {
    font-family: var(--font-display);
    font-size: 0.7rem;
    font-weight: 600;
    letter-spacing: 0.15em;
    text-transform: uppercase;
    color: var(--rock);
}

.logline {
    display: flex;
    align-items: baseline;
    justify-content: space-between;
    gap: 1rem;
    padding: 0.6rem 0;
    border-bottom: 1px solid var(--rule);
}

.logline:last-of-type { border-bottom: 0; }

.logline__name {
    font-family: var(--font-display);
    font-size: 1.05rem;
    font-weight: 600;
    letter-spacing: 0.04em;
    text-transform: uppercase;
}

.logline__date {
    font-family: var(--font-display);
    font-size: 0.72rem;
    font-weight: 600;
    letter-spacing: 0.12em;
    text-transform: uppercase;
    color: var(--rock);
    white-space: nowrap;
}

/* ==========================================================================
   Empty states — an invitation, never an apology
   ========================================================================== */

.blank {
    padding: 2.2rem 1rem;
    text-align: center;
    color: var(--rock);
}

.blank h2 {
    color: var(--ink-soft);
    margin-bottom: 0.4rem;
}

/* ==========================================================================
   Screen structure

   One heading pattern for every screen, one status strip, one spacing rhythm.
   Before this each screen invented its own arrangement out of inline styles,
   which is exactly why it read as cluttered.
   ========================================================================== */

.screenhead {
    font-size: 1.6rem;
    margin: 1.5rem 0 0.8rem;
}

.screenhead--sub {
    font-size: 0.8rem;
    letter-spacing: 0.18em;
    text-transform: uppercase;
    color: var(--rock);
    margin-top: 2rem;
    padding-bottom: 0.4rem;
    border-bottom: 1px solid var(--rule);
}

/* Identity and progress in a single line, so the top of the screen is not a
   stack of competing blocks. */
.strip {
    display: flex;
    align-items: center;
    gap: 0.7rem;
    padding: 0.7rem 0.85rem;
    background: var(--paper-raised);
    border: 1px solid var(--rule);
    border-radius: var(--radius-card);
}

.strip__face {
    width: 2.5rem;
    height: 2.5rem;
    flex: 0 0 auto;
    object-fit: cover;
    border-radius: 50%;
    background: var(--paper-well);
}

.strip__who {
    min-width: 0;
}

.strip__who strong {
    display: block;
    font-family: var(--font-display);
    font-size: 1.05rem;
    font-weight: 700;
    letter-spacing: 0.06em;
    text-transform: uppercase;
    line-height: 1.1;
}

.strip__who span {
    display: block;
    font-size: 0.85rem;
    color: var(--rock);
}

.notewrap {
    margin: -0.6rem 0 1.2rem;
    font-size: 0.9rem;
    color: var(--rock);
}

/* ==========================================================================
   Level — a quiet rank, not a scoreboard
   ========================================================================== */

.level {
    margin: 0 0 1.1rem;
}

.level__name {
    font-family: var(--font-display);
    font-size: 1.05rem;
    font-weight: 700;
    letter-spacing: 0.08em;
    text-transform: uppercase;
    color: var(--spruce);
}

.level__bar {
    height: 5px;
    margin: 0.4rem 0 0.3rem;
    background: var(--paper-well);
    border-radius: 100px;
    overflow: hidden;
}

.level__fill {
    height: 100%;
    background: var(--spruce);
    border-radius: 100px;
}

.level__next {
    font-size: 0.85rem;
    color: var(--rock);
}

/* ==========================================================================
   The map — pins appear as ground is covered
   ========================================================================== */

.map {
    position: relative;
    margin: 0 0 1.3rem;
    border: 1px solid var(--rule);
    border-radius: var(--radius-card);
    overflow: hidden;
    background: var(--paper-well);
}

.map img {
    width: 100%;
    display: block;
}

.map__pin {
    position: absolute;
    width: 1.4rem;
    height: 1.4rem;
    margin: -0.7rem 0 0 -0.7rem;
    border-radius: 50%;
    background: var(--stamp);
    border: 2px solid var(--paper-raised);
    box-shadow: 0 1px 4px rgba(27, 42, 51, 0.35);
}

.map__pin--boss {
    width: 1.8rem;
    height: 1.8rem;
    margin: -0.9rem 0 0 -0.9rem;
    background: var(--ink);
}

/* Somewhere still to go. Visible so the shape of the hunt is obvious, blank
   so it never says which place it is. */
.map__pin--ghost {
    background: transparent;
    border: 2px dashed var(--rock);
    opacity: 0.7;
}

.map--hero {
    margin-bottom: 0.9rem;
}

/* Typing a code, for when a camera or a rained-on sticker gives up. */
/* The one control this screen needs: the code off the marker. */
.codebar {
    display: flex;
    gap: 0.5rem;
    margin: 0;
}

.codebar input {
    flex: 1;
    min-width: 0;
    font-family: var(--font-display);
    font-size: 1.05rem;
    letter-spacing: 0.18em;
    text-transform: lowercase;
}

.codebar .btn {
    flex: 0 0 auto;
}

/* ==========================================================================
   The atlas — the map IS the hunt

   A scrolling frame with the map sized by percentage inside it. Dots are
   positioned in percentages within the same wrapper, so they travel and
   scale with the map for free instead of being repositioned on every zoom.
   ========================================================================== */

.atlas {
    position: relative;
    margin: 0 0 0.8rem;
}

.atlas__frame {
    overflow: auto;
    -webkit-overflow-scrolling: touch;
    /* The map is the hunt, so it takes the screen rather than a polite slice
       of it. Tall enough that a phone shows mostly map, with the clue readout
       and the tab bar still reachable without hunting for them. */
    height: 62vh;
    min-height: 20rem;
    background: var(--paper-well);
    border: 1px solid var(--rule);
    border-radius: var(--radius-card);
    cursor: grab;
    /* Every gesture is handled in script, so the browser must not also try to
       scroll underneath it — that fight is what made dragging feel broken. */
    touch-action: none;
    overscroll-behavior: contain;
    scrollbar-width: none;
}

.atlas__frame::-webkit-scrollbar {
    display: none;
}

.atlas__frame--panning {
    cursor: grabbing;
}

/* On a short screen -- a phone in landscape, mostly -- a fixed 62vh leaves
   almost nothing for the clue. Give the map less there. */
@media (max-height: 34rem) {
    .atlas__frame {
        height: 52vh;
        min-height: 14rem;
    }
}

/* The map fills its frame rather than sitting letterboxed at the top of it,
   which is what "take the remaining space" has to mean when a wide map meets
   a tall phone. */
.atlas__inner {
    min-height: 100%;
}

.atlas__inner {
    position: relative;
    width: 100%;
    transition: width 0.18s ease;
}

.atlas__inner img {
    width: 100%;
    display: block;
}

.atlas__zoom {
    position: absolute;
    right: 0.6rem;
    bottom: 0.6rem;
    display: flex;
    flex-direction: column;
    gap: 0.3rem;
}

.atlas__zoom button {
    width: 2.4rem;
    height: 2.4rem;
    min-height: 0;
    padding: 0;
    font-size: 1.3rem;
    line-height: 1;
    color: var(--ink);
    background: var(--paper-raised);
    border: 1px solid var(--rule);
    border-radius: var(--radius);
    box-shadow: 0 1px 4px rgba(27, 42, 51, 0.2);
}

/* A place on the map. Sized in rem so it stays tappable at any zoom rather
   than shrinking into nothing when the map is small. */
.dot {
    position: absolute;
    width: 1.9rem;
    height: 1.9rem;
    margin: -0.95rem 0 0 -0.95rem;
    padding: 0;
    min-height: 0;
    display: flex;
    align-items: center;
    justify-content: center;
    font-family: var(--font-display);
    font-size: 0.9rem;
    font-weight: 700;
    line-height: 1;
    border-radius: 50%;
    border: 2px solid var(--paper-raised);
    box-shadow: 0 1px 5px rgba(27, 42, 51, 0.4);
    cursor: pointer;
}

.dot--open {
    color: var(--paper-raised);
    /* Unlocked and go-able — blue. Only a sealed marker is grey. */
    background: #2c6cae;
}

.dot--found {
    color: var(--paper-raised);
    background: var(--spruce);
}

.dot--sealed {
    color: var(--paper-raised);
    background: var(--rock);
    opacity: 0.75;
}

/* Shut, rather than dangerous. */
.dot__lock {
    display: block;
    width: 1rem;
    height: 1rem;
}

.dot--boss .dot__lock {
    width: 1.25rem;
    height: 1.25rem;
}

.dot__lock svg {
    width: 100%;
    height: 100%;
    fill: none;
    stroke: currentColor;
    stroke-width: 2;
    stroke-linecap: round;
    stroke-linejoin: round;
}

.dot--boss {
    /* Same size as every other marker — only the colour marks it out. */
    background: var(--ink);
    color: var(--paper-raised);
}

/* The one you just tapped. */
.dot--on {
    box-shadow: 0 0 0 4px rgba(179, 63, 44, 0.35), 0 1px 5px rgba(27, 42, 51, 0.4);
    transform: scale(1.12);
}

/* Where the tapped clue is read. Below the map rather than floating over it,
   so it never covers the thing you are trying to look at. */
.readout {
    padding: 0.95rem 1.05rem;
    margin: 0 0 1.3rem;
    background: var(--paper-raised);
    border: 1px solid var(--rule);
    border-left: 3px solid var(--rule);
    border-radius: var(--radius);
    min-height: 4.5rem;
}

.readout--live {
    border-left-color: var(--stamp);
}

.readout__title {
    margin: 0 0 0.25rem;
    font-family: var(--font-display);
    font-size: 0.75rem;
    font-weight: 600;
    letter-spacing: 0.16em;
    text-transform: uppercase;
    color: var(--rock);
}

.readout__text {
    margin: 0;
    font-size: 1.05rem;
    line-height: 1.5;
}

/* ==========================================================================
   The bag
   ========================================================================== */

.bag {
    display: grid;
    grid-template-columns: repeat(2, 1fr);
    gap: 0.75rem;
    margin: 0 0 1.4rem;
}

.item {
    padding: 0.75rem;
    background: var(--paper-raised);
    border: 1px solid var(--rule);
    border-radius: var(--radius-card);
}

.item__art {
    width: 100%;
    aspect-ratio: 1 / 1;
    object-fit: contain;
    background: var(--paper-well);
    border-radius: var(--radius);
    margin-bottom: 0.5rem;
}

.item__art--none {
    display: flex;
    align-items: center;
    justify-content: center;
    color: var(--rock);
}

.item__name {
    font-family: var(--font-display);
    font-size: 0.95rem;
    font-weight: 700;
    letter-spacing: 0.05em;
    text-transform: uppercase;
    line-height: 1.1;
}

.item__from {
    margin-top: 0.2rem;
    font-size: 0.8rem;
    color: var(--rock);
}

.stats {
    display: flex;
    gap: 0.4rem;
    margin-top: 0.45rem;
}

.stat-pill {
    font-family: var(--font-display);
    font-size: 0.68rem;
    font-weight: 700;
    letter-spacing: 0.1em;
    text-transform: uppercase;
    padding: 0.1rem 0.4rem;
    border-radius: 100px;
    border: 1px solid currentColor;
}

.stat-pill--atk { color: var(--stamp); }
.stat-pill--def { color: var(--spruce); }

.power {
    display: flex;
    gap: 1.4rem;
    padding: 0.8rem 0 1.2rem;
}

/* ==========================================================================
   Encounters — blocked, choice, boss
   ========================================================================== */

.gate {
    margin: 1.2rem 0;
    padding: 1.1rem;
    text-align: left;
    background: var(--paper-raised);
    border: 1px solid var(--rule);
    border-left: 3px solid var(--stamp);
    border-radius: var(--radius);
}

/* The locked gate carries its own way out. The list of what you are short of
   and the button that answers it used to be two blocks with a gap between
   them, so the button read as unrelated to the list above it. */
.gate--locked .btn {
    margin-top: 1rem;
    margin-bottom: 0;
}

.gate__need {
    display: flex;
    align-items: center;
    gap: 0.75rem;
    margin-top: 0.8rem;
}

.gate__need img {
    width: 3.2rem;
    height: 3.2rem;
    object-fit: contain;
    background: var(--paper-well);
    border-radius: var(--radius);
}

.choice {
    display: block;
    width: 100%;
    margin: 0 0 0.7rem;
    padding: 1rem;
    text-align: left;
    font-family: var(--font-body);
    font-size: 1rem;
    letter-spacing: 0;
    text-transform: none;
    color: var(--ink);
    background: var(--paper-raised);
    border: 1px solid var(--rule);
    border-radius: var(--radius-card);
    min-height: 0;
}

.choice strong {
    display: block;
    font-family: var(--font-display);
    font-size: 1.05rem;
    font-weight: 700;
    letter-spacing: 0.06em;
    text-transform: uppercase;
    margin-bottom: 0.2rem;
}

.shortfall {
    display: flex;
    justify-content: space-between;
    gap: 1rem;
    padding: 0.55rem 0;
    border-bottom: 1px solid var(--rule);
    text-align: left;
}

.shortfall:last-of-type { border-bottom: 0; }

.shortfall__ok { color: var(--spruce); }
.shortfall__short { color: var(--stamp); font-weight: 600; }

/* ==========================================================================
   Companion
   ========================================================================== */

.companions {
    display: grid;
    grid-template-columns: repeat(2, 1fr);
    gap: 0.8rem;
    margin: 1.2rem 0;
}

.companion {
    padding: 0.85rem;
    text-align: center;
    background: var(--paper-raised);
    border: 1px solid var(--rule);
    border-radius: var(--radius-card);
    cursor: pointer;
}

.companion__art {
    width: 100%;
    aspect-ratio: 1 / 1;
    object-fit: contain;
    background: var(--paper-well);
    border-radius: var(--radius);
    margin-bottom: 0.5rem;
}

.companion input { display: none; }

.companion:has(input:checked) {
    border-color: var(--stamp);
    box-shadow: inset 0 0 0 1px var(--stamp);
}

.sidekick {
    display: flex;
    align-items: center;
    gap: 0.6rem;
    margin: 0 0 1rem;
}

.sidekick img {
    width: 2.6rem;
    height: 2.6rem;
    object-fit: contain;
    border-radius: 50%;
    background: var(--paper-well);
}

/* ==========================================================================
   Setup wizard — the forced first run

   Three steps, and the hunter should always know which one they are on and
   how many are left. Nothing here is skippable, so the progress indicator is
   reassurance rather than decoration.
   ========================================================================== */

.steps {
    display: flex;
    gap: 0.4rem;
    margin: 0 0 1.4rem;
}

.steps__dot {
    flex: 1;
    height: 4px;
    background: var(--paper-well);
    border-radius: 100px;
}

.steps__dot--done { background: var(--spruce); }
.steps__dot--now { background: var(--stamp); }

.steps__label {
    font-family: var(--font-display);
    font-size: 0.72rem;
    font-weight: 600;
    letter-spacing: 0.16em;
    text-transform: uppercase;
    color: var(--rock);
    margin: 0 0 0.35rem;
}

/* The kit picker: tap to take, tap again to put back. */
.kit {
    display: grid;
    grid-template-columns: repeat(2, 1fr);
    gap: 0.7rem;
    margin: 1rem 0 1.3rem;
}

.tool {
    position: relative;
    display: block;
    padding: 0.8rem;
    text-align: center;
    background: var(--paper-raised);
    border: 1.5px solid var(--rule);
    border-radius: var(--radius-card);
    cursor: pointer;
}

.tool input { position: absolute; opacity: 0; pointer-events: none; }

.tool__art {
    width: 100%;
    aspect-ratio: 1 / 1;
    object-fit: contain;
    background: var(--paper-well);
    border-radius: var(--radius);
    margin-bottom: 0.45rem;
}

/* The card is a <label>, so it inherits the form-label styling: display
   face, wide letter-spacing, uppercase. Right for the word above a text box
   and wrong for a sentence describing a torch, which came out as three lines
   of shouting. Sentence case and the body face from here down. */
.tool__what {
    font-family: var(--font-body);
    font-size: 0.83rem;
    font-weight: 400;
    letter-spacing: 0;
    text-transform: none;
    line-height: 1.35;
    color: var(--rock);
    margin-top: 0.2rem;
}

.tool:has(input:checked) {
    border-color: var(--stamp);
    box-shadow: inset 0 0 0 1px var(--stamp);
}

.tool:has(input:checked)::after {
    content: "Taken";
    position: absolute;
    top: 0.4rem;
    right: 0.4rem;
    font-family: var(--font-display);
    font-size: 0.62rem;
    font-weight: 700;
    letter-spacing: 0.12em;
    text-transform: uppercase;
    color: var(--paper-raised);
    background: var(--stamp);
    border-radius: 2px;
    padding: 0.1rem 0.3rem;
}

.counter {
    text-align: center;
    font-family: var(--font-display);
    font-size: 0.8rem;
    font-weight: 600;
    letter-spacing: 0.14em;
    text-transform: uppercase;
    color: var(--rock);
    margin-bottom: 0.8rem;
}

/* ==========================================================================
   Scenes — the branching encounter

   Body text reads like a page of the journal; options read like things you
   could actually do. A locked option stays visible on purpose: seeing the
   door you cannot open is what sends a hunter looking for the key.
   ========================================================================== */

.passage {
    font-size: 1.08rem;
    line-height: 1.65;
    color: var(--ink);
    padding: 1.2rem 1.15rem;
    margin: 0 0 1.2rem;
    background: var(--paper-raised);
    border: 1px solid var(--rule);
    border-left: 3px solid var(--ink-soft);
    border-radius: var(--radius);
}

.option {
    display: block;
    width: 100%;
    margin: 0 0 0.6rem;
    padding: 0.95rem 1rem;
    text-align: left;
    font-family: var(--font-body);
    font-size: 1rem;
    line-height: 1.4;
    letter-spacing: 0;
    text-transform: none;
    color: var(--ink);
    background: var(--paper-raised);
    border: 1px solid var(--rule);
    border-radius: var(--radius-card);
    min-height: 0;
}

.option:active { transform: translateY(1px); }

.option--locked {
    background: var(--paper-well);
    border-style: dashed;
    color: var(--rock);
    cursor: not-allowed;
    opacity: 0.85;
}

.option--locked:active { transform: none; }

.option__need {
    display: block;
    margin-top: 0.3rem;
    font-family: var(--font-display);
    font-size: 0.72rem;
    font-weight: 600;
    letter-spacing: 0.12em;
    text-transform: uppercase;
    color: var(--stamp);
}

/* A boss speaking mid-exploration, rather than narration. Reads as someone
   talking at you, which is the whole effect — the tram boss needling hunters
   in the lobby on day one. */
.speech {
    margin: 0 0 1.2rem;
    padding: 1rem 1.1rem;
    background: var(--ink);
    border-radius: var(--radius-card);
    color: var(--paper);
}

.speech__who {
    display: flex;
    align-items: center;
    gap: 0.6rem;
    margin-bottom: 0.6rem;
}

.speech__who img {
    width: 2.4rem;
    height: 2.4rem;
    object-fit: cover;
    border-radius: 50%;
    border: 2px solid var(--stamp);
}

.speech__who span {
    font-family: var(--font-display);
    font-size: 0.9rem;
    font-weight: 700;
    letter-spacing: 0.12em;
    text-transform: uppercase;
    color: var(--stamp);
}

.speech__line {
    margin: 0;
    font-size: 1.05rem;
    line-height: 1.6;
}

/* ==========================================================================
   Battles — turn based, scripted, no dice
   ========================================================================== */

.fighters {
    display: flex;
    align-items: stretch;
    gap: 0.7rem;
    margin: 0 0 1.2rem;
}

.fighter {
    flex: 1;
    padding: 0.8rem;
    text-align: center;
    background: var(--paper-raised);
    border: 1px solid var(--rule);
    border-radius: var(--radius-card);
}

.fighter__name {
    font-family: var(--font-display);
    font-size: 0.85rem;
    font-weight: 700;
    letter-spacing: 0.1em;
    text-transform: uppercase;
    line-height: 1.15;
}

.fighter__hp {
    height: 8px;
    margin: 0.5rem 0 0.3rem;
    background: var(--paper-well);
    border-radius: 100px;
    overflow: hidden;
}

.fighter__fill {
    height: 100%;
    border-radius: 100px;
    transition: width 0.35s ease;
}

.fighter--boss .fighter__fill { background: var(--stamp); }
.fighter--you .fighter__fill { background: var(--spruce); }

.fighter__count {
    font-family: var(--font-display);
    font-size: 0.75rem;
    font-weight: 600;
    letter-spacing: 0.1em;
    color: var(--rock);
}

.round {
    padding: 0.9rem 1rem;
    margin: 0 0 1.1rem;
    font-size: 0.98rem;
    line-height: 1.5;
    background: var(--paper-well);
    border-radius: var(--radius);
}

.round strong { color: var(--stamp); }

.moves {
    margin-bottom: 1rem;
}

.move {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 0.8rem;
    width: 100%;
    margin: 0 0 0.5rem;
    padding: 0.75rem 0.9rem;
    text-align: left;
    font-family: var(--font-body);
    font-size: 0.98rem;
    letter-spacing: 0;
    text-transform: none;
    color: var(--ink);
    background: var(--paper-raised);
    border: 1px solid var(--rule);
    border-radius: var(--radius);
    min-height: 0;
}

.move--companion {
    border-color: var(--spruce);
    border-style: dashed;
}

/* The last resort. Quieter than the rest so it reads as what you do when
   nothing better is available, without ever being unavailable itself. */
.move--bare {
    color: var(--rock);
    background: var(--paper-well);
}

.move__dmg {
    flex: 0 0 auto;
    font-family: var(--font-display);
    font-size: 0.75rem;
    font-weight: 700;
    letter-spacing: 0.1em;
    text-transform: uppercase;
    color: var(--stamp);
}

.move[disabled] { opacity: 0.5; }

/* ==========================================================================
   Level up — a choice, not an automatic bump
   ========================================================================== */

.perks {
    display: grid;
    gap: 0.7rem;
    margin: 1.2rem 0;
}

.perk {
    display: block;
    width: 100%;
    padding: 1rem;
    text-align: left;
    font-family: var(--font-body);
    font-size: 1rem;
    letter-spacing: 0;
    text-transform: none;
    color: var(--ink);
    background: var(--paper-raised);
    border: 1px solid var(--rule);
    border-radius: var(--radius-card);
    min-height: 0;
}

.perk strong {
    display: block;
    font-family: var(--font-display);
    font-size: 1.1rem;
    font-weight: 700;
    letter-spacing: 0.06em;
    text-transform: uppercase;
    margin-bottom: 0.15rem;
}

/* ==========================================================================
   Bottom bar — the only navigation, always under a thumb
   ========================================================================== */

.tabs {
    position: fixed;
    left: 0;
    right: 0;
    bottom: 0;
    z-index: 20;
    display: flex;
    background: var(--paper-raised);
    border-top: 1px solid var(--rule);
    padding-bottom: env(safe-area-inset-bottom, 0px);
    box-shadow: 0 -2px 12px rgba(27, 42, 51, 0.07);
}

.tabs__link {
    flex: 1;
    display: flex;
    flex-direction: column;
    align-items: center;
    justify-content: center;
    gap: 0.18rem;
    min-height: var(--bar-height);
    padding: 0.5rem 0.25rem;
    text-decoration: none;
    color: var(--rock);
    font-family: var(--font-display);
    font-size: 0.68rem;
    font-weight: 600;
    letter-spacing: 0.13em;
    text-transform: uppercase;
}

.tabs__link svg,
.tabs__link .navico {
    width: 1.3rem;
    height: 1.3rem;
    stroke: currentColor;
    fill: none;
    stroke-width: 1.6;
    stroke-linecap: round;
    stroke-linejoin: round;
}

/* Uploaded artwork brings its own colour and must not be stroked. */
.tabs__link .navico--art { stroke: none; }

.tabs__link[aria-current="page"] { color: var(--stamp); }
.tabs__link[aria-current="page"] svg { stroke-width: 2; }

/* Coins, always on screen. A currency you cannot see is a currency nobody
   spends. */
.tabs__count {
    position: absolute;
    top: 0.35rem;
    right: 50%;
    transform: translateX(1.15rem);
    min-width: 1.15rem;
    padding: 0 0.25rem;
    font-size: 0.62rem;
    font-weight: 700;
    letter-spacing: 0.04em;
    line-height: 1.2rem;
    text-align: center;
    color: var(--paper-raised);
    background: var(--spruce);
    border-radius: 100px;
}

.tabs__link {
    position: relative;
}

/* ==========================================================================
   Admin — same world, the working end of the logbook
   ========================================================================== */

.adminnav {
    max-width: 34rem;
    margin: 0 auto 1.2rem;
    padding: 0 var(--gutter);
    display: flex;
    gap: 0.4rem;
    overflow-x: auto;
    -webkit-overflow-scrolling: touch;
    scrollbar-width: none;
}

.adminnav::-webkit-scrollbar { display: none; }

.adminnav a {
    flex: 0 0 auto;
    padding: 0.45rem 0.75rem;
    white-space: nowrap;
    font-family: var(--font-display);
    font-size: 0.76rem;
    font-weight: 600;
    letter-spacing: 0.12em;
    text-transform: uppercase;
    text-decoration: none;
    color: var(--rock);
    border: 1px solid var(--rule);
    border-radius: 100px;
}

.adminnav a[aria-current="page"] {
    color: var(--paper-raised);
    background: var(--ink);
    border-color: var(--ink);
}

.board {
    width: 100%;
    border-collapse: collapse;
    font-size: 0.94rem;
}

.board th {
    padding: 0.4rem 0.5rem 0.4rem 0;
    text-align: left;
    font-family: var(--font-display);
    font-size: 0.7rem;
    font-weight: 600;
    letter-spacing: 0.14em;
    text-transform: uppercase;
    color: var(--rock);
    border-bottom: 1px solid var(--rule);
}

.board td {
    padding: 0.6rem 0.5rem 0.6rem 0;
    vertical-align: top;
    border-bottom: 1px solid var(--rule);
}

.board tr:last-child td { border-bottom: 0; }

/* A managed item, stacked for a phone. */
.row {
    display: flex;
    align-items: flex-start;
    justify-content: space-between;
    gap: 0.9rem;
    padding: 0.85rem 0;
    border-bottom: 1px solid var(--rule);
}

.row:last-of-type { border-bottom: 0; }

.row__title {
    font-family: var(--font-display);
    font-size: 1.05rem;
    font-weight: 600;
    letter-spacing: 0.03em;
    text-transform: uppercase;
    line-height: 1.15;
}

.row__meta {
    margin-top: 0.15rem;
    font-size: 0.84rem;
    color: var(--rock);
}

.row__actions {
    display: flex;
    flex-direction: column;
    gap: 0.35rem;
    flex: 0 0 auto;
}

.row__actions .btn,
.row__actions button {
    min-height: 2.25rem;
    padding: 0.35rem 0.7rem;
    font-size: 0.72rem;
    letter-spacing: 0.1em;
}

.pill {
    display: inline-block;
    padding: 0.12rem 0.42rem;
    font-family: var(--font-display);
    font-size: 0.66rem;
    font-weight: 600;
    letter-spacing: 0.13em;
    text-transform: uppercase;
    color: var(--rock);
    border: 1px solid var(--rule);
    border-radius: 100px;
}

.pill--on { color: var(--spruce); border-color: var(--spruce); }
.pill--off { color: var(--stamp); border-color: var(--stamp); }

.stagehead {
    display: flex;
    align-items: center;
    gap: 0.6rem;
    margin: 1.6rem 0 0.3rem;
}

.stagehead::after {
    content: "";
    flex: 1;
    border-top: 1px solid var(--rule);
}

.stagehead h2 {
    font-size: 0.8rem;
    letter-spacing: 0.18em;
    text-transform: uppercase;
    color: var(--rock);
}

/* ==========================================================================
   Page transitions

   Turbo hands navigation to the browser's View Transitions API, so the old
   page cross-fades into the new one instead of snapping. The masthead and the
   tab bar are named, which pins them in place while the content moves — that
   is what stops the whole screen lurching and makes it read as one app rather
   than a series of pages.
   ========================================================================== */

@view-transition {
    navigation: auto;
}

::view-transition-old(root) {
    animation: pageout 140ms cubic-bezier(0.4, 0, 1, 1) both;
}

::view-transition-new(root) {
    animation: pagein 220ms cubic-bezier(0, 0, 0.2, 1) both;
}

@keyframes pageout {
    to {
        opacity: 0;
        transform: translateY(-6px);
    }
}

@keyframes pagein {
    from {
        opacity: 0;
        transform: translateY(10px);
    }
}

/* Held still while the content beneath them changes. Without this the bar a
   thumb is resting on fades out and back, which feels like the app restarted. */
.bar {
    view-transition-name: masthead;
}

.tabs {
    view-transition-name: tabbar;
}

/* Browsers without View Transitions (Firefox at time of writing) get the same
   effect the plain way: Turbo inserts a fresh <main> on every navigation, and
   a CSS animation on a newly inserted element runs by itself. Scoped so the
   two never both fire. */
@supports not (view-transition-name: none) {
    main {
        animation: pagein 220ms cubic-bezier(0, 0, 0.2, 1) both;
    }
}

/* Turbo shows a cached snapshot instantly while fetching the real page. Fading
   it slightly is an honest signal that what you are looking at is a moment old
   and about to be replaced. */
.turbo-progress-bar {
    height: 3px;
    background: var(--stamp);
}

html[data-turbo-preview] main {
    opacity: 0.65;
}

/* ==========================================================================
   Reduced motion
   ========================================================================== */

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

    /* Page transitions included. Someone who has asked their phone for less
       motion has usually asked for a reason, and a sliding page is exactly
       the kind of thing that triggers it. */
    ::view-transition-old(root),
    ::view-transition-new(root) {
        animation: none !important;
    }
}

/* ==========================================================================
   The bag sheet.

   A dialog rather than a page, so checking what you are carrying mid-scene
   never costs you your place in it. Slides up from the bottom the way a
   phone sheet does, because that is where a thumb already is.
   ========================================================================== */

.sheet {
    border: 0;
    padding: 0;
    background: transparent;
    width: min(34rem, 100%);
    max-width: 100%;
    max-height: 100%;
    margin: auto auto 0;
    color: var(--ink);
}

.sheet::backdrop {
    background: rgba(27, 42, 51, 0.55);
}

.sheet__panel {
    background: var(--paper-raised);
    border: 1px solid var(--line);
    border-bottom: 0;
    border-radius: var(--radius) var(--radius) 0 0;
    padding: 1.1rem 1.1rem 1.4rem;
    max-height: 82vh;
    overflow-y: auto;
}

.sheet[open] .sheet__panel {
    animation: sheet-rise 0.22s ease-out;
}

@keyframes sheet-rise {
    from { transform: translateY(12%); opacity: 0; }
    to { transform: translateY(0); opacity: 1; }
}

.sheet__head {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 0.8rem;
    margin-bottom: 0.9rem;
}

.sheet__x {
    border: 0;
    background: none;
    font-size: 1.6rem;
    line-height: 1;
    padding: 0.2rem 0.5rem;
    cursor: pointer;
    color: var(--ink-soft);
}

/* The crafting popup has no header row, so its close mark sits in the corner. */
#recipeDetail .sheet__panel { position: relative; }
#recipeDetail .prize__x {
    position: absolute;
    top: 0.4rem;
    right: 0.5rem;
    z-index: 3;
}

.sheet__item {
    display: flex;
    align-items: flex-start;
    justify-content: space-between;
    gap: 0.8rem;
    padding: 0.7rem 0;
    border-bottom: 1px solid var(--line);
}

.sheet__qty {
    color: var(--ink-soft);
    font-size: 0.9rem;
}

.sheet__desc {
    margin: 0.25rem 0 0;
    font-size: 0.9rem;
    color: var(--ink-soft);
}

.sheet__stats {
    display: flex;
    gap: 0.3rem;
    flex-shrink: 0;
}

.sheet__empty {
    color: var(--ink-soft);
    padding: 0.6rem 0 1rem;
}

.sheet .btn {
    margin-top: 1.1rem;
}

@media (prefers-reduced-motion: reduce) {
    .sheet[open] .sheet__panel { animation: none; }
}

/* ==========================================================================
   The fight.

   Boss art with its health laid across the foot of it, the exchange in the
   middle, and one panel at the bottom holding you: face, health, companion
   and every action you can take. Health sits with the thing it belongs to.
   ========================================================================== */

.bossart {
    position: relative;
    margin: 0 0 0.9rem;
    border: 1px solid var(--line);
    border-radius: var(--radius);
    overflow: hidden;
    background: var(--paper-well);
    min-height: 9rem;
    display: grid;
    place-items: center;
}

.bossart img {
    display: block;
    width: 100%;
    height: auto;
}

.bossart__none {
    padding: 2.4rem 1rem 3rem;
    color: var(--ink-soft);
    text-transform: uppercase;
    letter-spacing: 0.05em;
    text-align: center;
}

/* The boss's health, laid across the foot of the portrait as a game HUD:
   name on the left, figures on the right, meter under both. It sits on a
   gradient scrim so it stays readable over any artwork. */
.bosshp {
    position: absolute;
    left: 0; right: 0; bottom: 0;
    z-index: 2;
    display: grid;
    grid-template-columns: 1fr auto;
    align-items: baseline;
    gap: 0.25rem 0.6rem;
    padding: 1.6rem 0.7rem 0.6rem;
    background: linear-gradient(to top, rgba(10, 16, 22, 0.86), rgba(10, 16, 22, 0.45) 55%, transparent);
    pointer-events: none;
}

.bosshp__name {
    font-family: var(--font-display);
    font-size: 0.74rem;
    font-weight: 700;
    letter-spacing: 0.14em;
    text-transform: uppercase;
    color: #fff;
    text-shadow: 0 1px 3px rgba(0, 0, 0, 0.65);
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}

.bosshp__num {
    font-variant-numeric: tabular-nums;
    font-size: 0.74rem;
    font-weight: 700;
    color: #DCE7EF;
    text-shadow: 0 1px 3px rgba(0, 0, 0, 0.65);
}

.bosshp__track { grid-column: 1 / -1; }

/* The health bars, drawn once for both sides.
 *
 * A fight has two of these and they were two different widgets that happened
 * to both be health: a floating pill for the boss, a thin line inside a card
 * for you. Now it is one object seen twice. Name on the left, number on the
 * right, and a thick notched bar under both, so a glance reads as a game
 * rather than a form. */
.hud {
    position: relative;
    z-index: 2;
}

.hud--boss {
    display: flex;
    align-items: center;
    gap: 0.65rem;
    margin: -0.6rem 0 1rem;
    padding: 0.6rem 0.75rem 0.7rem;
    background: var(--paper-raised);
    border: 1px solid var(--line, var(--rule));
    border-radius: var(--radius);
    box-shadow: 0 2px 8px rgba(31, 26, 20, 0.14);
}

/* Crossed swords on the boss card, so the screen announces itself as a fight
   before a word of it is read. */
.hud__badge {
    flex-shrink: 0;
    display: grid;
    place-items: center;
    width: 2.1rem;
    height: 2.1rem;
    border-radius: 50%;
    background: rgba(184, 64, 47, 0.12);
    color: #8f2f22;
}

.hud__badge .navico {
    width: 1.15rem;
    height: 1.15rem;
}

.hud--you {
    display: flex;
    align-items: center;
    gap: 0.6rem;
}

.hud__row {
    display: flex;
    align-items: baseline;
    justify-content: space-between;
    gap: 0.6rem;
    margin-bottom: 0.3rem;
}

.hud__name {
    min-width: 0;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
    font-family: var(--font-display);
    font-size: 0.78rem;
    font-weight: 700;
    letter-spacing: 0.08em;
    text-transform: uppercase;
}

.hud__count {
    flex-shrink: 0;
    font-variant-numeric: tabular-nums;
    font-size: 0.8rem;
    font-weight: 700;
    color: var(--ink-soft);
}

.hud__face {
    width: 2.1rem;
    height: 2.1rem;
    flex-shrink: 0;
    border-radius: 50%;
    overflow: hidden;
    display: grid;
    place-items: center;
    background: var(--paper-well);
    border: 1px solid var(--line, var(--rule));
    font-family: var(--font-display);
    color: var(--ink-soft);
}

.hud__face img { width: 100%; height: 100%; object-fit: cover; }

.hud__vitals { flex: 1; min-width: 0; }

/* ==========================================================================
   The meters, drawn once for both sides.
   ========================================================================== */

/* Thick, inset and capsuled. A hairline track reads as a progress bar on a
   form; this should read as something being worn down. */
.hud__track,
.bosshp__track {
    position: relative;
    height: calc(0.72rem + 10px);
    overflow: hidden;
    border-radius: 999px;
}

.hud__track { background: rgba(20, 32, 43, 0.14); }

/* Over artwork, so the empty part of the track has to be dark enough to read
   against a pale boss as well as a dark one. */
.bosshp__track { background: rgba(6, 11, 16, 0.55); }

.hud__fill,
.bosshp__fill,
.hud__chip,
.bosshp__chip {
    position: absolute;
    inset: 0 auto 0 0;
    border-radius: 999px;
}

/* The damage trail: a pale layer that hangs behind for a beat before catching
   up, so a hit is something you watch happen rather than a number that has
   already changed. Sits under the fill, which is why it is declared first. */
.hud__chip,
.bosshp__chip {
    background: rgba(255, 246, 230, 0.5);
    transition: width 0.55s cubic-bezier(0.22, 0.61, 0.36, 1) 0.2s;
}

/* Flat. One solid colour, no gloss, no gradient, no notches — the meter is
   information, not an object pretending to catch the light. */
.hud__fill,
.bosshp__fill {
    background: #d8503a;
    transition: width 0.4s cubic-bezier(0.22, 0.61, 0.36, 1);
}

/* Properly green. --spruce is #2e4739, which at meter size reads as very dark
   grey rather than "you are alright". */
.hud__fill--you { background: #2fb05c; }

/* Colour carries the state, so it reads without counting. Driven by battle.js
   rather than the old [style*="width:4"] substring match, which could not
   tell 4% from 40% and silently missed the server-rendered first paint. */
.hud__fill[data-low="mid"],
.bosshp__fill[data-low="mid"] { background: #d99a2b; }

.hud__fill[data-low="low"],
.bosshp__fill[data-low="low"] {
    background: #c8392a;
    animation: meter-pulse 1.15s ease-in-out infinite;
}

@keyframes meter-pulse {
    0%, 100% { filter: brightness(1); }
    50%      { filter: brightness(1.5); }
}

@media (prefers-reduced-motion: reduce) {
    .hud__fill, .bosshp__fill, .hud__chip, .bosshp__chip { transition: none; }
    .hud__fill[data-low="low"], .bosshp__fill[data-low="low"] { animation: none; }
}

.bossart.is-hit { animation: boss-hit 0.4s ease-out; }
.bossart.is-turning { animation: boss-turn 0.5s ease-out; }

@keyframes boss-hit {
    0% { transform: translateX(0); filter: none; }
    20% { transform: translateX(-6px) rotate(-1deg); filter: brightness(1.7) saturate(0.4); }
    45% { transform: translateX(5px) rotate(1deg); filter: brightness(1.4); }
    100% { transform: translateX(0); filter: none; }
}

@keyframes boss-turn {
    0% { opacity: 0.25; transform: scale(1.03); }
    100% { opacity: 1; transform: scale(1); }
}

.plate--beaten { margin-bottom: 1rem; }

/* Damage floating off whoever just took it. */
.hitnum {
    position: absolute;
    left: 50%;
    top: 35%;
    transform: translate(-50%, 0);
    font-family: var(--display, inherit);
    font-size: 2rem;
    font-weight: 700;
    color: #fff;
    pointer-events: none;
    animation: hitnum-rise 0.9s ease-out forwards;
    text-shadow: 0 2px 6px rgba(0, 0, 0, 0.75);
    z-index: 2;
}

@keyframes hitnum-rise {
    0% { opacity: 0; transform: translate(-50%, 8px) scale(0.8); }
    25% { opacity: 1; transform: translate(-50%, -6px) scale(1.1); }
    100% { opacity: 0; transform: translate(-50%, -38px) scale(1); }
}

/* --- the exchange ------------------------------------------------------- */

/* Deliberately not scrollable: the newest line is always the last one on
   screen, and old lines drop off the top rather than piling up behind a
   scrollbar nobody on a phone would find mid-fight. */
.log {
    margin: 0 0 1rem;
    padding: 0.85rem 0.95rem;
    background: var(--paper-raised);
    border: 1px solid var(--line);
    border-radius: var(--radius);
}

/* The exchange, as a conversation.
 *
 * Each line used to be prefixed with the speaker's name written out, and a
 * boss with a long name took most of a phone's width before the sentence
 * began. A face in the margin says the same thing: yours on the right,
 * whatever you are fighting on the left, the way every other conversation on
 * this phone already looks.
 *
 * No real boss is named anywhere in this file. The whole stylesheet is
 * inlined into every page, so a name written here is a name spoiled on the
 * home screen before anyone has met it. */
.log__line {
    display: flex;
    align-items: flex-start;
    gap: 0.5rem;
    /* Room between turns. Packed tighter than this the exchange reads as one
       block of text rather than as people taking it in turns. */
    margin: 0 0 1.8rem;
    line-height: 1.45;
}

.log__line:last-child { margin-bottom: 0; }

/* Yours comes back the other way, face on the outside. */
.log__line--you {
    flex-direction: row-reverse;
}

.log__face {
    flex-shrink: 0;
    width: 1.85rem;
    height: 1.85rem;
    display: grid;
    place-items: center;
    overflow: hidden;
    border-radius: 50%;
    background: var(--paper-well);
    border: 1px solid var(--line, var(--rule));
    font-family: var(--font-display);
    font-size: 0.78rem;
    font-weight: 700;
    color: var(--ink-soft);
}

.log__face img { width: 100%; height: 100%; object-fit: cover; }

/* The portrait nobody has uploaded yet. */
.log__ghost {
    width: 1.05rem;
    height: 1.05rem;
    fill: none;
    stroke: currentColor;
    stroke-width: 1.7;
    stroke-linecap: round;
    opacity: 0.5;
}

.log__line--you .log__face { border-color: var(--spruce); }
.log__line--boss .log__face { border-color: #b8402f; }
.log__line--pal .log__face { border-color: #b8802f; }

/* What was said, in a bubble tinted by who said it. */
.log__bubble {
    flex: 0 1 auto;
    min-width: 0;
    display: flex;
    flex-wrap: wrap;
    align-items: baseline;
    gap: 0.15rem 0.5rem;
    padding: 0.45rem 0.65rem;
    border-radius: var(--radius);
    background: var(--paper-well);
}

.log__line--you .log__bubble { background: rgba(47, 158, 79, 0.12); }
.log__line--boss .log__bubble { background: rgba(184, 64, 47, 0.1); }
.log__line--pal .log__bubble { background: rgba(184, 128, 47, 0.12); }

.log__text { flex: 1 1 8rem; min-width: 0; }

/* What the blow cost, in the colour of whoever it cost it. */
.log__hit {
    display: inline-flex;
    align-items: center;
    gap: 0.25rem;
    flex-shrink: 0;
    margin-left: auto;
    padding: 0.1rem 0.45rem;
    border-radius: 999px;
    font-size: 0.8rem;
    font-weight: 700;
    font-variant-numeric: tabular-nums;
}

.log__hit--dealt {
    color: #1f6b3a;
    background: rgba(47, 158, 79, 0.14);
}

.log__hit--taken {
    color: #8f2f22;
    background: rgba(184, 64, 47, 0.13);
}

.hitico {
    width: 0.95em;
    height: 0.95em;
    flex-shrink: 0;
    fill: none;
    stroke: currentColor;
    stroke-width: 1.9;
    stroke-linecap: round;
    stroke-linejoin: round;
}

/* Colour lives on the bubble and the face ring now, set further up. What
   is left here is the two lines that have neither: the narrator and the
   kill. */
.log__line--pal { font-size: 0.95rem; }

.log__line--finish .log__bubble {
    background: var(--ink);
    color: var(--paper);
    font-weight: 600;
}

.log__line--intro {
    display: block;
    color: var(--ink-soft);
    font-style: italic;
}


.log__line.is-new { animation: line-in 0.32s ease-out; }

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

/* The fight, as one screen.
 *
 * The pieces stack: portrait, health, the exchange, you, your moves. Left to
 * their natural heights that came to more than a phone has, so the moves —
 * the only part you can actually use — were pushed under the fold and the
 * bottom of the fight was cut off.
 *
 * Now the portrait gives up room first, the log takes whatever is left and
 * scrolls inside itself, and everything you press keeps its full size and
 * stays on screen. fit.js sets the height; the fallback below is only for
 * the moment before it runs. */
.fight {
    display: flex;
    flex-direction: column;
    /* A rough guess for the moment before fit.js measures properly. It has to
       be `height`, not `max-height`: a max-height would go on clamping the
       measured value afterwards, and the fight would sit in the middle of the
       screen with a strip of empty page under the action bar. */
    height: calc(100dvh - 12rem);
}

.fight .bossart {
    flex: 0 1 auto;
    min-height: 0;
}

.fight .hud--boss,
.fight .you,
.fight .moves,
.fight .move__swap {
    flex: 0 0 auto;
}

/* The exchange absorbs the leftover room and scrolls within it, newest last
   — where the eye already is. */
.fight .log {
    flex: 1 1 auto;
    /* Three lines or so. Below this the exchange stops being readable and
       the portrait should give up its room instead: you can always see the
       boss, but a fight you cannot follow is not a fight. */
    min-height: 8.5rem;
    /* Room to breathe at both ends. Lines that start hard against the top
       edge and end hard against the bottom read as a log file. */
    padding: 0.9rem 0.95rem;
    margin-bottom: 0.9rem;
    overflow-y: auto;
    overscroll-behavior: contain;
}

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

.you {
    position: relative;
    background: var(--paper-raised);
    border: 1px solid var(--line);
    border-radius: var(--radius);
    padding: 0.9rem;
}

/* You and your moves, as one bar across the foot of the screen.
 *
 * The tab bar is hidden during a fight, so this takes the whole width and
 * sits where the thumbs already are. It used to be a card floating in the
 * page with the app's navigation directly beneath it, which meant the first
 * thing under "Run away" was "Hunt". */
.fight .you {
    margin: 0 calc(var(--gutter) * -1);
    padding: 0.6rem var(--gutter) max(0.7rem, env(safe-area-inset-bottom));
    background: var(--paper-raised);
    border: 0;
    border-top: 1px solid var(--line, var(--rule));
    border-radius: 0;
    box-shadow: 0 -3px 14px rgba(31, 26, 20, 0.1);
}

.fight .hud--you { margin-bottom: 0.55rem; }

/* Moves keep a full tap target but lose the padding around it — the buttons
   have to stay easy to hit in gloves, the gaps between them do not. */
.fight .moves { margin-bottom: 0; }

.fight .move {
    margin-bottom: 0.4rem;
    padding: 0.6rem 0.85rem;
}

/* Attacking is the thing you came here to do, so it looks like a button you
   press and the rest look like options you consider. */
.fight .move--attack {
    font-weight: 700;
}

.fight .move .sword {
    flex-shrink: 0;
    margin-right: 0.15rem;
    color: var(--ink-soft, #3D4E5C);
}

.fight .move--attack > span:first-of-type,
.fight .move > span:first-of-type {
    flex: 1;
    text-align: left;
}

/* The bag, as one row rather than one row per bottle. It carries the pack
   icon so it reads as the same thing the corner of the header opens. */
.fight .move--bag svg {
    width: 1.15rem;
    height: 1.15rem;
    flex-shrink: 0;
    margin-right: 0.35rem;
    fill: none;
    stroke: currentColor;
    stroke-width: 1.6;
    stroke-linecap: round;
    stroke-linejoin: round;
}

/* How many of that one are left, beside its name in the drawer. */
.move__qty {
    margin-left: 0.3rem;
    color: var(--rock);
    font-variant-numeric: tabular-nums;
}

/* Running away is not a move, it is leaving. Third-tier: no card, no border,
   just a line of text under everything you could actually do. */
.fight .move--flee {
    justify-content: center;
    gap: 0.4rem;
    margin-bottom: 0;
    padding: 0.5rem;
    background: none;
    border: 0;
    color: var(--ink-soft);
    font-size: 0.85rem;
}

.fight .move--flee .move__dmg {
    color: var(--ink-soft);
    font-weight: 400;
    letter-spacing: 0.04em;
}

.you.is-hurt { animation: you-hurt 0.45s ease-out; }

@keyframes you-hurt {
    0% { box-shadow: none; transform: translateX(0); }
    25% { box-shadow: 0 0 0 3px rgba(184, 64, 47, 0.55); transform: translateX(-4px); }
    60% { transform: translateX(3px); }
    100% { box-shadow: none; transform: translateX(0); }
}

.you__top {
    display: flex;
    align-items: center;
    gap: 0.75rem;
    margin-bottom: 0.85rem;
}

.you__face {
    width: 2.9rem;
    height: 2.9rem;
    flex-shrink: 0;
    border-radius: 50%;
    overflow: hidden;
    background: var(--paper-well);
    border: 1px solid var(--line);
    display: grid;
    place-items: center;
    font-family: var(--display, inherit);
    font-size: 1.3rem;
    color: var(--ink-soft);
}

.you__face img { width: 100%; height: 100%; object-fit: cover; }

.you__vitals { flex: 1; min-width: 0; }

.you__name {
    font-family: var(--display, inherit);
    letter-spacing: 0.03em;
    text-transform: uppercase;
    margin-bottom: 0.3rem;
}

.you__pal {
    text-transform: none;
    letter-spacing: 0;
    color: var(--ink-soft);
    font-size: 0.85rem;
    margin-left: 0.4rem;
}

.you .fighter__hp {
    height: 0.6rem;
    background: var(--paper-well);
    border-radius: 999px;
    overflow: hidden;
}

.you .fighter__fill {
    height: 100%;
    background: linear-gradient(90deg, var(--spruce), #4d7a5f);
    transition: width 0.45s cubic-bezier(0.22, 0.61, 0.36, 1);
}

.you__hp {
    flex-shrink: 0;
    font-variant-numeric: tabular-nums;
    color: var(--ink-soft);
}

/* Moves are unavailable while a round is still being narrated. */
.fight.is-waiting .moves button {
    opacity: 0.55;
    pointer-events: none;
}

/* --- what you came away with -------------------------------------------- */

.spoils {
    margin: 1rem 0;
    padding: 0.85rem 0.95rem;
    background: var(--paper-raised);
    border: 1px solid var(--line);
    border-left: 3px solid var(--spruce);
    border-radius: var(--radius);
}

.spoils__row {
    display: flex;
    justify-content: space-between;
    gap: 0.8rem;
    padding: 0.35rem 0;
}

.spoils__amount { color: var(--spruce); font-weight: 600; }

@media (prefers-reduced-motion: reduce) {
    .bossart.is-hit,
    .bossart.is-turning,
    .you.is-hurt,
    .log__line.is-new { animation: none; }
    .bossart__fill,
    .you .fighter__fill { transition: none; }
    .hitnum { display: none; }
}

/* ==========================================================================
   Coins, the profile in the bar, and buying something.
   ========================================================================== */

/* One coin, same everywhere it appears. Sized off the text around it. */
.coin {
    width: 1em;
    height: 1em;
    flex-shrink: 0;
    vertical-align: -0.13em;
    fill: none;
    stroke: currentColor;
    stroke-width: 1.7;
    stroke-linecap: round;
    stroke-linejoin: round;
}

/* The sword, on anything that starts a fight. Same treatment as the coin:
   em-sized so it follows the label beside it, stroked in the current colour
   so it takes on whatever button it sits in. */
.sword {
    width: 1.15em;
    height: 1.15em;
    flex-shrink: 0;
    fill: none;
    stroke: currentColor;
    stroke-width: 1.7;
    stroke-linecap: round;
    stroke-linejoin: round;
}

.coinline {
    display: inline-flex;
    align-items: center;
    gap: 0.35rem;
}

[data-coin-balance].is-spent {
    animation: coin-spend 0.7s ease-out;
}

@keyframes coin-spend {
    0% { color: inherit; }
    30% { color: #b8402f; transform: scale(1.14); }
    100% { color: inherit; transform: scale(1); }
}

/* --- who you are, top left ---------------------------------------------- */

.me {
    display: flex;
    align-items: center;
    gap: 0.6rem;
    text-decoration: none;
    color: inherit;
    min-width: 0;
}

.me__face {
    width: 2.2rem;
    height: 2.2rem;
    flex-shrink: 0;
    border-radius: 50%;
    overflow: hidden;
    background: var(--paper-well);
    border: 1px solid var(--line, var(--rule));
    display: grid;
    place-items: center;
    font-family: var(--display, inherit);
    font-size: 1rem;
    text-transform: uppercase;
    color: var(--ink-soft);
}

.me__face img { width: 100%; height: 100%; object-fit: cover; }

.me__text {
    display: flex;
    flex-direction: column;
    line-height: 1.15;
    min-width: 0;
}

.me__name {
    font-family: var(--display, inherit);
    letter-spacing: 0.03em;
    text-transform: uppercase;
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
}

.me__stats {
    display: flex;
    align-items: center;
    gap: 0.55rem;
    font-size: 0.78rem;
    color: var(--ink-soft);
}

.me__stat { display: inline-flex; align-items: center; gap: 0.2rem; }

/* --- the bag, with room to breathe -------------------------------------- */

.bag {
    gap: 1rem;
    margin: 0 0 1.6rem;
}

.item {
    padding: 1rem 0.95rem 1.05rem;
    display: flex;
    flex-direction: column;
    gap: 0.4rem;
}

.item__art { margin-bottom: 0.25rem; }

.item__name { margin-top: 0.1rem; }

.item__from { margin: 0; }

.item .stats { margin: 0.1rem 0; }

/* Equip control on a weapon or armour tile — one weapon, one armour active. */
.item__equip { margin: 0.2rem 0 0; }
.item__equip .btn {
    margin: 0;
    width: 100%;
    min-height: 0;
    padding: 0.35rem 0.6rem;
    font-size: 0.78rem;
    letter-spacing: 0.06em;
}
.item__equipped {
    margin-top: 0.2rem;
    font-family: var(--font-display);
    font-size: 0.72rem;
    font-weight: 700;
    letter-spacing: 0.08em;
    text-transform: uppercase;
    color: var(--spruce);
}

.item.is-bought {
    animation: item-bought 0.7s ease-out;
}

@keyframes item-bought {
    0% { box-shadow: none; }
    30% { box-shadow: 0 0 0 3px var(--spruce); }
    100% { box-shadow: none; }
}

/* --- what you just bought ------------------------------------------------ */

.prize__panel { text-align: center; }

.prize__eyebrow { margin: 0 0 0.8rem; }

.prize__art {
    width: 8.5rem;
    height: 8.5rem;
    margin: 0 auto 0.9rem;
    display: grid;
    place-items: center;
    background: var(--paper-well);
    border: 1px solid var(--line, var(--rule));
    border-radius: var(--radius);
    overflow: hidden;
    color: var(--ink-soft);
    animation: prize-pop 0.45s cubic-bezier(0.18, 0.89, 0.32, 1.28);
}

.prize__art img { width: 100%; height: 100%; object-fit: contain; }

@keyframes prize-pop {
    0% { opacity: 0; transform: scale(0.4) rotate(-8deg); }
    70% { opacity: 1; transform: scale(1.08) rotate(2deg); }
    100% { opacity: 1; transform: scale(1) rotate(0); }
}

.prize__name {
    font-family: var(--display, inherit);
    letter-spacing: 0.03em;
    text-transform: uppercase;
    margin: 0 0 0.35rem;
}

.prize__desc {
    margin: 0 0 0.7rem;
    color: var(--ink-soft);
}

.prize__stats { justify-content: center; margin-bottom: 0.9rem; }

.prize__spent {
    display: inline-flex;
    align-items: center;
    gap: 0.3rem;
    margin: 0;
    color: #b8402f;
    font-weight: 600;
}

.prize__left {
    color: var(--ink-soft);
    font-weight: 400;
    margin-left: 0.5rem;
}

@media (prefers-reduced-motion: reduce) {
    .prize__art,
    .item.is-bought,
    [data-coin-balance].is-spent { animation: none; }
}

/* ==========================================================================
   The shop shelf, and a finished marker on the map.
   ========================================================================== */

/* The artwork IS the card. Everything else sits on top of it. */
.wares {
    position: relative;
    display: flex;
    flex-direction: column;
    justify-content: flex-end;
    min-height: 15rem;
    padding: 0;
    border: 1px solid var(--line, var(--rule));
    border-radius: var(--radius-card);
    overflow: hidden;
    background-color: var(--paper-well);
    background-size: cover;
    background-position: center;
}

.wares--noart {
    background-image: repeating-linear-gradient(
        45deg,
        var(--paper-well),
        var(--paper-well) 10px,
        var(--paper-raised) 10px,
        var(--paper-raised) 20px
    );
}

/* A price tag hanging off the corner. */
.wares__price {
    position: absolute;
    top: 0.5rem;
    right: 0.5rem;
    z-index: 2;
    display: inline-flex;
    align-items: center;
    gap: 0.25rem;
    padding: 0.28rem 0.5rem;
    border-radius: 999px;
    background: rgba(27, 42, 51, 0.82);
    color: #fff;
    font-variant-numeric: tabular-nums;
    font-size: 0.85rem;
    font-weight: 600;
    backdrop-filter: blur(2px);
}

/* Text needs its own ground to stand on or it disappears into the art. */
.wares__body {
    position: relative;
    padding: 2.6rem 0.85rem 0.9rem;
    background: linear-gradient(
        to bottom,
        rgba(242, 239, 230, 0) 0%,
        rgba(242, 239, 230, 0.86) 38%,
        var(--paper-raised) 72%
    );
}

.wares__name {
    margin: 0 0 0.4rem;
    font-family: var(--display, inherit);
    font-size: 1rem;
    letter-spacing: 0.02em;
    text-transform: uppercase;
    line-height: 1.15;
}

.wares__stats { margin: 0 0 0.45rem; }

.wares__desc {
    margin: 0 0 0.5rem;
    font-size: 0.85rem;
    line-height: 1.35;
    color: var(--ink-soft);
}

.wares__owned {
    margin: 0 0 0.5rem;
    font-size: 0.82rem;
    color: var(--spruce);
    font-weight: 600;
}

[data-shop-item].is-bought {
    animation: item-bought 0.7s ease-out;
}

/* --- a marker you have finished ----------------------------------------- */

/* .dot--boss set a dark background AFTER .dot--found, so a boss marker you
   had already beaten never turned green. Both classes together now read as
   finished, which is the whole point of the colour. */
.dot--found.dot--boss {
    background: var(--spruce);
    color: var(--paper-raised);
}

.readout__earned {
    margin: 0.4rem 0 0;
    color: var(--spruce);
    font-weight: 600;
}

.readout--done .readout__title {
    color: var(--spruce);
}

/* ==========================================================================
   You, in full.
   ========================================================================== */

.me:hover .me__name,
.me[aria-current="page"] .me__name {
    color: var(--stamp);
}

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

.profile__face {
    width: 6.5rem;
    height: 6.5rem;
    margin: 0 auto 0.8rem;
    border-radius: 50%;
    overflow: hidden;
    background: var(--paper-well);
    border: 1px solid var(--line, var(--rule));
    display: grid;
    place-items: center;
    font-family: var(--display, inherit);
    font-size: 2.6rem;
    text-transform: uppercase;
    color: var(--ink-soft);
}

.profile__face img { width: 100%; height: 100%; object-fit: cover; }

.profile__name { margin: 0 0 0.2rem; }

.profile__rank {
    margin: 0;
    color: var(--ink-soft);
    text-transform: uppercase;
    letter-spacing: 0.06em;
    font-size: 0.85rem;
}

/* Three numbers across, readable at arm's length. */
.tally {
    display: grid;
    grid-template-columns: repeat(3, 1fr);
    gap: 0.7rem;
    margin: 0 0 0.8rem;
}

.tally__cell {
    padding: 0.8rem 0.5rem;
    text-align: center;
    background: var(--paper-raised);
    border: 1px solid var(--line, var(--rule));
    border-radius: var(--radius-card);
}

/* Every cell is the same height whatever is inside it.
 *
 * The coins one holds a coin as well as a number, and an inline image grows
 * the line box by a pixel or two — enough that one tile in a row of six sat
 * lower than the rest. Fixing the row's height rather than hoping the
 * contents agree is what keeps them level whatever artwork gets uploaded. */
.tally__cell strong {
    display: flex;
    align-items: center;
    justify-content: center;
    gap: 0.2rem;
    height: 1.65rem;
    font-family: var(--display, inherit);
    font-size: 1.5rem;
    line-height: 1;
}

/* The coin inside one sits on the text baseline rather than dragging the
   row taller. */
.tally__cell strong .coin {
    width: 0.8em;
    height: 0.8em;
    vertical-align: baseline;
}

.tally__cell .coinline { justify-content: center; }

.tally__cell span {
    display: block;
    margin-top: 0.15rem;
    font-size: 0.78rem;
    text-transform: uppercase;
    letter-spacing: 0.05em;
    color: var(--ink-soft);
}

/* ==========================================================================
   The bag, tidied — and the shop saying no.
   ========================================================================== */

/* Artwork is never cropped. The old rule forced every picture into a square,
   which sliced the sides off anything drawn wider than it is tall. The frame
   holds the space; the picture sits inside it whole. */
.item__frame {
    position: relative;
    display: flex;
    align-items: center;
    justify-content: center;
    height: 7.5rem;
    padding: 0.4rem;
    margin-bottom: 0.5rem;
    background: var(--paper-well);
    border-radius: var(--radius);
    overflow: hidden;
}

.item__frame .item__art {
    width: auto;
    max-width: 100%;
    height: 100%;
    max-height: 100%;
    aspect-ratio: auto;
    object-fit: contain;
    margin: 0;
    background: none;
}

.item__frame .item__art--none {
    width: 100%;
    height: 100%;
}

/* How many you are carrying, on the picture rather than in a line of prose. */
.item__qty {
    position: absolute;
    right: 0.35rem;
    bottom: 0.35rem;
    padding: 0.15rem 0.45rem;
    border-radius: 999px;
    background: rgba(27, 42, 51, 0.82);
    color: #fff;
    font-size: 0.78rem;
    font-variant-numeric: tabular-nums;
    font-weight: 600;
}

/* Something that gets spent reads differently from something that keeps. */
.stat-pill--use {
    background: rgba(184, 128, 47, 0.16);
    color: #8a5f18;
}

.item--spends {
    border-style: dashed;
}

.item__from--src {
    font-size: 0.78rem;
    opacity: 0.8;
}

/* --- not enough coins ---------------------------------------------------- */

.short__what {
    margin: 0 0 0.9rem;
    line-height: 1.5;
}

.short__gap {
    display: grid;
    grid-template-columns: 1fr 1fr;
    gap: 0.7rem;
    margin-bottom: 0.9rem;
}

.short__gap > div {
    padding: 0.7rem 0.5rem;
    background: var(--paper-well);
    border-radius: var(--radius);
    text-align: center;
}

.short__gap strong {
    display: inline-flex;
    font-family: var(--display, inherit);
    font-size: 1.3rem;
}

.short__gap div:last-child strong { color: #b8402f; }

.short__gap span {
    display: block;
    margin-top: 0.15rem;
    font-size: 0.75rem;
    text-transform: uppercase;
    letter-spacing: 0.05em;
    color: var(--ink-soft);
}

/* ==========================================================================
   The shop card as a button, the sound toggle, and the two popups.
   ========================================================================== */

/* The form must not become the grid item — the card inside it must. */
.wares-form { display: contents; }

/* .wares is a <button> now, so it needs the browser's button styling undone
   before it can look like a card again. */
button.wares {
    width: 100%;
    text-align: left;
    font: inherit;
    color: inherit;
    cursor: pointer;
    -webkit-appearance: none;
    appearance: none;
    transition: transform 0.14s ease, box-shadow 0.14s ease;
}

button.wares:hover,
button.wares:focus-visible {
    transform: translateY(-2px);
    box-shadow: 0 6px 18px rgba(27, 42, 51, 0.18);
}

button.wares:active { transform: translateY(0) scale(0.99); }

/* --- the sound toggle ---------------------------------------------------- */

.setting {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 1rem;
    padding: 0.9rem;
    margin-bottom: 0.8rem;
    background: var(--paper-raised);
    border: 1px solid var(--line, var(--rule));
    border-radius: var(--radius-card);
}

.setting__what strong { display: block; }

.setting__what span {
    display: block;
    margin-top: 0.15rem;
    font-size: 0.82rem;
    color: var(--ink-soft);
}

.toggle {
    display: inline-flex;
    align-items: center;
    gap: 0.5rem;
    border: 0;
    background: none;
    cursor: pointer;
    font: inherit;
    color: var(--ink-soft);
    padding: 0;
}

.toggle__track {
    width: 2.9rem;
    height: 1.6rem;
    border-radius: 999px;
    background: var(--spruce);
    position: relative;
    transition: background 0.18s ease;
}

.toggle__knob {
    position: absolute;
    top: 0.2rem;
    left: 1.5rem;
    width: 1.2rem;
    height: 1.2rem;
    border-radius: 50%;
    background: #fff;
    transition: left 0.18s ease;
}

.toggle[aria-pressed="true"] .toggle__track { background: var(--rock, #8a8a8a); }
.toggle[aria-pressed="true"] .toggle__knob { left: 0.2rem; }

.toggle__word { min-width: 1.8rem; text-align: left; }

/* --- both popups --------------------------------------------------------- */

.prize .sheet__panel {
    padding: 1.4rem 1.2rem 1.5rem;
}

.prize__eyebrow {
    color: var(--ink-soft);
    letter-spacing: 0.1em;
}

.prize__name { font-size: 1.25rem; }

.prize__desc {
    font-size: 0.9rem;
    line-height: 1.45;
    max-width: 26rem;
    margin-left: auto;
    margin-right: auto;
}

.prize__spent {
    justify-content: center;
    padding: 0.6rem 0.9rem;
    margin: 0 auto 0.4rem;
    background: var(--paper-well);
    border-radius: 999px;
    font-variant-numeric: tabular-nums;
}

.prize .btn { margin-top: 1rem; }

/* The stand-in when an item has no artwork yet. A parcel, drawn in the same
   line weight as everything else: the camera emoji that used to sit here
   rendered as a large black blob in the middle of the popup. */
.prize__box {
    width: 3.2rem;
    height: 3.2rem;
    fill: none;
    stroke: currentColor;
    stroke-width: 1.4;
    stroke-linecap: round;
    stroke-linejoin: round;
    color: var(--ink-soft);
    opacity: 0.6;
}

/* Where the thing went. A purchase used to end on a price and a balance,
   which says what it cost but never where it is now. */
.prize__landed {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    gap: 0.45rem;
    margin: 0 auto;
    color: var(--spruce);
    font-weight: 600;
    font-size: 0.92rem;
}

.prize__landed svg {
    width: 1.15em;
    height: 1.15em;
    flex-shrink: 0;
    fill: none;
    stroke: currentColor;
    stroke-width: 1.7;
    stroke-linecap: round;
    stroke-linejoin: round;
}

/* The bag takes the delivery, so the popup's claim is watchable. It lives in
   the top bar now; the old bottom-tab selector is kept because the same
   animation still reads correctly if the bag ever moves back down there. */
.tabs__link.is-stuffed svg,
.bartool.is-stuffed svg {
    animation: stuffed 0.7s ease-out;
}

/* The number goes with it, a beat later, so you see the pack move and then
   the count that moved catch your eye. */
.bartool.is-stuffed .bartool__count {
    animation: counted 0.6s ease-out 0.12s;
}

@keyframes stuffed {
    0%   { transform: translateY(0)     scale(1)    rotate(0deg); }
    22%  { transform: translateY(-0.3rem) scale(1.18) rotate(-7deg); }
    45%  { transform: translateY(-0.1rem) scale(1.08) rotate(6deg); }
    68%  { transform: translateY(0)     scale(0.95) rotate(-3deg); }
    100% { transform: translateY(0)     scale(1)    rotate(0deg); }
}

/* Up and back, because the number went up. */
@keyframes counted {
    0%   { transform: translateY(0)       scale(1);   background: var(--stamp); }
    35%  { transform: translateY(-0.3rem) scale(1.5); background: var(--spruce); }
    100% { transform: translateY(0)       scale(1);   background: var(--stamp); }
}

@media (prefers-reduced-motion: reduce) {
    .tabs__link.is-stuffed svg,
    .bartool.is-stuffed svg,
    .bartool.is-stuffed .bartool__count { animation: none; }
}

.short__what { font-size: 1.02rem; }

/* ==========================================================================
   Shop cards, sized so the artwork is actually the card.

   The first attempt let the text panel grow with its content; with a
   description in it that panel covered 93% of the tile and the picture
   underneath was two pixels of nothing. The art now owns a fixed share of a
   fixed shape, and the label is a compact strip along the bottom.
   ========================================================================== */

button.wares,
.wares {
    aspect-ratio: 3 / 4;
    min-height: 0;
    justify-content: flex-end;
}

.wares__body {
    /* Only as tall as the two short lines inside it. */
    padding: 2rem 0.6rem 0.6rem;
    background: linear-gradient(
        to bottom,
        rgba(242, 239, 230, 0) 0%,
        rgba(242, 239, 230, 0.9) 45%,
        var(--paper-raised) 78%
    );
}

.wares__name {
    margin: 0 0 0.25rem;
    font-size: 0.86rem;
    line-height: 1.2;
    /* Two lines at most, so one long name cannot push the art out. */
    display: -webkit-box;
    -webkit-line-clamp: 2;
    -webkit-box-orient: vertical;
    overflow: hidden;
}

.wares__stats {
    margin: 0;
    gap: 0.25rem;
    flex-wrap: wrap;
}

.wares__stats .stat-pill {
    font-size: 0.68rem;
    padding: 0.1rem 0.35rem;
}

.wares__owned {
    margin: 0.25rem 0 0;
    font-size: 0.74rem;
}

.wares__price {
    top: 0.4rem;
    right: 0.4rem;
    padding: 0.22rem 0.45rem;
    font-size: 0.8rem;
}

/* Two per row on a phone, three once there is room for them. */
@media (min-width: 34rem) {
    .bag {
        grid-template-columns: repeat(3, 1fr);
    }
}

/* Two columns, always — and a description that cannot grow without limit.
   The tile is a fixed shape, the label strip is capped, and what is left is
   guaranteed to the artwork. */
.bag {
    grid-template-columns: repeat(2, 1fr);
}

button.wares,
.wares {
    aspect-ratio: 2 / 3;
}

.wares__body {
    padding: 1.6rem 0.6rem 0.6rem;
}

.wares__desc {
    margin: 0.25rem 0 0;
    font-size: 0.74rem;
    line-height: 1.3;
    color: var(--ink-soft);
    display: -webkit-box;
    -webkit-line-clamp: 2;
    -webkit-box-orient: vertical;
    overflow: hidden;
}

/* Six figures in one tight block rather than two loose rows of three. */
.tally--six {
    grid-template-columns: repeat(3, 1fr);
    gap: 0.35rem;
    margin-bottom: 1rem;
}

.tally--six .tally__cell {
    padding: 0.6rem 0.35rem;
}

.tally--six .tally__cell strong { font-size: 1.25rem; }

.tally--six .tally__cell span { font-size: 0.7rem; }

/* Your companion, shown with you. */
.sidekick--mine {
    display: flex;
    align-items: center;
    gap: 0.8rem;
    padding: 0.8rem;
    margin-bottom: 0.9rem;
    background: var(--paper-raised);
    border: 1px solid var(--line, var(--rule));
    border-radius: var(--radius-card);
}

.sidekick--mine img,
.sidekick--mine .sidekick__none {
    width: 3.4rem;
    height: 3.4rem;
    flex-shrink: 0;
    border-radius: 50%;
    object-fit: cover;
    background: var(--paper-well);
    display: grid;
    place-items: center;
    font-family: var(--display, inherit);
    font-size: 1.3rem;
    color: var(--ink-soft);
}

.sidekick--mine strong { margin-right: 0.4rem; }

/* The row is a <button>, so it inherits the button rule: display face, wide
   letter-spacing, uppercase. That is right for the name and wrong for
   everything else — a companion's blurb is a sentence, and set in caps at
   button size it wrapped to four shouting lines on a phone. Sentence case
   and the body face from here down. */
.sidekick--mine div {
    min-width: 0;
    text-align: left;
}

.sidekick--mine .item__from {
    margin-top: 0.25rem;
    font-family: var(--font-body);
    font-size: 0.82rem;
    font-weight: 400;
    letter-spacing: 0;
    text-transform: none;
    line-height: 1.4;
    color: var(--ink-soft);
}

.sidekick--mine .item__from {
    margin: 0.3rem 0 0;
    font-size: 0.82rem;
}

/* Signing out is not a primary action; it sits quietly at the foot, stacked
   full width — two half-width buttons side by side on a phone were a pair of
   targets nobody could hit cleanly. */
.profile__out {
    margin-top: 1.4rem;
}

.profile__out form { margin: 0; }
.profile__out .btn { width: 100%; margin: 0 0 0.6rem; }

/* ==========================================================================
   A drawer, and the camera tile on the side-quest board.
   ========================================================================== */

/* The bag pulls up from the bottom of the screen with a grab handle, the way
   a sheet on a phone does — it is somewhere you glance, not somewhere you go. */
.sheet--drawer .sheet__panel {
    padding-top: 0.7rem;
}

.sheet__grab {
    display: block;
    width: 2.4rem;
    height: 0.28rem;
    margin: 0 auto 0.7rem;
    border-radius: 999px;
    background: var(--line, var(--rule));
}

/* One animation, and transform only.
 *
 * It stuttered because three things happened at once on open: the panel slid,
 * its height was still settling, and fetched contents landed mid-flight,
 * forcing layout on nearly every frame. Transform is the one property here
 * the compositor can run by itself, the panel is promoted before it moves,
 * and the contents fade in after the slide rather than reflowing during it. */
/* Transitions, not keyframes — and that is what stops the flash.
 *
 * showModal() flips the dialog to displayed immediately, so with a keyframe
 * animation the browser gets one frame of the panel sitting at its final
 * position before the animation takes hold. That single frame is the flash.
 *
 * A transition with @starting-style has no such gap: the panel is declared at
 * translateY(100%) the instant it appears and moves from there. `overlay` and
 * `display` are transitioned with allow-discrete so the close animates too,
 * instead of the panel vanishing while the backdrop fades. Transform and
 * opacity only, so every frame is composited and none of it touches layout. */
.sheet--drawer {
    transform: translate3d(0, 100%, 0);
    transition:
        transform 0.36s cubic-bezier(0.16, 1, 0.3, 1),
        overlay 0.36s allow-discrete,
        display 0.36s allow-discrete;
    will-change: transform;
}

.sheet--drawer[open] {
    transform: translate3d(0, 0, 0);
}

@starting-style {
    .sheet--drawer[open] {
        transform: translate3d(0, 100%, 0);
    }
}

.sheet--drawer::backdrop {
    opacity: 0;
    transition:
        opacity 0.36s ease-out,
        overlay 0.36s allow-discrete,
        display 0.36s allow-discrete;
}

.sheet--drawer[open]::backdrop {
    opacity: 1;
}

@starting-style {
    .sheet--drawer[open]::backdrop {
        opacity: 0;
    }
}

/* The panel itself no longer animates — the dialog carries the movement, so
   there is only ever one thing in flight. */
.sheet--drawer[open] .sheet__panel {
    animation: none;
}

/* --- the camera tile ----------------------------------------------------- */

/* No organizer photo belongs on an unfinished side quest: the picture that
   goes here is the one the hunter has not taken yet. */
.quest__cam {
    display: grid;
    place-items: center;
    width: 100%;
    height: 100%;
    color: var(--rock, var(--ink-soft));
    background: var(--paper-well);
}

.quest__cam svg {
    width: 42%;
    max-width: 3.4rem;
    height: auto;
    fill: none;
    stroke: currentColor;
    stroke-width: 1.4;
    stroke-linecap: round;
    stroke-linejoin: round;
    opacity: 0.75;
}

.quest--todo:hover .quest__cam svg,
.quest--todo:focus-visible .quest__cam svg {
    opacity: 1;
}

/* The quest popup's own fields need the same spacing as a form on a page. */
#questDo label {
    display: block;
    margin: 0.9rem 0 0.3rem;
    font-size: 0.85rem;
    text-transform: uppercase;
    letter-spacing: 0.05em;
    color: var(--ink-soft);
}

#questDo input[type="text"],
#questDo input[name="caption"] {
    width: 100%;
}

/* What to point the camera at. Plain text: the camera glyph that used to
   ride alongside it had no rule of its own anywhere, so it rendered at its
   intrinsic 340px in solid black and swallowed the drawer. */
.quest__brief {
    margin: 0.15rem 0 0.9rem;
    color: var(--ink-soft);
    line-height: 1.45;
}

/* The empty frame the photo will fill. */
.shot {
    display: block;
    cursor: pointer;
}

.shot__frame {
    position: relative;
    display: grid;
    place-items: center;
    width: 100%;
    aspect-ratio: 4 / 3;
    overflow: hidden;
    border: 2px dashed var(--rule);
    border-radius: var(--radius-card);
    background: var(--paper-well);
    transition: border-color 0.15s ease, background 0.15s ease;
}

.shot:hover .shot__frame,
#questPhoto:focus-visible + .shot .shot__frame {
    border-color: var(--stamp);
    background: var(--paper-raised);
}

/* Once there is a photo it is no longer a placeholder: solid edge, no hint. */
.shot.is-loaded .shot__frame {
    border-style: solid;
    border-color: var(--rule);
    background: var(--ink);
}

.shot__preview {
    width: 100%;
    height: 100%;
    object-fit: cover;
    display: block;
}

.shot__empty {
    display: grid;
    justify-items: center;
    gap: 0.5rem;
    padding: 0.75rem;
    text-align: center;
    color: var(--rock);
    font-family: var(--font-display);
    font-size: 0.95rem;
    letter-spacing: 0.04em;
    text-transform: uppercase;
}

.shot__empty svg {
    width: 2.6rem;
    height: 2.6rem;
    fill: none;
    stroke: currentColor;
    stroke-width: 1.4;
    stroke-linecap: round;
    stroke-linejoin: round;
    opacity: 0.8;
}

/* A button you cannot press should not look like one you can. Without this,
   "Capture the find" sat there in full stamp red with nothing attached. */
.btn[disabled] {
    opacity: 0.45;
    cursor: not-allowed;
    box-shadow: none;
    transform: none;
}

@media (prefers-reduced-motion: reduce) {
    .sheet--drawer { transition: none; }
    .sheet--drawer[open] .sheet__panel { animation: none; }
}

/* ==========================================================================
   Shop cards, rebuilt as bag cards.

   The art-as-background version put the words on top of the picture, and the
   two fought over one space — the text won and the artwork was reduced to a
   few pixels. Framed art above, words below: the same card the bag uses, so
   an item looks like itself wherever you meet it.
   ========================================================================== */

/* Tighter shelf, and less air between the confirm drawer's two buttons. */
.bag[data-shop] { gap: 0.55rem; }
#confirmBuy .btn--quiet { margin-top: 0.5rem; }

button.item--buy {
    width: 100%;
    text-align: left;
    font: inherit;
    color: inherit;
    cursor: pointer;
    -webkit-appearance: none;
    appearance: none;
    aspect-ratio: auto;
    background: var(--paper-raised);
    border: 1px solid var(--line, var(--rule));
    border-radius: var(--radius-card);
    transition: transform 0.14s ease, box-shadow 0.14s ease;
}

button.item--buy:hover,
button.item--buy:focus-visible {
    transform: translateY(-2px);
    box-shadow: 0 6px 18px rgba(27, 42, 51, 0.18);
}

button.item--buy:active { transform: translateY(0) scale(0.99); }

/* The price rides on the corner of the picture, as a tag would. */
.item__price {
    position: absolute;
    top: 0.35rem;
    right: 0.35rem;
    display: inline-flex;
    align-items: center;
    gap: 0.22rem;
    padding: 0.2rem 0.45rem;
    border-radius: 999px;
    background: rgba(27, 42, 51, 0.82);
    color: #fff;
    font-size: 0.78rem;
    font-weight: 600;
    font-variant-numeric: tabular-nums;
}

.item__owned {
    margin: 0.35rem 0 0;
    font-size: 0.8rem;
    font-weight: 600;
    color: var(--spruce);
}

/* Artwork fills its frame edge to edge, at full height. Letterboxing left a
   band of empty board down either side of every picture; filling the frame
   makes the item read as the card. */
/* The frame is a square the width of the tile, always.
   It used to take its size from the picture inside it, so an item with no
   artwork yet collapsed to a 16px sliver and took the name and the price
   down with it — a whole row of shop tiles rendered as empty grey bars. */
.item__frame {
    padding: 0;
    width: 100%;
    height: auto;
    aspect-ratio: 1 / 1;
}

.item__frame .item__art {
    width: 100%;
    height: 100%;
    max-width: none;
    max-height: none;
    object-fit: cover;
    object-position: center;
}

.item__frame .item__art--none {
    display: grid;
    place-items: center;
}

/* The name, laid over the foot of the artwork.
 *
 * It used to sit under the picture with a row of stat pills and an owned
 * count beneath that, which left the art perhaps a centimetre of the tile
 * and turned the shelf into a spreadsheet. What a thing does belongs in the
 * popup, where there is room to say it properly. */
.item--buy .item__frame {
    height: 10.5rem;
    margin-bottom: 0;
}

.item__label {
    position: absolute;
    left: 0;
    right: 0;
    bottom: 0;
    padding: 1.6rem 0.6rem 0.5rem;
    /* Its own shade, so the name stays readable over a pale picture without
       dimming the whole tile. */
    background: linear-gradient(to top, rgba(20, 17, 13, 0.82), transparent);
    color: #fff;
    font-family: var(--font-display);
    font-size: 0.82rem;
    font-weight: 600;
    letter-spacing: 0.04em;
    line-height: 1.25;
    text-align: left;
    text-transform: uppercase;
    text-shadow: 0 1px 3px rgba(0, 0, 0, 0.5);
}

/* And what it does, under the name. Lower case and lighter, so it reads as
   the answer to a question about the name rather than a second name. */
.item__does {
    display: block;
    margin-top: 0.15rem;
    font-family: var(--font-body);
    font-size: 0.72rem;
    font-weight: 400;
    letter-spacing: 0;
    text-transform: none;
    color: rgba(255, 255, 255, 0.86);
}

/* Already carrying one: a tick in the corner, not a sentence. */
.item__got {
    position: absolute;
    top: 0.35rem;
    left: 0.35rem;
    display: grid;
    place-items: center;
    width: 1.35rem;
    height: 1.35rem;
    border-radius: 50%;
    background: var(--spruce);
    color: #fff;
}

.item__got svg {
    width: 0.85rem;
    height: 0.85rem;
    fill: none;
    stroke: currentColor;
    stroke-width: 2.6;
    stroke-linecap: round;
    stroke-linejoin: round;
}

/* ==========================================================================
   Refusals you cannot miss, game over, and a fight that stays legible.
   ========================================================================== */

/* A locked option used to be a dashed tan card with small print — quiet
   enough that people read it as decoration and kept tapping. Blocked now
   looks blocked: a red edge, a bar of colour, and the reason in plain sight. */
.option--locked {
    background: rgba(184, 64, 47, 0.06);
    border: 1px solid rgba(184, 64, 47, 0.45);
    border-left: 4px solid #b8402f;
    border-style: solid;
    color: var(--ink);
    cursor: not-allowed;
    opacity: 1;
}

.option__need {
    display: flex;
    align-items: center;
    gap: 0.35rem;
    margin-top: 0.45rem;
    padding: 0.35rem 0.5rem;
    background: #b8402f;
    border-radius: var(--radius);
    color: #fff;
    font-family: var(--font-display);
    font-size: 0.72rem;
    font-weight: 700;
    letter-spacing: 0.1em;
    text-transform: uppercase;
}

.option__need::before {
    content: "";
    width: 0.85rem;
    height: 0.85rem;
    flex-shrink: 0;
    background: currentColor;
    /* A padlock, drawn in CSS so it needs no asset. */
    -webkit-mask: no-repeat center / contain url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'%3E%3Cpath fill='%23000' d='M12 2a5 5 0 0 0-5 5v3H6a2 2 0 0 0-2 2v8a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2v-8a2 2 0 0 0-2-2h-1V7a5 5 0 0 0-5-5zm0 2a3 3 0 0 1 3 3v3H9V7a3 3 0 0 1 3-3z'/%3E%3C/svg%3E");
    mask: no-repeat center / contain url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'%3E%3Cpath fill='%23000' d='M12 2a5 5 0 0 0-5 5v3H6a2 2 0 0 0-2 2v8a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2v-8a2 2 0 0 0-2-2h-1V7a5 5 0 0 0-5-5zm0 2a3 3 0 0 1 3 3v3H9V7a3 3 0 0 1 3-3z'/%3E%3C/svg%3E");
}

/* Moves stay fully legible while a round narrates. Dimming every button to
   55% made the whole panel look broken for the second it was busy — the
   round is still playing, the buttons are simply not listening yet. */
.fight.is-waiting .moves button {
    opacity: 1;
    pointer-events: none;
}

.fight.is-waiting .moves { cursor: progress; }

/* --- game over ----------------------------------------------------------- */

.gameover__eyebrow {
    display: inline-block;
    margin-bottom: 0.7rem;
    padding: 0.3rem 0.7rem;
    background: #b8402f;
    border-radius: var(--radius);
    color: #fff;
    letter-spacing: 0.16em;
}

.toll {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 1rem;
    padding: 0.9rem;
    margin: 0 0 1rem;
    background: rgba(184, 64, 47, 0.08);
    border: 1px solid rgba(184, 64, 47, 0.4);
    border-radius: var(--radius-card);
}

.toll__amount {
    font-family: var(--font-display);
    font-size: 1.5rem;
    color: #b8402f;
    font-variant-numeric: tabular-nums;
}

.toll__what {
    text-align: right;
    font-size: 0.85rem;
    color: var(--ink-soft);
}

/* Healing shows on your own bar as it happens. */
.you.is-healed {
    animation: you-healed 0.5s ease-out;
}

@keyframes you-healed {
    0% { box-shadow: none; }
    30% { box-shadow: 0 0 0 3px rgba(46, 71, 57, 0.6); }
    100% { box-shadow: none; }
}

/* The story of how you came by the thing, once the photo is in. */
.passage--reveal {
    padding: 0.9rem 1rem;
    background: var(--paper-raised);
    border-left: 3px solid var(--spruce);
    border-radius: var(--radius);
}

@media (prefers-reduced-motion: reduce) {
    .you.is-healed { animation: none; }
}

/* ==========================================================================
   Reward kinds, and shop artwork that fills its column.
   ========================================================================== */

/* What a side quest pays out, as a category with an icon. Enough to judge a
   quest by; not enough to spoil the item itself. */
.kind {
    display: inline-flex;
    align-items: center;
    gap: 0.3rem;
    margin-top: 0.35rem;
    padding: 0.2rem 0.5rem;
    border-radius: 999px;
    font-family: var(--font-display);
    font-size: 0.68rem;
    font-weight: 700;
    letter-spacing: 0.08em;
    text-transform: uppercase;
    background: var(--paper-well);
    color: var(--ink-soft);
    border: 1px solid var(--line, var(--rule));
}

.kind__icon {
    width: 0.9rem;
    height: 0.9rem;
    flex-shrink: 0;
    fill: none;
    stroke: currentColor;
    stroke-width: 1.8;
    stroke-linecap: round;
    stroke-linejoin: round;
}

/* Each category reads at a glance by colour as well as by name. */
.kind--weapon { background: rgba(184, 64, 47, 0.12); color: #8f2f22; border-color: rgba(184, 64, 47, 0.35); }
.kind--armour { background: rgba(46, 71, 57, 0.12); color: var(--spruce); border-color: rgba(46, 71, 57, 0.35); }
.kind--potion { background: rgba(58, 92, 128, 0.12); color: #2f4d6b; border-color: rgba(58, 92, 128, 0.35); }
.kind--crafting-part { background: rgba(184, 128, 47, 0.14); color: #8a5f18; border-color: rgba(184, 128, 47, 0.38); }
.kind--gear { background: var(--paper-well); color: var(--ink-soft); }

/* --- shop artwork -------------------------------------------------------- */

/* The picture fills the whole column and is given real height, so the item is
   the thing you see rather than a thumbnail with a caption. Descriptions read
   in the popup now, which is what freed the room. */
button.item--buy .item__frame {
    height: auto;
    aspect-ratio: 1 / 1;
    margin-bottom: 0;
    border-radius: var(--radius-card);
}

/* No padding below the tile: the name is laid over the foot of the artwork,
   so a strip of board under it was empty space that read as a broken card. */
button.item--buy {
    padding: 0;
    overflow: hidden;
}

button.item--buy .item__name,
button.item--buy .stats,
button.item--buy .item__owned {
    padding-inline: 0.7rem;
}

/* ==========================================================================
   Category headings, and the reward badge on the picture.
   ========================================================================== */

/* The category leads the section: large, coloured, countable at a glance. */
.cathead {
    display: flex;
    align-items: center;
    gap: 0.55rem;
    margin: 1.6rem 0 0.8rem;
    padding-bottom: 0.5rem;
    border-bottom: 2px solid var(--cat, var(--line, var(--rule)));
    font-family: var(--font-display);
    font-size: 1.35rem;
    letter-spacing: 0.03em;
    text-transform: uppercase;
    color: var(--cat, var(--ink));
}

.cathead__dot {
    width: 0.85rem;
    height: 0.85rem;
    flex-shrink: 0;
    border-radius: 50%;
    background: var(--cat, var(--ink));
}

.cathead__count {
    margin-left: auto;
    padding: 0.12rem 0.55rem;
    border-radius: 999px;
    background: var(--cat, var(--ink));
    color: #fff;
    font-size: 0.8rem;
}

/* One colour per category, so a set is recognisable before it is read. */
.cathead--bugs { --cat: #6d8a2f; }
.cathead--animals { --cat: #8a5f18; }
.cathead--items { --cat: #2f4d6b; }
.cathead--hotel { --cat: #7a3b6b; }
.cathead--nature { --cat: #2e4739; }
.cathead--other { --cat: #5a6b73; }

/* The reward badge rides on the picture rather than dangling under the card,
   where it read as a stray label belonging to nothing. */
.quest__frame { position: relative; }

.quest__frame .kind {
    position: absolute;
    left: 0.4rem;
    bottom: 0.4rem;
    margin: 0;
    background: rgba(27, 42, 51, 0.86);
    border-color: transparent;
    color: #fff;
    backdrop-filter: blur(2px);
}

.quest__frame .kind--weapon { background: rgba(143, 47, 34, 0.92); }
.quest__frame .kind--armour { background: rgba(46, 71, 57, 0.92); }
.quest__frame .kind--potion { background: rgba(47, 77, 107, 0.92); }
.quest__frame .kind--crafting-part { background: rgba(138, 95, 24, 0.92); }
.quest__frame .kind--quest-key { background: rgba(184, 64, 47, 0.92); }
.quest__frame .kind--map-piece { background: rgba(122, 59, 107, 0.92); }

/* The badge on a tile is a mark, not a label: a small disc in the corner.
   At full width it covered a tenth of the picture and competed with the very
   photo it was sitting on. The name is still there for hover and for screen
   readers; the popup spells it out in words. */
.quest__frame .kind--mark {
    left: auto;
    right: 0.35rem;
    bottom: 0.35rem;
    width: 1.5rem;
    height: 1.5rem;
    padding: 0;
    display: grid;
    place-items: center;
    border-radius: 50%;
    background: rgba(27, 42, 51, 0.55);
    box-shadow: 0 1px 4px rgba(0, 0, 0, 0.25);
}

.quest__frame .kind--mark .kind__icon {
    width: 0.85rem;
    height: 0.85rem;
    stroke-width: 2;
}

/* Colour stays, at a whisper rather than a shout. */
.quest__frame .kind--mark.kind--weapon { background: rgba(143, 47, 34, 0.72); }
.quest__frame .kind--mark.kind--armour { background: rgba(46, 71, 57, 0.72); }
.quest__frame .kind--mark.kind--potion { background: rgba(47, 77, 107, 0.72); }
.quest__frame .kind--mark.kind--crafting-part { background: rgba(138, 95, 24, 0.72); }
.quest__frame .kind--mark.kind--quest-key { background: rgba(184, 64, 47, 0.75); }
.quest__frame .kind--mark.kind--map-piece { background: rgba(122, 59, 107, 0.72); }

/* ==========================================================================
   The workbench.

   Recipes used to complete themselves the instant a hunter held the last
   part — silently, so gear appeared unexplained and most people never learned
   crafting existed. Now it is a place you go, with a list of what you could
   make and what you are still short of.
   ========================================================================== */

/* Real gutter between cards, and real room inside them. Packed tight, the
   recipes read as one striped list and it took a second to see where one
   thing you could make ended and the next began. */
.recipes {
    display: grid;
    gap: 1.5rem;
    margin-bottom: 1.4rem;
}

.recipe {
    width: 100%;
    display: block;
    min-height: 0;
    padding: 0.9rem;
    background: var(--paper-raised);
    border: 1px solid var(--line, var(--rule));
    border-left: 3px solid var(--rock, var(--line));
    border-radius: var(--radius-card);
    box-shadow: 0 1px 2px rgba(31, 26, 20, 0.06);
    /* The card is a <button> now, and inherits the button rule: display face,
       uppercase, wide letter-spacing. None of that belongs on a card of
       prose. */
    font-family: var(--font-body);
    font-size: 1rem;
    font-weight: 400;
    letter-spacing: 0;
    text-transform: none;
    text-align: left;
    color: var(--ink);
    cursor: pointer;
}

/* One you cannot build yet steps back, but only as far as the card. The
   checklist on it is the reason the card is there at all, so it keeps its
   full contrast: greying out the very thing a hunter came to read would
   defeat the point of showing it. */
.recipe--short {
    background: var(--paper-well);
    border-left-color: var(--line, var(--rule));
}

.recipe--short .recipe__name,
.recipe--short .recipe__tally {
    color: var(--ink-soft);
}

.recipe--short .recipe__art {
    opacity: 0.55;
}

.recipe--short .parts {
    background: var(--paper-raised);
}

.recipe--made {
    opacity: 0.72;
    border-left-color: var(--line, var(--rule));
}

.recipe__go {
    margin-left: auto;
    flex-shrink: 0;
    color: var(--ink-soft);
}

.recipe__go svg {
    width: 1.1rem;
    height: 1.1rem;
    fill: none;
    stroke: currentColor;
    stroke-width: 2;
    stroke-linecap: round;
    stroke-linejoin: round;
}

.recipe__short {
    margin: 0 0 0.6rem;
    color: var(--stamp);
    font-weight: 600;
    font-size: 0.9rem;
}

/* What the thing does for you, under the numbers. The pills say "+6 atk";
   this is the line that says what that means when something is hitting you. */
.prize__benefit {
    margin: 0.55rem 0 0.9rem;
    color: var(--ink-soft);
    font-size: 0.95rem;
    line-height: 1.45;
    text-align: center;
}

/* What you are holding, along the top. */
.mats {
    display: grid;
    grid-template-columns: repeat(auto-fill, minmax(4.5rem, 1fr));
    gap: 0.6rem;
    margin: 0 0 1.6rem;
}

.mat { text-align: center; }

.mat__art {
    position: relative;
    aspect-ratio: 1;
    display: grid;
    place-items: center;
    overflow: hidden;
    margin-bottom: 0.3rem;
    background: var(--paper-well);
    border: 1px solid var(--line, var(--rule));
    border-radius: var(--radius);
    color: var(--ink-soft);
}

.mat__art img { width: 100%; height: 100%; object-fit: cover; }

.mat__qty {
    position: absolute;
    right: 0.15rem;
    bottom: 0.15rem;
    padding: 0.05rem 0.3rem;
    border-radius: 999px;
    background: rgba(27, 42, 51, 0.85);
    color: #fff;
    font-size: 0.68rem;
    font-weight: 700;
}

.mat__name {
    display: block;
    font-size: 0.68rem;
    line-height: 1.2;
    color: var(--ink-soft);
}

/* Ready to build reads green before a word is read; already made steps back. */
.recipe--ready { border-left-color: var(--spruce); }

.recipe--made {
    border-left-color: var(--line, var(--rule));
    opacity: 0.72;
}

.recipe__top {
    display: flex;
    align-items: center;
    gap: 0.8rem;
}

/* The parts sit under the heading row, sunk into their own well so the
   checklist reads as the ingredients for the thing above it. Spacing lives
   here rather than on the row above, so a made recipe with no list under it
   does not leave a gap where the list would have been. */
.recipe .parts {
    margin-top: 0.75rem;
    padding: 0.6rem 0.7rem;
    background: var(--paper-well);
    border-radius: var(--radius);
}

.recipe__art {
    width: 3.6rem;
    height: 3.6rem;
    flex-shrink: 0;
    display: grid;
    place-items: center;
    overflow: hidden;
    background: var(--paper-well);
    border-radius: var(--radius);
    color: var(--ink-soft);
}

.recipe__art img { width: 100%; height: 100%; object-fit: cover; }

.recipe__what { flex: 1; min-width: 0; }

.recipe__name {
    display: block;
    font-family: var(--font-display);
    letter-spacing: 0.02em;
    text-transform: uppercase;
}

.recipe__tally {
    display: block;
    margin-top: 0.2rem;
    font-size: 0.8rem;
    color: var(--ink-soft);
    font-variant-numeric: tabular-nums;
}

/* The parts, ticked off — a recipe you cannot build yet is a to-do list.
   Sunk into its own well so the checklist reads as the ingredients for the
   thing above it, rather than as more of the same card. */
.recipe__needs {
    margin: 0 0 0.9rem;
    padding: 0.7rem 0.8rem;
    background: var(--paper-well);
    border-radius: var(--radius);
}

.recipe__needs .eyebrow {
    margin: 0 0 0.5rem;
}

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

.part {
    display: flex;
    align-items: center;
    gap: 0.5rem;
    font-size: 0.88rem;
    color: var(--ink-soft);
}

.part__tick {
    width: 1.1rem;
    height: 1.1rem;
    flex-shrink: 0;
    display: grid;
    place-items: center;
    border-radius: 50%;
    border: 1px solid var(--line, var(--rule));
    font-size: 0.72rem;
    line-height: 1;
}

.part--have { color: var(--ink); }

.part--have .part__tick {
    background: var(--spruce);
    border-color: var(--spruce);
    color: #fff;
}

.recipe__done {
    margin: 0;
    color: var(--spruce);
    font-weight: 600;
    font-size: 0.9rem;
}

/* The moment the parts become the thing. */
.recipe.is-forging {
    animation: forge 0.7s ease-out;
}

.recipe.is-forging .part {
    text-decoration: line-through;
    opacity: 0.5;
    transition: opacity 0.3s ease;
}

@keyframes forge {
    0% { box-shadow: none; }
    35% { box-shadow: 0 0 0 3px rgba(184, 128, 47, 0.75); transform: scale(1.012); }
    100% { box-shadow: none; transform: scale(1); }
}

/* The nudge: something is ready and you have not been told twice. */
.tabs__count--nudge {
    background: var(--spruce);
    color: #fff;
}

@media (prefers-reduced-motion: reduce) {
    .recipe.is-forging { animation: none; }
}

/* Kit groups: what a tool is for, said once above the tiles. */
.kithead {
    margin: 1.4rem 0 0.2rem;
    font-family: var(--font-display);
    font-size: 1.15rem;
    letter-spacing: 0.04em;
    text-transform: uppercase;
}

.kithead__blurb {
    margin: 0 0 0.7rem;
    font-size: 0.86rem;
    line-height: 1.4;
    color: var(--ink-soft);
}

/* The boss you cannot fight yet: shown, but plainly out of reach. Turning a
   hunter away with only a checklist made the walk feel wasted — seeing the
   thing is the reason to go back for what it wants. */
.plate--locked {
    position: relative;
}

.plate--locked img {
    filter: grayscale(0.7) brightness(0.72);
}

/* No badge stamped across its face. A pill reading NOT YET sat dead centre
   over the portrait, hiding the one thing the screen exists to show, and
   said nothing the card below it does not say better. The drained colour
   is the signal. */

/* Sound off is a state worth seeing from across a table: the crossed-out
   speaker turns red, so nobody spends the afternoon wondering why the app is
   silent. sound.js already swaps the two icons via [data-on]/[data-off]. */
/* Sound off: the crossed-out speaker swaps in (sound.js toggles the two via
   [hidden]) and the icon itself goes red. The disc stays exactly as it is on
   the other two tools, so muting does not make one corner icon a different
   shape from its neighbours. Filling the whole button red was the thing that
   looked wrong, not the disc. */
.bar__tools .mutebtn[aria-pressed="true"] svg,
.bar__tools .mutebtn[aria-pressed="true"] .navico {
    stroke: #b8402f;
}

/* The companion row IS the swap control now, so nothing competes for the same
   line. A button crammed in beside the portrait and the blurb squeezed both
   on a narrow screen. */
button.sidekick--mine {
    width: 100%;
    text-align: left;
    font: inherit;
    color: inherit;
    cursor: pointer;
}

button.sidekick--mine:hover,
button.sidekick--mine:focus-visible {
    border-color: var(--spruce);
}

.sidekick--mine > div {
    flex: 1;
    min-width: 0;
}

.sidekick__go {
    flex-shrink: 0;
    width: 1.1rem;
    height: 1.1rem;
    color: var(--ink-soft);
}

.sidekick__go svg {
    width: 100%;
    height: 100%;
    fill: none;
    stroke: currentColor;
    stroke-width: 2;
    stroke-linecap: round;
    stroke-linejoin: round;
}

#swapPal .companions {
    margin: 0.6rem 0 0;
}

/* The `hidden` attribute must actually hide things.
 *
 * A UA stylesheet applies `display: none` to [hidden] at the lowest possible
 * specificity, so any rule of ours that sets `display` on the same element
 * silently beats it. That is exactly what happened to the sound button: its
 * two icons are toggled by adding and removing `hidden`, and
 * `.mutebtn svg { display: block }` kept both of them on screen at once.
 * Fixing it here rather than per-component, because the same trap is waiting
 * for every other thing we toggle this way. */
[hidden] {
    display: none !important;
}

/* Boss art gets a ceiling. A tall portrait pushed the health bar, the log and
   every action button below the fold, so the fight happened somewhere you
   could not see without scrolling. Capped against the viewport rather than a
   fixed pixel height, so it holds up on a phone and on a laptop. */
/* Top-aligned, and faded out rather than cut off.
 *
 * A hard crop through the middle of a tall portrait looks like a mistake —
 * the picture appears to have been guillotined. Anchoring to the top keeps
 * the face, which is the part worth seeing, and the bottom dissolves into
 * the page so the crop reads as deliberate. The mask only bites when the
 * artwork is actually taller than the room it has. */
/* The whole boss, always — never a crop.
 *
 * This box is measured by fit.js and shrunk to whatever the screen has left,
 * and the image used to be `height: auto` inside it, so the container simply
 * cut the bottom off the artwork. Letting the image fill the box and contain
 * itself means it scales down instead of being sliced, at any screen height. */
.bossart {
    min-height: 0;
    overflow: hidden;
    /* No border or plate: the portrait is the thing, not a framed exhibit. */
    border: 0;
    background: transparent;
}

.bossart img {
    /* The container is a centring grid, so the image has to be told to
       stretch into its cell — left to itself it sits at natural size and the
       box simply clips it, which is what cut the boss off at the knees. */
    align-self: stretch;
    justify-self: stretch;
    width: 100%;
    height: 100%;
    max-height: 100%;
    min-height: 0;
    object-fit: contain;
    object-position: center bottom;
}

.plate--boss img {
    max-height: 34vh;
    width: 100%;
    object-fit: cover;
    object-position: center center;
}

/* The attack is the thing you press most, so it looks like the main action
   rather than one row in a list. */
/* Neutral, not red. Red is this app's colour for danger and for locked
   things, so a red Attack button read as one you were not allowed to press.
   Weight and a solid ground carry "this is the main action" instead. */
.move--attack {
    border-color: rgba(20, 32, 43, 0.28);
    background: rgba(20, 32, 43, 0.06);
    font-weight: 600;
}

.move--attack .move__dmg {
    color: var(--ink-soft, #3D4E5C);
}

.move__swap {
    display: block;
    width: 100%;
    margin: -0.2rem 0 0.6rem;
    padding: 0.3rem;
    border: 0;
    background: none;
    color: var(--ink-soft);
    font: inherit;
    font-size: 0.8rem;
    text-decoration: underline;
    cursor: pointer;
}

/* A finished marker should read as green from arm's length. The spruce used
   everywhere else is nearly black at dot size — on the map it looked like an
   unfinished marker in shadow rather than one that was done. */
.dot--found,
.dot--found.dot--boss {
    background: #2f9e4f;
    color: #fff;
    box-shadow: 0 0 0 2px rgba(47, 158, 79, 0.28);
}

/* --- the bag drawer, steadied --------------------------------------------
   It jumped: the panel opened at the height of one line of placeholder text,
   then snapped to full height when the contents arrived, and the grid inside
   inherited page-width sizing it did not have room for. */

#bagDrawer .sheet__panel {
    /* Opens at a settled size and stays there while the contents load. */
    min-height: 60vh;
    max-height: 86vh;
    display: flex;
    flex-direction: column;
}

#bagDrawer [data-bag-body] {
    flex: 1;
    overflow-y: auto;
    -webkit-overflow-scrolling: touch;
}

/* Cards inside a drawer are smaller than cards on a page. */
#bagDrawer .bag {
    grid-template-columns: repeat(2, 1fr);
    gap: 0.7rem;
    margin-bottom: 0.5rem;
}

#bagDrawer .item { padding: 0.7rem; }
#bagDrawer .item__frame { height: 5.5rem; }
#bagDrawer .item__from { font-size: 0.78rem; }
#bagDrawer .notewrap { margin: 0.6rem 0 0; }

/* The placeholder should not look like content. */
#bagDrawer [data-bag-body] > .notewrap:only-child {
    display: grid;
    place-items: center;
    min-height: 40vh;
    color: var(--ink-soft);
}

/* --- picking a companion on a phone ---------------------------------------
   The swap drawer reused the setup grid, which assumes a full page: two tall
   cards a row, so choosing between seven meant scrolling a dialog blind. In
   the drawer they become rows you can read down at a glance. */

#swapPal .sheet__panel {
    max-height: 86vh;
    display: flex;
    flex-direction: column;
}

#swapPal .companions {
    display: block;
    flex: 1;
    overflow-y: auto;
    -webkit-overflow-scrolling: touch;
    margin: 0.6rem 0 0.9rem;
}

#swapPal .companion {
    display: flex;
    align-items: center;
    gap: 0.75rem;
    text-align: left;
    padding: 0.6rem;
    margin-bottom: 0.5rem;
}

#swapPal .companion__art {
    width: 3rem;
    height: 3rem;
    flex-shrink: 0;
    aspect-ratio: 1 / 1;
    margin: 0;
    border-radius: 50%;
    object-fit: cover;
}

#swapPal .companion .item__name {
    margin: 0;
    flex: 1;
    min-width: 0;
}

#swapPal .companion .stats {
    justify-content: flex-end !important;
    margin: 0;
    flex-shrink: 0;
}

/* The one you are carrying, marked clearly rather than by a hairline. */
#swapPal .companion:has(input:checked) {
    background: rgba(47, 158, 79, 0.1);
    border-color: #2f9e4f;
    box-shadow: inset 0 0 0 1px #2f9e4f;
}

/* Submit stays put while the list scrolls under it. */
#swapPal .btn { margin: 0; flex-shrink: 0; }

/* ==========================================================================
   The map takes the screen, and the tab bar stays put.
   ========================================================================== */

/* The clue moved into a drawer, so the map gets the height that panel used to
   occupy. Sized against the viewport minus the header, the code bar and the
   tab bar, so the whole map is visible without scrolling the page at all. */
.atlas {
    margin: 0 0 0.6rem;
}

.atlas__frame {
    height: calc(100dvh - 15rem);
    min-height: 20rem;
}

/* The tab bar is the way around the app, so it stays on screen rather than
   waiting at the bottom of a long page. */
/* And the bottom bar stays put too.
   Sticky meant it sat in the flow at the end of the document, so a long page
   (the standings, with everybody on it) pushed it down past the fold and you
   had to scroll to the bottom to change screens. Fixed, it is always there;
   `main` already reserves --bar-height of padding for exactly this. */
.tabs {
    position: fixed;
    left: 0;
    right: 0;
    bottom: 0;
    z-index: 20;
    background: var(--paper-raised);
    border-top: 1px solid var(--line, var(--rule));
    /* Clear of the home indicator on a phone. */
    padding-bottom: max(0.25rem, env(safe-area-inset-bottom));
}

/* The clue drawer is short — it is one or two lines, not a page. Its own
   bottom padding, because the shared drawer padding leaves the last line
   sitting right on the edge when there is so little above it. */
#readout .sheet__panel {
    min-height: 0;
    padding-bottom: max(1.6rem, calc(env(safe-area-inset-bottom) + 1.2rem));
}

.readout__title {
    font-family: var(--font-display);
    letter-spacing: 0.03em;
    text-transform: uppercase;
}

/* Clues are written as riddles, in lines. The line breaks are the rhythm of
   the thing, so they are kept rather than collapsed into a paragraph. */
.readout__text {
    white-space: pre-line;
}

/* Both ways of answering, right under the riddle. Reading a clue and then
   having to close it, find the box at the top of the screen and start typing
   put three steps between knowing the answer and giving it. */
.readout__do {
    display: flex;
    gap: 0.5rem;
    margin-top: 1rem;
}

/* The code box, opened by the button above it. code.js closes it on load, so
   with nothing running it stays open and still works. */
.readout__code {
    margin-top: 0.6rem;
}

.readout__do .btn {
    flex: 1;
    padding-inline: 0.7rem;
    font-size: 0.82rem;
    letter-spacing: 0.06em;
}

.scanico {
    width: 1.1em;
    height: 1.1em;
    flex-shrink: 0;
    fill: none;
    stroke: currentColor;
    stroke-width: 1.8;
    stroke-linecap: round;
    stroke-linejoin: round;
}

/* The scanner. A viewfinder you are holding up at a sticker on a wall wants
   the whole phone, so this is its own screen rather than a corner of the
   clue drawer. */
.scan__panel {
    max-width: 30rem;
}

.scan__view {
    position: relative;
    aspect-ratio: 1;
    max-height: 60vh;
    margin: 0.6rem 0 0.8rem;
    overflow: hidden;
    background: #14110d;
    border-radius: var(--radius);
}

.scan__view video {
    width: 100%;
    height: 100%;
    object-fit: cover;
    display: block;
}

/* Corner brackets: something to line the square up inside, so nobody has to
   guess how close to hold the phone. */
.scan__frame {
    position: absolute;
    inset: 18%;
    border-radius: 0.5rem;
    box-shadow: 0 0 0 100vmax rgba(20, 17, 13, 0.45);
}

.scan__frame::before,
.scan__frame::after {
    content: "";
    position: absolute;
    inset: 0;
    border: 3px solid #f2efe6;
    border-radius: 0.4rem;
}

.scan__frame::before {
    clip-path: polygon(0 0, 34% 0, 34% 3px, 3px 3px, 3px 34%, 0 34%,
                       100% 34%, 100% 0, 66% 0, 66% 3px, calc(100% - 3px) 3px,
                       calc(100% - 3px) 34%, 100% 34%);
}

.scan__frame::after {
    transform: rotate(180deg);
    clip-path: polygon(0 0, 34% 0, 34% 3px, 3px 3px, 3px 34%, 0 34%,
                       100% 34%, 100% 0, 66% 0, 66% 3px, calc(100% - 3px) 3px,
                       calc(100% - 3px) 34%, 100% 34%);
}

.scan__note {
    margin: 0 0 0.5rem;
    text-align: center;
    font-size: 0.9rem;
    color: var(--ink-soft);
}

.readout--done .readout__title { color: #2f9e4f; }

/* The clue drawer's heading: a state badge sitting next to the title, and an
   even rhythm down the panel so the riddle, its payout and the answer buttons
   read as one stack rather than three loose blocks. */
#readout .sheet__head {
    align-items: center;
    margin-bottom: 0.75rem;
}

.readout__heading {
    display: flex;
    align-items: center;
    gap: 0.55rem;
    min-width: 0;
}

.readout__heading .readout__title {
    margin: 0;
    font-size: 0.9rem;
    letter-spacing: 0.08em;
}

.readout__icon {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    width: 1.7rem;
    height: 1.7rem;
    flex-shrink: 0;
    border-radius: 999px;
    font-family: var(--font-display);
    font-size: 0.95rem;
    font-weight: 700;
    line-height: 1;
    background: var(--paper-well);
    color: var(--rock);
}

/* Empty before any marker is tapped, so it takes no space then. */
.readout__icon:empty { display: none; }

.readout__icon svg {
    width: 1rem;
    height: 1rem;
    fill: none;
    stroke: currentColor;
    stroke-width: 1.8;
    stroke-linecap: round;
    stroke-linejoin: round;
}

.readout__icon--found  { background: rgba(47, 158, 79, 0.16); color: var(--spruce); }
.readout__icon--open   { background: rgba(44, 108, 174, 0.16); color: #2c6cae; }
.readout__icon--boss   { background: rgba(178, 58, 42, 0.15); color: var(--stamp); }
.readout__icon--sealed { background: var(--paper-well); color: var(--rock); }

#readout .readout__text { margin: 0; }
#readout .readout__earned { margin: 0.5rem 0 0; }
#readout .readout__do { margin-top: 0.9rem; }
#readout .readout__code { margin-top: 0.6rem; }

/* An item that opens a door rather than winning a fight. Without this it had
   an empty stat row, which reads as "this does nothing". */
.stat-pill--special {
    background: rgba(47, 77, 107, 0.14);
    color: #2f4d6b;
}

/* ==========================================================================
   A scene: what is happening at the top, what you can do at the bottom.

   Everything used to stack in reading order, so on a short passage the
   actions floated up under the text and on a long one they were below the
   fold. Fixing them to the foot means a hunter's thumb always knows where to
   go, and the story always starts at the top.
   ========================================================================== */

/* A scene is exactly one screen — never more.
 *
 * This was a min-height, so a long passage grew the page and the last line
 * of the story ended up underneath the tab bar with the actions pushed off
 * the bottom. It is a fixed height now: the viewport less the header, the
 * rule and the tab bar. The story takes what room is left after the actions
 * have theirs and scrolls inside its own box if it is longer than that, so
 * the page itself never scrolls and the choices are always on screen. */
.scene {
    display: flex;
    flex-direction: column;
    height: calc(100dvh - 11rem - env(safe-area-inset-bottom, 0px));
}

/* Main reserves room under every other page so the tab bar never covers the
   last line. A fitted screen — a scene, a fight — already stops short of the
   bar by measurement, so that reserved strip is pure overflow: it was the
   last thing making these pages scroll. */
main:has(> [data-fit]) {
    padding-bottom: 0;
}

/* Onboarding has no header above it, so the first line would otherwise start
   hard against the top of the phone. A little air instead. */
main.main--naked {
    padding-top: 30px;
}

/* ==========================================================================
   The fight's two doors: swing, or use something.
   ========================================================================== */

.acts {
    display: grid;
    grid-template-columns: 1fr 1fr;
    gap: 0.6rem;
    margin: 0 0 0.5rem;
}

.act {
    display: flex;
    flex-direction: column;
    align-items: center;
    justify-content: center;
    gap: 0.15rem;
    min-height: 4.2rem;
    padding: 0.7rem 0.6rem;
    border: 1px solid var(--rule);
    border-radius: var(--radius-card);
    background: var(--paper-raised);
    color: var(--ink);
    cursor: pointer;
}

.act svg { width: 1.4rem; height: 1.4rem; }

.act__label {
    font-family: var(--font-display);
    font-size: 1rem;
    font-weight: 700;
    letter-spacing: 0.07em;
    text-transform: uppercase;
    line-height: 1;
}

.act__sub {
    font-size: 0.72rem;
    color: var(--ink-soft, #3D4E5C);
}

.act:disabled { opacity: 0.45; cursor: not-allowed; }

.runaway {
    display: block;
    width: 100%;
    margin: 0.15rem 0 0;
    padding: 0.5rem;
    border: 0;
    background: none;
    color: var(--ink-soft, #3D4E5C);
    font-size: 0.8rem;
    cursor: pointer;
}

/* The cards inside either drawer: a picture, what it is, what it does, and
   what it is worth. Same object whether it is a weapon or a bottle. */
.usegrid {
    display: grid;
    grid-template-columns: repeat(2, 1fr);
    gap: 0.6rem;
}

.usecard {
    position: relative;
    display: flex;
    flex-direction: column;
    align-items: flex-start;
    gap: 0.2rem;
    width: 100%;
    height: 100%;
    padding: 0.6rem;
    text-align: left;
    border: 1px solid var(--rule);
    border-radius: var(--radius-card);
    background: var(--paper-raised);
    color: var(--ink);
    cursor: pointer;
}

.usecard:disabled { opacity: 0.45; cursor: not-allowed; }

.usecard__art {
    position: relative;
    display: grid;
    place-items: center;
    width: 100%;
    aspect-ratio: 1 / 1;
    margin-bottom: 0.25rem;
    overflow: hidden;
    border-radius: var(--radius);
    background: var(--paper-well);
    color: var(--rock, #6b6257);
}

.usecard__art img { width: 100%; height: 100%; object-fit: cover; display: block; }
.usecard__art svg { width: 1.7rem; height: 1.7rem; }

.usecard__qty {
    position: absolute;
    right: 0.25rem;
    bottom: 0.25rem;
    padding: 0.1rem 0.35rem;
    border-radius: 999px;
    background: rgba(20, 32, 43, 0.82);
    color: #fff;
    font-size: 0.7rem;
    font-weight: 700;
    font-variant-numeric: tabular-nums;
}

.usecard__name {
    font-family: var(--font-display);
    font-size: 0.86rem;
    font-weight: 700;
    letter-spacing: 0.03em;
    line-height: 1.15;
}

.usecard__does {
    font-size: 0.72rem;
    line-height: 1.35;
    color: var(--ink-soft, #3D4E5C);
    /* Two lines at most: a long description must not make one card twice the
       height of the one beside it. */
    display: -webkit-box;
    -webkit-line-clamp: 2;
    -webkit-box-orient: vertical;
    overflow: hidden;
}

.usecard__tag {
    margin-top: auto;
    padding-top: 0.3rem;
    font-size: 0.72rem;
    font-weight: 700;
}

.usecard__tag--atk { color: #b8402f; }
.usecard__tag--use { color: var(--spruce); }

/* The hunt, held. One notice on an otherwise empty screen. */
.paused {
    display: flex;
    flex-direction: column;
    align-items: center;
    text-align: center;
    gap: 0.5rem;
    padding: 2.4rem 1.2rem;
    max-width: 26rem;
    margin: 0 auto;
}

.paused__mark {
    display: grid;
    place-items: center;
    width: 4.2rem;
    height: 4.2rem;
    margin-bottom: 0.4rem;
    border-radius: 50%;
    background: var(--paper-well);
    border: 1px solid var(--line, var(--rule));
    color: var(--rock, #6b6257);
}

.paused__mark svg { width: 2.1rem; height: 2.1rem; }

.paused .screenhead { margin: 0; }

.paused__notice {
    margin: 0.3rem 0 0;
    font-size: 1.05rem;
    line-height: 1.5;
}

.paused__hint {
    margin: 0.9rem 0 0;
    font-size: 0.86rem;
    line-height: 1.5;
    color: var(--ink-soft, #3D4E5C);
}

/* So a hunt is never left paused by accident. */
.pausebar {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 1rem;
    flex-wrap: wrap;
    margin: 0 0 1rem;
    padding: 0.7rem 0.9rem;
    background: rgba(184, 64, 47, 0.1);
    border: 1px solid rgba(184, 64, 47, 0.45);
    border-radius: var(--radius);
    font-weight: 600;
}

/* The heading and any artwork keep their size. */
.scene > .eyebrow,
.scene > .screenhead,
.scene > .plate,
.scene > .gate {
    flex: 0 0 auto;
}

/* The story takes the leftover room and scrolls within it. When a boss is
   speaking, the portrait and name stay put and only the line scrolls — you
   should always be able to see who is talking. */
.scene > .passage {
    flex: 0 1 auto;
    min-height: 0;
    overflow-y: auto;
    overscroll-behavior: contain;
}

.scene > .speech {
    flex: 0 1 auto;
    min-height: 0;
    display: flex;
    flex-direction: column;
}

.scene > .speech .speech__line {
    min-height: 0;
    overflow-y: auto;
    overscroll-behavior: contain;
}

/* The actions sit at the foot, always, at their full size. */
.scene > form,
.scene > .btn,
.scene > .moves {
    flex: 0 0 auto;
    margin-top: auto;
}

/* Only the first of them gets pushed down — the rest follow it. */
.scene > form ~ .btn,
.scene > .btn ~ .btn,
.scene > form ~ form {
    margin-top: 0.5rem;
}

.scene > form .option:last-child {
    margin-bottom: 0;
}

/* ==========================================================================
   The trophy shelf.

   Every boss has a card, whether or not it has been met. A beaten one shows
   the boss on its back with a seal; one you have stood in front of shows it
   upright; one you have never met is a silhouette with no name, because
   knowing there is something up there is the hook.
   ========================================================================== */

.trophies {
    display: grid;
    grid-template-columns: repeat(auto-fill, minmax(7.5rem, 1fr));
    gap: 0.7rem;
    margin: 0 0 1.4rem;
}

.trophy {
    text-align: center;
    padding: 0.6rem 0.5rem 0.7rem;
    background: var(--paper-raised);
    border: 1px solid var(--line, var(--rule));
    border-radius: var(--radius-card);
}

/* A beaten one is a button. It inherits the button rule, which is display
   face and uppercase, and none of that belongs on a tile of prose. */
button.trophy {
    width: 100%;
    min-height: 0;
    display: block;
    font-family: var(--font-body);
    font-size: 1rem;
    font-weight: 400;
    letter-spacing: 0;
    text-transform: none;
    color: var(--ink);
    cursor: pointer;
}

/* Where it was, under the name in the popup. */
.prize__where {
    margin: -0.4rem 0 0.8rem;
    font-size: 0.85rem;
    color: var(--ink-soft);
}

.trophy__art {
    position: relative;
    aspect-ratio: 1 / 1;
    margin-bottom: 0.45rem;
    border-radius: var(--radius);
    overflow: hidden;
    background: var(--paper-well);
    display: grid;
    place-items: center;
}

.trophy__art img {
    width: 100%;
    height: 100%;
    object-fit: cover;
    object-position: center center;
}

.trophy__mark {
    font-family: var(--font-display);
    font-size: 2rem;
    color: var(--rock, var(--ink-soft));
}

.trophy__name {
    display: block;
    font-family: var(--font-display);
    font-size: 0.82rem;
    letter-spacing: 0.02em;
    text-transform: uppercase;
    line-height: 1.2;
}

.trophy__when {
    display: block;
    margin-top: 0.15rem;
    font-size: 0.72rem;
    color: var(--ink-soft);
}

/* --- beaten --------------------------------------------------------------- */

.trophy--won {
    border-color: #c9a227;
    box-shadow: 0 0 0 1px rgba(201, 162, 39, 0.35), 0 2px 10px rgba(201, 162, 39, 0.18);
    background: linear-gradient(180deg, rgba(201, 162, 39, 0.1), var(--paper-raised) 60%);
}

.trophy--won .trophy__name { color: #8a6d13; }

/* A struck seal in the corner, the way a stamp lands in the book. */
.trophy__seal {
    position: absolute;
    right: 0.3rem;
    bottom: 0.3rem;
    width: 1.5rem;
    height: 1.5rem;
    display: grid;
    place-items: center;
    border-radius: 50%;
    background: #c9a227;
    color: #fff;
    font-size: 0.85rem;
    font-weight: 700;
    box-shadow: 0 1px 4px rgba(0, 0, 0, 0.3);
}

/* The walk-through. One screen at a time: story at the top, the way on at
   the foot, and the tab bar hidden for the whole sequence. */
.tut {
    display: flex;
    flex-direction: column;
    position: relative;
}

/* The story takes the leftover room and scrolls inside it, so the button
   never leaves the screen no matter how much a companion has to say. */
.tut__body {
    flex: 1 1 auto;
    min-height: 0;
    overflow-y: auto;
    overscroll-behavior: contain;
}

.tut__para {
    margin: 0 0 0.9rem;
    line-height: 1.6;
}

/* Back, as a mark in the corner rather than a button in the flow — it is the
   smallest action on the screen and should look it. */
.backdot {
    position: absolute;
    top: -0.15rem;
    right: 0;
    z-index: 2;
    display: grid;
    place-items: center;
    width: 2.2rem;
    height: 2.2rem;
    border-radius: 50%;
    background: var(--paper-well);
    border: 1px solid var(--line, var(--rule));
    color: var(--ink-soft);
    text-decoration: none;
}

.backdot svg {
    width: 1.1rem;
    height: 1.1rem;
    fill: none;
    stroke: currentColor;
    stroke-width: 2;
    stroke-linecap: round;
    stroke-linejoin: round;
}

/* Artwork: the real map, the real keys. Drawings of an imaginary hunt would
   teach a hunter the wrong thing to look for. */
.tut__art {
    position: relative;
    margin: 0 0 1rem;
}

.tut__art img {
    width: 100%;
    /* The whole map, scaled to fit — not a crop through the middle of it.
       `cover` at a fixed 30vh sliced the top and bottom off the artwork, and
       once onboarding lost its header the taller fitted body made that crop
       worse rather than better. Contain keeps the map readable at any height,
       and the well behind it fills whatever it does not use. */
    max-height: 34vh;
    object-fit: contain;
    object-position: center;
    background: var(--paper-well);
    border: 1px solid var(--line, var(--rule));
    border-radius: var(--radius);
}

.tut__art figcaption {
    margin-top: 0.4rem;
    font-size: 0.8rem;
    color: var(--ink-soft);
    text-align: center;
}

/* Three example markers laid over the map, so the states are learned here
   rather than guessed at later. */
.tut__pin {
    position: absolute;
    display: grid;
    place-items: center;
    width: 1.7rem;
    height: 1.7rem;
    border-radius: 50%;
    font-size: 0.8rem;
    font-weight: 700;
    color: #fff;
    box-shadow: 0 2px 6px rgba(0, 0, 0, 0.35);
}

.tut__pin--found  { left: 18%; top: 30%; background: #2f9e4f; }
.tut__pin--open   { left: 46%; top: 55%; background: var(--stamp); }
.tut__pin--sealed { left: 72%; top: 26%; background: var(--rock, #6b6257); font-size: 0.7rem; }

.tut__art--row img { max-height: none; }

.tut__keys {
    display: flex;
    justify-content: center;
    gap: 0.6rem;
}

.tut__key {
    flex: 1 1 0;
    min-width: 0;
    text-align: center;
}

.tut__key img {
    width: 100%;
    aspect-ratio: 1;
    object-fit: cover;
    border-radius: var(--radius);
    border: 1px solid var(--line, var(--rule));
}

.tut__key span {
    display: block;
    margin-top: 0.3rem;
    font-size: 0.72rem;
    line-height: 1.2;
    color: var(--ink-soft);
}

/* The one practical line on each screen, set apart from the story so a
   hunter skimming for "what do I actually do" finds it. */
.tut__note {
    display: flex;
    gap: 0.5rem;
    margin: 1rem 0 0;
    padding: 0.6rem 0.75rem;
    background: var(--paper-well);
    border-radius: var(--radius);
    font-size: 0.9rem;
    color: var(--ink-soft);
}

.tut__note::before {
    content: "→";
    flex-shrink: 0;
    color: var(--stamp);
    font-weight: 700;
}

/* Your companion, chipping in. Dark, like the speech bubble a boss gets in a
   scene, because it is the same thing: somebody talking rather than the app
   explaining. */
.tut__says {
    display: flex;
    align-items: flex-start;
    gap: 0.65rem;
    margin: 1rem 0 0;
    padding: 0.7rem 0.85rem;
    background: var(--ink);
    border-radius: var(--radius-card);
    color: var(--paper);
}

.tut__says img,
.tut__says-none {
    width: 2.4rem;
    height: 2.4rem;
    flex-shrink: 0;
    border-radius: 50%;
    object-fit: cover;
    display: grid;
    place-items: center;
    background: var(--paper-well);
    border: 2px solid var(--stamp);
    color: var(--ink-soft);
    font-family: var(--font-display);
}

.tut__says p {
    margin: 0;
    font-size: 0.92rem;
    line-height: 1.45;
}

.tut__says strong {
    display: block;
    margin-bottom: 0.15rem;
    font-family: var(--font-display);
    font-size: 0.72rem;
    font-weight: 700;
    letter-spacing: 0.09em;
    text-transform: uppercase;
    color: var(--stamp);
}

/* The foot: where you are, and the way on. Clear of the bottom of the phone,
   since there is no tab bar here to hold it up. */
.tut__foot {
    flex: 0 0 auto;
    margin-top: auto;
    padding-top: 1rem;
    padding-bottom: max(1rem, env(safe-area-inset-bottom));
}

.tut__foot .steps {
    margin: 0 0 0.9rem;
}

/* Your face is the control for changing your face. A separate "change
   picture" button would be a second thing to aim at when the picture is
   already the obvious target. */
.profile__face--edit {
    position: relative;
    cursor: pointer;
}

.profile__cam {
    position: absolute;
    right: -0.1rem;
    bottom: -0.1rem;
    display: grid;
    place-items: center;
    width: 1.9rem;
    height: 1.9rem;
    border-radius: 50%;
    background: var(--ink);
    color: var(--paper);
    border: 2px solid var(--paper);
}

.profile__cam svg {
    width: 1rem;
    height: 1rem;
    fill: none;
    stroke: currentColor;
    stroke-width: 1.8;
    stroke-linecap: round;
    stroke-linejoin: round;
}

.profile__save {
    margin-top: 0.9rem;
}

/* The old map, piece by piece. What you have is artwork; what you have not
   is a padlock — showing a dimmed picture of a piece you have not found
   would spend the thing the search is for. */
.pieces {
    display: grid;
    grid-template-columns: repeat(auto-fill, minmax(6rem, 1fr));
    gap: 0.7rem;
    margin: 0 0 1.4rem;
}

.piece {
    text-align: center;
    padding: 0.5rem 0.4rem 0.6rem;
    background: var(--paper-raised);
    border: 1px solid var(--line, var(--rule));
    border-radius: var(--radius-card);
}

.piece--have { border-color: rgba(201, 162, 39, 0.55); }

.piece--locked {
    background: var(--paper-well);
    border-style: dashed;
}

.piece__art {
    display: grid;
    place-items: center;
    aspect-ratio: 1;
    overflow: hidden;
    margin-bottom: 0.4rem;
    background: var(--paper-well);
    border-radius: var(--radius);
    color: var(--ink-soft);
}

.piece__art img {
    width: 100%;
    height: 100%;
    object-fit: cover;
}

.piece__mark { font-size: 1.6rem; }

.piece__lock {
    width: 1.5rem;
    height: 1.5rem;
    fill: none;
    stroke: currentColor;
    stroke-width: 1.7;
    stroke-linecap: round;
    stroke-linejoin: round;
    opacity: 0.55;
}

.piece__name {
    display: block;
    font-size: 0.74rem;
    line-height: 1.25;
    color: var(--ink-soft);
}

.piece--have .piece__name {
    color: var(--ink);
    font-weight: 600;
}

/* The badge handed over on the map, the moment you get back from a fight.
   Same seal as the shelf card it is about to become, so the popup and the
   trophy read as the same object. */
.badge__art {
    position: relative;
    width: 8.5rem;
    height: 8.5rem;
    margin: 0 auto 0.9rem;
    display: grid;
    place-items: center;
    overflow: hidden;
    border-radius: 50%;
    background: var(--paper-well);
    border: 3px solid #c9a227;
    box-shadow: 0 3px 14px rgba(31, 26, 20, 0.2);
}

.badge__art img {
    width: 100%;
    height: 100%;
    object-fit: cover;
}

.badge__mark {
    font-size: 3rem;
    color: #c9a227;
}

.badge__seal {
    position: absolute;
    right: 0.4rem;
    bottom: 0.4rem;
    width: 1.9rem;
    height: 1.9rem;
    display: grid;
    place-items: center;
    border-radius: 50%;
    background: #c9a227;
    color: #fff;
    font-size: 1rem;
    font-weight: 700;
    box-shadow: 0 1px 4px rgba(0, 0, 0, 0.3);
}

/* --- met, still standing -------------------------------------------------- */

.trophy--met { border-color: rgba(184, 64, 47, 0.45); }
.trophy--met .trophy__art img { filter: saturate(0.85); }
.trophy--met .trophy__when { color: #8f2f22; }

/* --- never met ------------------------------------------------------------ */

.trophy--hidden { border-style: dashed; }

.trophy--hidden .trophy__art {
    background: repeating-linear-gradient(
        45deg,
        var(--paper-well),
        var(--paper-well) 8px,
        var(--paper-raised) 8px,
        var(--paper-raised) 16px
    );
}

.trophy--hidden .trophy__name,
.trophy--hidden .trophy__when { color: var(--ink-soft); }

/* ==========================================================================
   A scene told a line at a time, and a boss going down.
   ========================================================================== */

/* Each sentence fades up in turn, so a passage reads rather than lands. */
.scene__line {
    display: inline;
    opacity: 0;
    animation: scene-line 0.5s ease-out forwards;
}

/* A passage the author broke into lines keeps them: two people talking are
   written a line each, and a conversation set as one paragraph reads as one
   voice. */
.scene__line--own {
    display: block;
}

.scene__line--own + .scene__line--own {
    margin-top: 0.5rem;
}

@keyframes scene-line {
    from { opacity: 0; transform: translateY(0.35rem); }
    to { opacity: 1; transform: translateY(0); }
}

/* The choice waits until the passage has finished being read. Hidden with
   visibility rather than display so the page does not jump when it arrives. */
[data-scene-actions] {
    visibility: hidden;
    opacity: 0;
    transition: opacity 0.35s ease-out;
}

[data-scene-actions].is-ready {
    visibility: visible;
    opacity: 1;
}

/* No JavaScript, no staggering — and the actions must never be stranded. */
.no-js [data-scene-actions] {
    visibility: visible;
    opacity: 1;
}

/* The boss leaves the screen when it goes down. */
.bossart.is-defeated {
    animation: boss-defeated 0.7s ease-in forwards;
    pointer-events: none;
}

@keyframes boss-defeated {
    0% { opacity: 1; transform: translateY(0) scale(1); filter: none; }
    30% { transform: translateY(-0.5rem) scale(1.02); filter: brightness(1.4); }
    100% { opacity: 0; transform: translateY(2.5rem) scale(0.94); }
}

.confetti {
    position: fixed;
    inset: 0;
    width: 100%;
    height: 100%;
    pointer-events: none;
    z-index: 9999;
}

@media (prefers-reduced-motion: reduce) {
    .scene__line { opacity: 1; animation: none; }
    [data-scene-actions] { visibility: visible; opacity: 1; }
    .bossart.is-defeated { animation: none; opacity: 0; }
}

/* ==========================================================================
   Standings.

   Everyone on the mountain, in order. The old version was a name and a
   number per row, which on a hunt of eight people who all know each other
   read as a spreadsheet: nobody is "Q. Whitaker, 38" to their friends, they
   are the one with the marmot.
   ========================================================================== */

.ranks {
    list-style: none;
    margin: 1.1rem 0 1.6rem;
    padding: 0;
    display: grid;
    gap: 0.5rem;
}

.rank {
    display: grid;
    grid-template-columns: 1.6rem 2.6rem 1fr auto;
    align-items: center;
    gap: 0.7rem;
    padding: 0.6rem 0.75rem;
    background: var(--paper-raised);
    border: 1px solid var(--rule);
    border-radius: var(--radius-card);
}

/* The top three, without medals or confetti: a heavier edge is enough. */
.rank--top {
    border-color: var(--rock);
}

.rank--you {
    border-color: var(--stamp);
    background: var(--paper-well);
}

.rank__place {
    font-family: var(--font-display);
    font-size: 1.15rem;
    font-weight: 700;
    color: var(--rock);
    text-align: center;
}

.rank--top .rank__place { color: var(--ink); }

.rank__face {
    width: 2.6rem;
    height: 2.6rem;
    display: grid;
    place-items: center;
    overflow: hidden;
    border-radius: 50%;
    background: var(--paper-well);
    border: 1px solid var(--rule);
    font-family: var(--font-display);
    font-size: 1.1rem;
    font-weight: 700;
    color: var(--ink-soft);
}

.rank__face img {
    width: 100%;
    height: 100%;
    object-fit: cover;
}

.rank__who {
    display: grid;
    gap: 0.1rem;
    min-width: 0;
}

.rank__name {
    font-family: var(--font-display);
    font-size: 1.05rem;
    line-height: 1.15;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}

.rank__you {
    margin-left: 0.35rem;
    font-size: 0.7rem;
    letter-spacing: 0.08em;
    text-transform: uppercase;
    color: var(--stamp);
}

.rank__pal {
    font-size: 0.82rem;
    color: var(--rock);
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}

.rank__score {
    text-align: right;
    line-height: 1.1;
}

.rank__score strong {
    display: block;
    font-family: var(--font-display);
    font-size: 1.25rem;
    font-variant-numeric: tabular-nums;
}

.rank__score span {
    font-size: 0.72rem;
    letter-spacing: 0.08em;
    text-transform: uppercase;
    color: var(--rock);
}

/* Boss markers wear a sword instead of a "!". */
.dot__boss { display: block; width: 1.15rem; height: 1.15rem; }
.dot--boss .dot__boss { width: 1.35rem; height: 1.35rem; }
.dot__boss svg { width: 100%; height: 100%; }


/* Sound options popup: music and effects switch independently. */
.soundmenu { position: relative; display: inline-flex; }
.soundmenu__panel { position: absolute; right: 0; top: calc(100% + .45rem); z-index: 60; min-width: 13.5rem;
  background: #fff; border: 1px solid rgba(28,25,23,.12); border-radius: .85rem; padding: .55rem;
  box-shadow: 0 12px 28px -8px rgba(28,25,23,.28), 0 6px 12px -6px rgba(28,25,23,.16); }
.soundmenu__title { margin: 0; padding: .25rem .5rem .4rem; font-size: .7rem; letter-spacing: .09em;
  text-transform: uppercase; color: #8a8580; }
.soundmenu__row { display: flex; align-items: center; justify-content: space-between; gap: 1rem;
  padding: .5rem .5rem; border-radius: .55rem; font-size: .92rem; font-weight: 600; color: #292524; cursor: pointer; }
.soundmenu__row:hover { background: #f5f4f1; }
.soundmenu__row input { width: 1.15rem; height: 1.15rem; accent-color: #0e7490; cursor: pointer; }
@media (max-width: 640px) { .soundmenu__panel { right: -.25rem; min-width: 12.5rem; } }


/* Sidequest character pins. Warm amber against the blue location dots, so a
   living thing never reads as somewhere to look. */
.dot--npc { background: #b3761a; border-color: #8a5a12; color: #fff; }
.dot--npc:hover, .dot--npc:focus-visible { background: #c98a26; }
/* The four states of a character pin, read at a glance from across the map:
   grey you have not been to yet, yellow starts a chain, blue you have met,
   green you are square with. */

/* Not visited. Grey, so the map shows where everyone is without pretending
   you have done anything about them. Their name and face still open on a tap. */
.dot--npc-new { background: #9a948c; border-color: #78716c; color: #fff; }

/* Met them, favour still owed. */
.dot--npc-seen { background: #2c6cae; border-color: #21548a; color: #fff; }

/* Squared away. */
.dot--npc-done { background: #2fb05c; border-color: #228344; color: #fff; }

/* Start here. These are the characters who ask for nothing and hand something
   over — every other character in their chain is waiting on an item only this
   one gives out, so walking up to any of the others first gets you nowhere.
   Yellow and a bang, the way a game says "this one has something for you".
   Drops back to an ordinary pin the moment their favour is done. */
.dot--npc-start {
    background: #e8b71d;
    border-color: #b8860b;
    color: #2a2000;
}

.dot--npc-start .dot__npc { display: none; }

.dot--npc-start::after {
    content: "!";
    font-family: var(--font-display);
    font-size: 1.15rem;
    font-weight: 800;
    line-height: 1;
}
.dot__npc { display: block; width: 1.05rem; height: 1.05rem; }
.dot--npc .dot__npc svg { width: 100%; height: 100%; }

/* Character cards under the trophies. */
.npcshelf { margin: 1.6rem 0 0; }
.npcshelf__head { display: flex; align-items: baseline; justify-content: space-between; gap: 1rem; margin: 0 0 .7rem; }
.npcshelf__head h2 { margin: 0; font-size: 1rem; letter-spacing: .1em; text-transform: uppercase; }
.npcshelf__count { font-size: .8rem; color: #8a8580; }
.npcgrid { display: grid; grid-template-columns: repeat(auto-fill, minmax(9.5rem, 1fr)); gap: .7rem; }
.npccard { display: block; text-align: left; background: #fff; border: 1px solid rgba(28,25,23,.10); border-radius: .9rem; overflow: hidden; }
.npccard__art { aspect-ratio: 16 / 9; background: #f2efe9; display: flex; align-items: center; justify-content: center; color: #c3bdb5; }
.npccard__art img { width: 100%; height: 100%; object-fit: cover; display: block; }
.npccard__art svg { width: 2rem; height: 2rem; }
.npccard__body { padding: .55rem .65rem .7rem; }
.npccard__name { font-weight: 700; font-size: .92rem; line-height: 1.2; }
.npccard__species { font-size: .72rem; letter-spacing: .06em; text-transform: uppercase; color: #8a8580; margin-top: .15rem; }
.npccard__desc { font-size: .78rem; line-height: 1.35; color: #57534e; margin-top: .4rem; }
.npccard__tag { display: inline-block; margin-top: .45rem; font-size: .7rem; font-weight: 700; letter-spacing: .06em; text-transform: uppercase; padding: .16rem .5rem; border-radius: 999px; }
.npccard__tag--met { background: #e7f2ee; color: #0e7490; }
.npccard__tag--new { background: #f2efe9; color: #8a8580; }
.npccard--unmet .npccard__name { color: #8a8580; }


/* Portrait in the map readout, for a character already scanned. */
.readout__face { display: block; width: 100%; aspect-ratio: 16 / 9; object-fit: cover; border-radius: .7rem; margin: 0 0 .6rem; background: #f2efe9; }
/* Character pins on the admin placing board. */
.boardpin--npc { background: #b3761a; border-color: #8a5a12; color: #fff; }
.boardpin--npc .boardpin__label { background: #b3761a; color: #fff; }

/* Met a character out of sequence: where to go instead. */
.npc__hint {
    margin: 0.7rem 0 0;
    padding: 0.6rem 0.75rem;
    border-left: 3px solid var(--blue, #2c6cae);
    background: rgba(44, 108, 174, 0.08);
    font-size: 0.9rem;
    line-height: 1.45;
}

/* Board tabs: compact, so they cost the map as little height as possible.
   The number is what is still out there on that board — the reason to switch. */
.mapswitch { display: grid; grid-auto-flow: column; grid-auto-columns: 1fr;
    gap: .2rem; width: 100%; padding: .2rem; margin: 0 0 .5rem;
    background: #ece8e1; border-radius: .7rem; }
.mapswitch__btn { display: inline-flex; align-items: center; justify-content: center; gap: .35rem;
    border: 0; background: transparent; cursor: pointer; width: 100%;
    padding: .4rem .5rem; border-radius: .55rem;
    font-family: inherit; font-weight: 700; font-size: .8rem; line-height: 1.1;
    color: #78716c; transition: background .15s ease, color .15s ease, box-shadow .15s ease; }
.mapswitch__btn svg { width: .95rem; height: .95rem; fill: none; stroke: currentColor;
    stroke-width: 2; stroke-linecap: round; stroke-linejoin: round; }
.mapswitch__btn:hover { color: #44403c; }
.mapswitch__btn.is-on { background: #fff; color: #1c1917; box-shadow: 0 1px 2px rgba(28,25,23,.12); }
.mapswitch__btn:focus-visible { outline: 2px solid #0e7490; outline-offset: 2px; }
.mapswitch__n { display: inline-flex; align-items: center; justify-content: center;
    min-width: 1.15rem; height: 1.15rem; padding: 0 .3rem; border-radius: 999px;
    background: #d8d2c7; color: #57534e; font-size: .68rem; font-weight: 800; }
.mapswitch__btn.is-on .mapswitch__n { background: #b3761a; color: #fff; }

/* A character screen that simply scrolls.
   `.scene` is height-locked to the viewport for the story beats, where the
   text is short and the choices must stay put. A character can talk at
   length, ask for something and list a bagful of things to offer — locked to
   the viewport, the buttons fell off the bottom with no way to reach them. */
.scene--flow {
    display: block;
    height: auto;
    min-height: 0;
    overflow: visible;
    padding-bottom: 1.5rem;
}

.scene--flow > .passage,
.scene--flow .npc__say {
    flex: none;
    overflow: visible;
    min-height: 0;
}

/* Which hunt you are playing. Two halves of one control, the same shape as
   the map switcher — you are standing on one of them, and the other is a
   place you can go. */
.modeswitch {
    display: grid;
    grid-auto-flow: column;
    grid-auto-columns: 1fr;
    gap: .2rem;
    width: 100%;
    padding: .2rem;
    background: var(--paper-well, #ece8e1);
    border: 1px solid var(--line, var(--rule));
    border-radius: .8rem;
}

/* Each half is either a button in a form or the plain span you are on, so
   both have to sit identically. */
.modeswitch form { display: contents; }

.modeswitch__opt {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    gap: .4rem;
    width: 100%;
    min-height: 2.5rem;
    padding: .45rem .6rem;
    border: 0;
    border-radius: .62rem;
    background: transparent;
    color: var(--ink-soft, #78716c);
    font-family: var(--font-display);
    font-size: .82rem;
    font-weight: 700;
    letter-spacing: .05em;
    text-transform: uppercase;
    cursor: pointer;
    transition: background .15s ease, color .15s ease, box-shadow .15s ease;
}

.modeswitch__opt svg {
    width: 1rem;
    height: 1rem;
    flex-shrink: 0;
    fill: none;
    stroke: currentColor;
    stroke-width: 2;
    stroke-linecap: round;
    stroke-linejoin: round;
}

.modeswitch__opt:hover:not(.is-on),
.modeswitch__opt:focus-visible:not(.is-on) { color: var(--ink); }

/* The one you are on: lifted, not merely coloured, so it reads as the side
   you are standing on rather than the one being recommended. */
.modeswitch__opt.is-on {
    background: var(--paper-raised, #fff);
    color: var(--ink, #1c1917);
    box-shadow: 0 1px 2px rgba(28, 25, 23, .14);
    cursor: default;
}

@media (prefers-reduced-motion: reduce) {
    .modeswitch__opt { transition: none; }
}

/* The stat icons on a profile — a star for points, a coin, a marker, a sword,
   a shield, a heart.
   partials/stat-icon draws them to the same rules as the nav icons: stroked in
   currentColor and never filled, so each one picks up the colour of the stat
   it sits in. Without this they inherit the browser's default fill and render
   as solid black blobs, which is what they were doing. */
.statico {
    width: 1.15rem;
    height: 1.15rem;
    display: block;
    stroke: currentColor;
    fill: none;
    stroke-width: 1.7;
    stroke-linecap: round;
    stroke-linejoin: round;
}

/* The little frame each one sits in, so a row of stats lines up whether its
   icon is wide (a shield) or narrow (a sword). */
.tally__ico {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    width: 1.15rem;
    height: 1.15rem;
    flex-shrink: 0;
}

/* Colour by what the stat means, the same three the fight screen uses. */
.tally__ico--atk { color: #b8402f; }
.tally__ico--def { color: #2c6cae; }
.tally__ico--hp  { color: var(--spruce, #2f9e4f); }

/* Leading the profile page, so it wants a gap under it. */
.modeswitch { margin: 0 0 1.1rem; }
