/* AruaRPG web client.
   Dark slate base with a warm gold accent, because gold already means Zulie and Radiant in this
   game. Portraits render with image-rendering: pixelated so the ROSE sprite art stays crisp on a
   high-density phone screen instead of going soft. Rarity is a border colour rather than a badge,
   so a collection grid reads at a glance. */

:root {
  --bg: #0f1115;
  --panel: #1a1e28;
  --panel-2: #151922;
  --line: #272d3a;
  --line-soft: #21252f;
  --text: #e6e8ee;
  --muted: #8189a0;
  --dim: #5c6478;
  --gold: #f0b429;
  --gold-dim: #4a3d16;
  --blue: #4cc2ff;
  --green: #4ade80;
  --danger: #f87171;
  --tap: 44px;
}

* { box-sizing: border-box; }

html, body {
  margin: 0;
  padding: 0;
  background: var(--bg);
  color: var(--text);
  font-family: ui-sans-serif, system-ui, -apple-system, "Segoe UI", sans-serif;
  font-size: 15px;
  line-height: 1.45;
  -webkit-text-size-adjust: 100%;
  overscroll-behavior-y: contain;
}

a { color: inherit; text-decoration: none; }

/* ---------------------------------------------------------------- chrome */

.top {
  position: sticky; top: 0; z-index: 20;
  display: flex; align-items: center; gap: 10px;
  padding: max(8px, env(safe-area-inset-top)) 14px 10px;
  background: rgba(15, 17, 21, .92);
  backdrop-filter: blur(8px);
  border-bottom: 1px solid var(--line-soft);
}
.av {
  width: 34px; height: 34px; flex: none; border-radius: 10px;
  background: linear-gradient(145deg, #2b3140, #1a1e27);
  border: 1px solid #333a49;
  display: flex; align-items: center; justify-content: center;
  font-size: 16px; overflow: hidden;
}
.av img { width: 100%; height: 100%; object-fit: cover; }
.who { flex: 1; min-width: 0; }
.who b { display: block; font-size: 13px; font-weight: 600; }
.who span { display: block; font-size: 11px; color: var(--dim); }

/* A live event window. Gold because that is already what this app means by "worth your attention
   now", and it sits above the status strip because a surge changes what the rest of the page is
   worth doing. */
.event {
  display: flex; align-items: center; gap: 10px;
  padding: 9px 12px; margin-bottom: 10px;
  border: 1px solid var(--gold-dim); border-radius: 12px;
  background: linear-gradient(160deg, #241f12, var(--panel));
  color: inherit; text-decoration: none;
}
.event b { display: block; font-size: 13px; color: var(--gold); }
.event .lbl { display: block; }
.event .left { flex: none; font-size: 12px; font-weight: 700; color: var(--gold); font-variant-numeric: tabular-nums; }
.event .dot {
  flex: none; width: 8px; height: 8px; border-radius: 50%;
  background: var(--gold); box-shadow: 0 0 0 0 rgba(240, 180, 41, .6);
  animation: pulse 2.2s infinite;
}

@keyframes pulse {
  70%  { box-shadow: 0 0 0 7px rgba(240, 180, 41, 0); }
  100% { box-shadow: 0 0 0 0 rgba(240, 180, 41, 0); }
}

/* Motion is decoration here: the border and colour already carry the meaning. */
@media (prefers-reduced-motion: reduce) {
  .event .dot { animation: none; }
}

/* The unread count as a notification pill. It used to be dim grey text after a middot, which put the
   one thing worth noticing in the header into the same colour as the thing next to it. Overrides
   .who b's display:block, which would otherwise drop it onto its own line. */
.who .nb {
  display: inline-block; vertical-align: 1px; margin-left: 6px;
  padding: 0 6px; border-radius: 999px;
  background: #e5484d; color: #fff;
  font-size: 10px; font-weight: 700; line-height: 15px;
  font-variant-numeric: tabular-nums;
}
.zul { font-size: 12px; font-weight: 700; color: var(--gold); white-space: nowrap; }
.zul small { color: #7a6320; }

.body {
  padding: 12px 12px calc(76px + env(safe-area-inset-bottom));
  max-width: 720px; margin: 0 auto;
  background: radial-gradient(120% 40% at 50% 0%, #171b24 0%, var(--bg) 60%);
  min-height: 60vh;
}

.tabs {
  position: fixed; left: 0; right: 0; bottom: 0; z-index: 20;
  display: flex;
  background: #141821;
  border-top: 1px solid var(--line-soft);
  padding-bottom: env(safe-area-inset-bottom);
}
.tb {
  /* min-width:0 lets six tabs share a 320px screen without the widest label forcing an overflow;
     nowrap keeps "Collection" on one line, which is what it would otherwise break onto two of. */
  flex: 1; min-width: 0; text-align: center; padding: 8px 0 10px;
  font-size: 9.5px; white-space: nowrap; color: #666e80; min-height: var(--tap);
}
.tb.on { color: var(--gold); }
.ic { display: block; width: 20px; height: 20px; margin: 0 auto 3px; opacity: .55; background: currentColor;
      -webkit-mask-size: contain; mask-size: contain; -webkit-mask-repeat: no-repeat; mask-repeat: no-repeat;
      -webkit-mask-position: center; mask-position: center; border-radius: 3px; }
.tb.on .ic { opacity: 1; }
/* Simple geometric glyphs, inline so the shell needs no image requests to paint. */
.ic-home       { -webkit-mask-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'%3E%3Cpath d='M4 11l8-7 8 7v9a1 1 0 0 1-1 1h-5v-6h-4v6H5a1 1 0 0 1-1-1z'/%3E%3C/svg%3E"); mask-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'%3E%3Cpath d='M4 11l8-7 8 7v9a1 1 0 0 1-1 1h-5v-6h-4v6H5a1 1 0 0 1-1-1z'/%3E%3C/svg%3E"); }
.ic-collection { -webkit-mask-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'%3E%3Cpath d='M4 6h7v7H4zm9 0h7v7h-7zM4 15h7v5H4zm9 0h7v5h-7z'/%3E%3C/svg%3E"); mask-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'%3E%3Cpath d='M4 6h7v7H4zm9 0h7v7h-7zM4 15h7v5H4zm9 0h7v5h-7z'/%3E%3C/svg%3E"); }
.ic-expedition { -webkit-mask-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'%3E%3Cpath d='M12 2a10 10 0 1 0 0 20 10 10 0 0 0 0-20zm3.5 6.5l-2 5-5 2 2-5z'/%3E%3C/svg%3E"); mask-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'%3E%3Cpath d='M12 2a10 10 0 1 0 0 20 10 10 0 0 0 0-20zm3.5 6.5l-2 5-5 2 2-5z'/%3E%3C/svg%3E"); }
.ic-missions   { -webkit-mask-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'%3E%3Cpath d='M6 3h12v18l-6-4-6 4z'/%3E%3C/svg%3E"); mask-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'%3E%3Cpath d='M6 3h12v18l-6-4-6 4z'/%3E%3C/svg%3E"); }
.ic-you        { -webkit-mask-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'%3E%3Cpath d='M12 12a4 4 0 1 0 0-8 4 4 0 0 0 0 8zm0 2c-4 0-7 2-7 4.5V21h14v-2.5C19 16 16 14 12 14z'/%3E%3C/svg%3E"); mask-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'%3E%3Cpath d='M12 12a4 4 0 1 0 0-8 4 4 0 0 0 0 8zm0 2c-4 0-7 2-7 4.5V21h14v-2.5C19 16 16 14 12 14z'/%3E%3C/svg%3E"); }

/* ---------------------------------------------------------------- pieces */

.px { image-rendering: pixelated; image-rendering: -moz-crisp-edges; }

.strip { display: flex; gap: 8px; margin-bottom: 10px; }
.st {
  flex: 1; min-width: 0; background: var(--panel); border: 1px solid var(--line);
  border-radius: 12px; padding: 9px 11px; display: flex; align-items: center; gap: 10px;
}
.st.hot { border-color: var(--gold-dim); background: linear-gradient(160deg, #241f12, var(--panel)); }
.ring {
  width: 34px; height: 34px; flex: none; border-radius: 50%;
  background: conic-gradient(var(--blue) 0turn var(--p, .5turn), #252b38 var(--p, .5turn) 1turn);
  display: flex; align-items: center; justify-content: center;
}
.ring i { width: 25px; height: 25px; border-radius: 50%; background: var(--panel-2);
          display: flex; align-items: center; justify-content: center;
          font-style: normal; font-size: 9px; font-weight: 700; color: var(--blue); }
.ring.gold { background: conic-gradient(var(--gold) 0turn var(--p, .5turn), #252b38 var(--p, .5turn) 1turn); }
.ring.gold i { color: var(--gold); }

/* Green for the daily board, matching the Today tile on the Missions page. Blue stays the
   expedition's, so the two rings on the home strip are never the same colour by accident. */
.ring.green { background: conic-gradient(var(--green) 0turn var(--p, .5turn), #252b38 var(--p, .5turn) 1turn); }
.ring.green i { color: var(--green); }
.lbl { font-size: 9px; letter-spacing: .09em; text-transform: uppercase; color: var(--dim); font-weight: 700; }
.st b { display: block; font-size: 12px; margin-top: 1px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }

.fd {
  display: flex; gap: 10px; background: var(--panel); border: 1px solid var(--line);
  border-radius: 12px; padding: 10px; margin-bottom: 8px;
}
.fd.gold { border-color: var(--gold-dim); background: linear-gradient(160deg, #241f12, var(--panel)); }
.fd img { width: 44px; height: 44px; flex: none; background: var(--panel-2); border-radius: 9px; border: 1px solid #2a303d; }
.fd .tx { flex: 1; min-width: 0; }
.fd .tx b { font-size: 12.5px; display: block; font-weight: 600; }
.fd .tx p { margin: 2px 0 0; font-size: 12px; color: var(--muted); }
.fd .ago { font-size: 10px; color: var(--dim); margin-top: 4px; display: block; }

.btn {
  display: inline-block; margin-top: 8px; margin-right: 6px; padding: 8px 14px; border-radius: 9px;
  font-size: 12px; font-weight: 700; background: var(--gold); color: #1a1405; border: 0;
  min-height: 36px; cursor: pointer; font-family: inherit;
}
.btn.gh { background: #232936; color: #c3cad9; }
.btn.wide { display: block; width: 100%; text-align: center; margin-right: 0; }
.btn[disabled] { opacity: .45; cursor: not-allowed; }

.sect { font-size: 9px; letter-spacing: .1em; text-transform: uppercase; color: var(--dim);
        font-weight: 700; margin: 18px 0 8px; }

.hub { display: grid; grid-template-columns: repeat(4, 1fr); gap: 8px; }
.hb {
  background: var(--panel); border: 1px solid var(--line); border-radius: 11px;
  padding: 12px 4px; text-align: center; font-size: 10px; color: var(--muted);
  position: relative; min-height: var(--tap);
}
.hb.hot { border-color: var(--gold-dim); background: linear-gradient(160deg, #241f12, var(--panel)); color: var(--gold); }
.hb .n { display: block; font-size: 17px; font-weight: 700; color: var(--text); margin-bottom: 2px; }

.grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(96px, 1fr)); gap: 8px; }
.mon {
  background: var(--panel); border: 1px solid var(--line); border-radius: 11px;
  padding: 8px 6px; text-align: center;
}
/* The one you are wearing. Gold, matching every other "this is the active one" in the app, so the
   card reads as chosen rather than as merely lacking a button. */
.mon.worn { border-color: var(--gold-dim); background: linear-gradient(160deg, #241f12, var(--panel)); }

.mon img { width: 100%; max-width: 72px; aspect-ratio: 1; background: var(--panel-2); border-radius: 8px; }
.mon b { display: block; font-size: 11px; margin-top: 5px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.mon span { display: block; font-size: 10px; color: var(--dim); }
/* Radiant gets the whole card, not just its edge. These are roughly a one-in-fifty roll and the
   game already gives them a golden card everywhere else, so a border alone undersold them in the
   one place you actually browse your collection.
   The gradient stays dark enough for the white name to keep its contrast, and the portraits have
   transparent backgrounds so the gold reads through the sprite rather than behind a box. */
.mon.q-radiant {
  border-color: var(--gold);
  background: linear-gradient(160deg, #3a2c0c 0%, #241f12 55%, var(--panel) 100%);
  box-shadow: 0 0 0 1px rgba(240, 180, 41, .25), 0 0 14px rgba(240, 180, 41, .12);
}
.mon.q-radiant img { background: rgba(240, 180, 41, .07); }
.mon.q-radiant b { color: #ffe6a3; }

/* The large portrait on the monster page. Same rarity language as the grid tile, so a monster does
   not lose its treatment on the page you opened to look at it properly. */
img.hero {
  width: 128px; height: 128px; border-radius: 12px;
  background: var(--panel-2); border: 1px solid var(--line);
}
img.hero.q-radiant {
  border-color: var(--gold);
  background: linear-gradient(160deg, #3a2c0c 0%, #241f12 55%, var(--panel-2) 100%);
  box-shadow: 0 0 0 1px rgba(240, 180, 41, .25), 0 0 20px rgba(240, 180, 41, .16);
}
img.hero.q-fine  { border-color: #3b6ea5; }
img.hero.q-runty { border-color: #3a2f2f; }
.mon.q-fine    { border-color: #3b6ea5; }
.mon.q-runty   { border-color: #3a2f2f; }

.card { background: var(--panel); border: 1px solid var(--line); border-radius: 12px; padding: 12px; margin-bottom: 8px; }
.bar { height: 6px; border-radius: 4px; background: #252b38; overflow: hidden; margin-top: 6px; }
.bar i { display: block; height: 100%; background: var(--blue); }
.bar.green i { background: var(--green); }

/* Tall enough to carry its own numbers. The label is centred over the whole bar, not over the fill,
   so it does not slide about as the fill grows. */
.bar.tall { height: 18px; border-radius: 6px; position: relative; }
.bar.tall > span {
  position: absolute; inset: 0;
  display: flex; align-items: center; justify-content: center;
  font-size: 11px; font-weight: 700; color: var(--text);
  text-shadow: 0 1px 2px rgba(0, 0, 0, .65);
  font-variant-numeric: tabular-nums;
}

.note { background: #241416; border: 1px solid #4a2326; color: #f0b5b5; border-radius: 11px;
        padding: 10px 12px; font-size: 12.5px; margin-bottom: 10px; }
.note.warn { background: #241f12; border-color: var(--gold-dim); color: #e5cb8a; }
.note.ok { background: #12241a; border-color: #1e4a30; color: #a7e8c0; }

/* A closed, filled bag. The previous version ended on an open arc ("a4 4 0 0 1 8 0"), which some
   engines refuse to rasterise as a mask at all; the mask then applies nothing and .ic's
   currentColor background shows through as a plain square. Every path here is closed for that
   reason. */
.ic-shop { -webkit-mask-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'%3E%3Cpath d='M5 8h14l-1 12H6zM9 8V7a3 3 0 0 1 6 0v1h-2V7a1 1 0 0 0-2 0v1z'/%3E%3C/svg%3E"); mask-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'%3E%3Cpath d='M5 8h14l-1 12H6zM9 8V7a3 3 0 0 1 6 0v1h-2V7a1 1 0 0 0-2 0v1z'/%3E%3C/svg%3E"); }

.empty { text-align: center; color: var(--dim); padding: 34px 16px; font-size: 13px; }

/* Native selects, styled to match rather than replaced. A custom dropdown would lose the platform
   picker, which on a phone is a full-height wheel and better than anything rebuilt here. */
.sel {
  width: 100%; margin-top: 5px; padding: 10px 11px; min-height: var(--tap);
  background: var(--panel-2); color: var(--text);
  border: 1px solid var(--line); border-radius: 10px;
  font-family: inherit; font-size: 13px;
  appearance: none;
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='%238189a0'%3E%3Cpath d='M7 10l5 5 5-5z'/%3E%3C/svg%3E");
  background-repeat: no-repeat; background-position: right 8px center; background-size: 20px;
}
.sel:focus-visible { outline: 2px solid var(--gold); outline-offset: -1px; }

/* An unfound species keeps its silhouette: the shape is a hint, the detail is the reward. */
/* A pale silhouette, not a black one. brightness(0) alone paints the shape in black on a card that
   is very nearly black already, so an uncaught species rendered as an empty tile rather than as a
   monster you have not met. invert lifts it off the panel; the low opacity keeps it a shape rather
   than a picture.

   background MUST be cleared with it. A CSS filter applies to the element's own background as well
   as its content, so inverting while .mon img still carries background: var(--panel-2) turns the
   dark tile into a light grey square and hides the silhouette behind it. The portraits are RGBA, so
   with no background the filter only ever touches the monster. */
.mon.unseen img { background: transparent; filter: brightness(0) invert(1) opacity(.3); }
.mon.unseen b { color: var(--dim); letter-spacing: .08em; }
.mon.unseen span { color: var(--dim); }

/* A portrait with no art 404s rather than serving a placeholder, so the img is empty. This keeps
   the tile the right size and shows a neutral mark instead of the browser's broken-image icon. */
.mon img:not([src]), img.px[data-missing] { visibility: hidden; }
.mon .noart {
  width: 100%; max-width: 72px; aspect-ratio: 1; margin: 0 auto;
  background: var(--panel-2); border-radius: 8px;
  display: flex; align-items: center; justify-content: center;
  color: var(--dim); font-size: 18px;
}

/* Shop: three to a row, description under the tile. */
.shopgrid { display: grid; grid-template-columns: repeat(3, 1fr); gap: 8px; }
@media (max-width: 360px) { .shopgrid { grid-template-columns: repeat(2, 1fr); } }
.shopitem {
  background: var(--panel); border: 1px solid var(--line); border-radius: 11px;
  padding: 10px 7px; text-align: center; display: flex; flex-direction: column;
}
.shopitem .glyph {
  width: 100%; aspect-ratio: 1; border-radius: 9px; background: var(--panel-2);
  display: flex; align-items: center; justify-content: center; font-size: 26px;
  border: 1px solid var(--line-soft);
}
/* Rendered item art fills the tile; the emoji marker sits underneath it and only shows when there is
   no art to show. An <img> that 404s adds .noart via its own onerror handler. */
.shopitem .glyph img { width: 100%; height: 100%; object-fit: contain; border-radius: 9px; }
.shopitem .glyph img + .marker { display: none; }
.shopitem .glyph.noart .marker { display: block; }

.shopitem b { display: block; font-size: 11.5px; margin-top: 7px; line-height: 1.25; }
.shopitem .blurb { font-size: 10.5px; color: var(--muted); margin-top: 3px; flex: 1; line-height: 1.3; }
.shopitem .cost { font-size: 12px; font-weight: 700; color: var(--gold); margin-top: 6px; }
.shopitem .cost.cant { color: var(--dim); }
.shopitem form { margin-top: 6px; }
.shopitem .btn { width: 100%; margin: 0; padding: 8px 4px; font-size: 11px; min-height: 36px; }

/* The pull-to-refresh label. Fixed above the viewport and translated down by the gesture, so it
   reads as the page being dragged rather than a banner appearing. */
.pull {
  position: fixed; top: -34px; left: 0; right: 0; z-index: 40;
  text-align: center; padding: 8px 0; font-size: 11.5px;
  color: var(--gold); background: var(--panel-2);
  border-bottom: 1px solid var(--line);
  pointer-events: none;
}

/* The server-activity strip. Horizontal, so ambient news costs one row of height rather than pushing
   the player's own actionable items off the screen. Scroll-snap so it settles on a card instead of
   stopping halfway through one. */
.scroller {
  display: flex; gap: 8px; overflow-x: auto; padding-bottom: 4px;
  scroll-snap-type: x proximity;
  -webkit-overflow-scrolling: touch;
  scrollbar-width: none;
}
.scroller::-webkit-scrollbar { display: none; }
.scroll-item {
  flex: none; width: 108px; scroll-snap-align: start;
  background: var(--panel); border: 1px solid var(--line); border-radius: 11px;
  padding: 9px 8px; text-align: center;
}
.scroll-item img, .scroll-item .noart {
  width: 100%; max-width: 56px; aspect-ratio: 1; margin: 0 auto;
  background: var(--panel-2); border-radius: 8px;
  display: flex; align-items: center; justify-content: center; color: var(--dim);
}
.scroll-item b {
  display: block; font-size: 10.5px; margin-top: 6px; line-height: 1.25;
  /* Two lines then ellipsis: a long title must not make one card taller than its neighbours. */
  display: -webkit-box; -webkit-line-clamp: 2; -webkit-box-orient: vertical; overflow: hidden;
}
.scroll-item span { display: block; font-size: 9.5px; color: var(--dim); margin-top: 3px; }

/* A status card that fills to show how far along it is.
   A hard-stopped gradient rather than a soft one: a blur reads as a style, whereas an edge at 60%
   reads as a measurement, which is the whole point. The fill is deliberately low-contrast so the
   text on top keeps its legibility at any percentage. */
.st.fill {
  background:
    linear-gradient(90deg,
      var(--tint, rgba(240, 180, 41, .16)) 0, var(--tint, rgba(240, 180, 41, .16)) var(--p, 0%),
      var(--panel) var(--p, 0%), var(--panel) 100%);
  border-color: var(--edge, var(--line));
}
.st.fill .lbl { color: var(--tint-label, var(--dim)); }
.st.fill b { color: var(--tint-text, var(--text)); }

/* Two different jobs, two different colours. Today resets and is finishable, so it reads green like
   every other completed thing on the page. The campaign only ever advances, so it takes the gold
   already used for progression and reward. Identical fills made them look like one measurement
   split in half. */
.st.daily    { --tint: rgba(74, 222, 128, .18); --edge: #1e4a30; --tint-label: #7fd3a0; --tint-text: #d6f5e2; }
.st.campaign { --tint: rgba(240, 180, 41, .18); --edge: var(--gold-dim); --tint-label: #c9a54a; --tint-text: #f6e3b4; }

/* Not yet done today, so it earns the stronger treatment. */
.st.daily.hot { --tint: rgba(74, 222, 128, .3); --edge: var(--green); }

/* Section headings that carry an icon. The glyph sits inline at text size and inherits the heading's
   dim colour, so it reads as punctuation rather than as decoration competing with the label.
   Every path below is CLOSED: an open arc is what made the shop tab render as a plain square,
   because a mask that will not rasterise applies nothing and the full-size background shows. */
.sect.titled { display: flex; align-items: center; gap: 6px; }
.sect .si {
  width: 13px; height: 13px; flex: none; background: currentColor;
  -webkit-mask-size: contain; mask-size: contain;
  -webkit-mask-repeat: no-repeat; mask-repeat: no-repeat;
  -webkit-mask-position: center; mask-position: center;
}
/* A sun: today, the thing that resets. */
.si-daily { -webkit-mask-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'%3E%3Cpath d='M12 7a5 5 0 1 1 0 10a5 5 0 1 1 0-10zM11 1h2v3h-2zM11 20h2v3h-2zM1 11h3v2H1zM20 11h3v2h-3z'/%3E%3C/svg%3E"); mask-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'%3E%3Cpath d='M12 7a5 5 0 1 1 0 10a5 5 0 1 1 0-10zM11 1h2v3h-2zM11 20h2v3h-2zM1 11h3v2H1zM20 11h3v2h-3z'/%3E%3C/svg%3E"); }
/* A banner: the campaign, the thing you advance. */
.si-campaign { -webkit-mask-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'%3E%3Cpath d='M5 2h2v20H5zM8 3h11l-3 4 3 4H8z'/%3E%3C/svg%3E"); mask-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'%3E%3Cpath d='M5 2h2v20H5zM8 3h11l-3 4 3 4H8z'/%3E%3C/svg%3E"); }
/* An archive box: what already happened. */
/* A paw print, for what the trip brings home rather than what it ticks. Every shape is a closed
   circle for the same reason the shop bag is closed: a mask that fails to rasterise applies nothing,
   and .si's currentColor background then shows through as a plain square. */
.si-tracks { -webkit-mask-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'%3E%3Cpath d='M6 5.8a2.2 2.2 0 1 1 0 4.4a2.2 2.2 0 1 1 0-4.4zM10 2.8a2.2 2.2 0 1 1 0 4.4a2.2 2.2 0 1 1 0-4.4zM14 2.8a2.2 2.2 0 1 1 0 4.4a2.2 2.2 0 1 1 0-4.4zM18 5.8a2.2 2.2 0 1 1 0 4.4a2.2 2.2 0 1 1 0-4.4zM12 12.2a4.3 4.3 0 1 1 0 8.6a4.3 4.3 0 1 1 0-8.6z'/%3E%3C/svg%3E"); mask-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'%3E%3Cpath d='M6 5.8a2.2 2.2 0 1 1 0 4.4a2.2 2.2 0 1 1 0-4.4zM10 2.8a2.2 2.2 0 1 1 0 4.4a2.2 2.2 0 1 1 0-4.4zM14 2.8a2.2 2.2 0 1 1 0 4.4a2.2 2.2 0 1 1 0-4.4zM18 5.8a2.2 2.2 0 1 1 0 4.4a2.2 2.2 0 1 1 0-4.4zM12 12.2a4.3 4.3 0 1 1 0 8.6a4.3 4.3 0 1 1 0-8.6z'/%3E%3C/svg%3E"); }

.si-past { -webkit-mask-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'%3E%3Cpath d='M3 4h18v4H3zM5 10h14v10H5zM9 12h6v2H9z'/%3E%3C/svg%3E"); mask-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'%3E%3Cpath d='M3 4h18v4H3zM5 10h14v10H5zM9 12h6v2H9z'/%3E%3C/svg%3E"); }

/* Collection search. The clear button only appears once there is something to clear, so the bar is
   a single field until it needs to be two. */
.searchbar { display: flex; gap: 6px; align-items: stretch; margin-bottom: 8px; }
.searchbar .sel { margin-top: 0; background-image: none; }
.searchbar .btn { margin: 0; flex: none; padding: 8px 12px; font-size: 11px; }

/* Quick filters for the thing most worth filtering on. Horizontally scrollable rather than wrapped,
   so a narrow phone keeps them on one line instead of eating three rows of the grid. */
.chips { display: flex; gap: 6px; overflow-x: auto; padding-bottom: 8px; scrollbar-width: none; }
.chips::-webkit-scrollbar { display: none; }
.chip {
  flex: none; padding: 7px 12px; min-height: 34px; border-radius: 999px;
  background: var(--panel); border: 1px solid var(--line); color: var(--muted);
  font-family: inherit; font-size: 11.5px; cursor: pointer;
}
.chip.on { background: linear-gradient(160deg, #241f12, var(--panel)); border-color: var(--gold); color: var(--gold); }
.chip:focus-visible { outline: 2px solid var(--gold); outline-offset: 1px; }

/* A card that has just arrived. The glow is a moment, not a state: it fades on its own, because an
   effect left running becomes wallpaper and stops meaning "this is new". */
.fresh { animation: arrive 6s ease-out forwards; }
@keyframes arrive {
  0%   { border-color: var(--gold); box-shadow: 0 0 0 1px var(--gold), 0 0 18px rgba(240, 180, 41, .45); transform: scale(.94); }
  12%  { transform: scale(1); }
  70%  { border-color: var(--gold); box-shadow: 0 0 0 1px rgba(240, 180, 41, .5), 0 0 12px rgba(240, 180, 41, .25); }
  100% { border-color: var(--line); box-shadow: none; }
}

/* Honour the system setting. A pulsing border is exactly the sort of thing somebody turns this on
   to avoid, and the card is perfectly legible without it. */
@media (prefers-reduced-motion: reduce) {
  .fresh { animation: none; border-color: var(--gold); }
}

/* Rarity as text colour on gear, matching the border language the collection uses on monsters. */
.r-common    { color: var(--text); }
.r-uncommon  { color: var(--green); }
.r-rare      { color: var(--blue); }
.r-epic      { color: #a78bfa; }
.r-legendary { color: var(--gold); }

/* details/summary carries the expandable history with no JavaScript at all. The default marker is
   hidden and replaced with a chevron that rotates, because the native one sits badly against a
   flex row and differs between engines. */
details > summary { list-style: none; }
details > summary::-webkit-details-marker { display: none; }
details > summary::after {
  content: ""; flex: none; width: 8px; height: 8px; margin-left: 2px;
  border-right: 2px solid var(--dim); border-bottom: 2px solid var(--dim);
  transform: rotate(45deg) translateY(-2px);
  transition: transform .15s ease;
}
details[open] > summary::after { transform: rotate(-135deg) translateY(-2px); }
details > summary:focus-visible { outline: 2px solid var(--gold); outline-offset: -2px; border-radius: 12px; }

table.lb { width: 100%; border-collapse: collapse; font-size: 12.5px; }
table.lb th { text-align: left; font-size: 9px; letter-spacing: .09em; text-transform: uppercase;
              color: var(--dim); padding: 6px 8px; font-weight: 700; }
table.lb td { padding: 8px; border-top: 1px solid var(--line-soft); }
table.lb tr:first-child td { border-top: 0; }

.centre { max-width: 380px; margin: 8vh auto; text-align: center; padding: 0 20px; }
.centre h1 { font-size: 22px; margin: 0 0 6px; }
.centre p { color: var(--muted); font-size: 13.5px; }

@media (prefers-reduced-motion: no-preference) {
  .btn { transition: transform .06s ease; }
  .btn:active { transform: scale(.97); }
}
