/*
 * Soul Bound design system — the "system window" language, app-wide.
 *
 * Loads before system_panel.css alphabetically, so the auth panels can still
 * override anything here. Tokens live only in this file; nothing else should
 * redefine them.
 */

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

:root {
  /* Matches the iOS shell exactly: SceneDelegate's systemBackground and the
     WKWebView background are this same #050812. */
  --sys-bg: #050812;
  --sys-bg-glow: #0d1429;
  --sys-surface: rgba(10, 17, 36, 0.92);
  --sys-surface-solid: #0a1124;

  --sys-cyan: #22d3ee;
  --sys-line: rgba(34, 211, 238, 0.45);
  --sys-line-soft: rgba(34, 211, 238, 0.16);

  --sys-text: #e8f4ff;
  --sys-text-dim: rgba(232, 244, 255, 0.55);
  --sys-text-faint: rgba(232, 244, 255, 0.3);

  --sys-danger: #ff5d73;
  --sys-gold: #ffc84a;
  --sys-gem: #b98bff;
  --sys-xp: #22d3ee;

  /* Chest rarities, shared by drops and achievement badges. */
  --sys-common: #9fb3c8;
  --sys-rare: #4aa8ff;
  --sys-epic: #b98bff;
  --sys-legendary: #ffc84a;

  --sys-grid: 36px;
  --sys-notch: 18px;
  --sys-font: -apple-system, BlinkMacSystemFont, "SF Pro Display", "Helvetica Neue", sans-serif;

  --sys-tabbar-height: 62px;
  --sys-topbar-height: 52px;
}

/* ------------------------------------------------------------------ base -- */

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

html {
  color-scheme: dark;
  /* Stop iOS inflating text in the web view. */
  -webkit-text-size-adjust: 100%;
}

body {
  margin: 0;
  min-height: 100dvh;
  font-family: var(--sys-font);
  color: var(--sys-text);
  background-color: var(--sys-bg);
  background-image:
    radial-gradient(circle at 50% 0%, var(--sys-bg-glow), transparent 55%),
    repeating-linear-gradient(to right, rgba(34, 211, 238, 0.055) 0 1px, transparent 1px var(--sys-grid)),
    repeating-linear-gradient(to bottom, rgba(34, 211, 238, 0.055) 0 1px, transparent 1px var(--sys-grid));
  background-attachment: fixed;
  -webkit-tap-highlight-color: transparent;
}

/* Two soft, slowly-drifting glow blobs behind everything — the grid alone
   read as flat; this gives the background actual depth and a sense of the
   "system" being alive rather than a static texture. */
body::before,
body::after {
  content: "";
  position: fixed;
  z-index: -1;
  border-radius: 50%;
  filter: blur(70px);
  opacity: 0.22;
  pointer-events: none;
}

body::before {
  top: -15%;
  left: -20%;
  width: 65vw;
  height: 65vw;
  background: radial-gradient(circle, var(--sys-cyan), transparent 70%);
  animation: sys-aurora-drift-a 24s ease-in-out infinite;
}

/* Gold rather than another cool color — cyan alone read as a flat blue wash;
   pairing it with the app's existing "premium reward" accent (coins/trophy)
   gives the background actual warm/cool contrast instead of one hue. */
body::after {
  bottom: -20%;
  right: -15%;
  width: 60vw;
  height: 60vw;
  background: radial-gradient(circle, var(--sys-gold), transparent 70%);
  animation: sys-aurora-drift-b 28s ease-in-out infinite;
}

@keyframes sys-aurora-drift-a {
  0%, 100% { transform: translate(0, 0) scale(1); }
  50% { transform: translate(8vw, 6vh) scale(1.15); }
}

@keyframes sys-aurora-drift-b {
  0%, 100% { transform: translate(0, 0) scale(1); }
  50% { transform: translate(-6vw, -8vh) scale(1.1); }
}

@media (prefers-reduced-motion: reduce) {
  body::before,
  body::after {
    animation: none;
  }
}

h1, h2, h3, h4, h5, h6, p {
  margin: 0;
}

a {
  color: inherit;
  text-decoration: none;
}

/* ----------------------------------------------------------------- shell -- */

/* Every signed-in screen sits in this, which reserves room for the top bar
   and the tab bar. */
.sys-app {
  padding:
    calc(var(--sys-topbar-height) + 20px + env(safe-area-inset-top))
    20px
    calc(var(--sys-tabbar-height) + 28px + env(safe-area-inset-bottom));
  max-width: 560px;
  margin: 0 auto;
}

/* Native supplies its own top and tab bars outside the web view's viewport
   (see MainTabBarController/TopBarView), so the space this normally reserves
   for the CSS bars would just be a dead gap. */
.is-native .sys-app {
  padding-top: 20px;
  padding-bottom: 28px;
}

/* Hidden rather than unrendered (unlike the tab bar) — native's "Log out"
   action still submits this exact form via JS, so the form itself must stay
   in the DOM even though nothing shows it on screen. */
.is-native .sys-topbar {
  display: none;
}

/* Opt-in per page (via the layout's `center_page` content_for) — centers
   short content vertically in the space between the top and tab bars
   instead of it sitting flush under the top bar with all the leftover
   room dumped below it. The top/bottom padding above still reserves the
   bars' own space; this only centers within what's left. */
.sys-app--center {
  min-height: 100dvh;
  display: flex;
  flex-direction: column;
  justify-content: center;
}

.sys-stack > * + * {
  margin-top: 16px;
}

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

.sys-eyebrow {
  display: flex;
  align-items: center;
  gap: 8px;
  font-size: 9px;
  font-weight: 500;
  letter-spacing: 0.42em;
  text-indent: 0.42em;
  text-transform: uppercase;
  color: var(--sys-cyan);
}

.sys-eyebrow--center {
  justify-content: center;
}

/* The diamond flanks are drawn, not typed, so they scale with the text. */
.sys-eyebrow--flanked::before,
.sys-eyebrow--flanked::after {
  content: "";
  width: 5px;
  height: 5px;
  flex: none;
  background: var(--sys-cyan);
  transform: rotate(45deg);
  box-shadow: 0 0 8px rgba(34, 211, 238, 0.8);
}

.sys-title {
  font-size: 22px;
  font-weight: 200;
  letter-spacing: 0.3em;
  text-indent: 0.3em;
  text-transform: uppercase;
  color: #fff;
  text-shadow: 0 0 20px rgba(34, 211, 238, 0.45);
}

.sys-title--lg {
  font-size: 30px;
}

.sys-subhead {
  font-size: 10px;
  font-weight: 500;
  letter-spacing: 0.3em;
  text-transform: uppercase;
  color: var(--sys-text-dim);
}

.sys-note {
  font-size: 12px;
  line-height: 1.6;
  letter-spacing: 0.06em;
  color: var(--sys-text-dim);
}

.sys-rule {
  height: 1px;
  margin: 0;
  border: 0;
  background: linear-gradient(to right, transparent, var(--sys-line), transparent);
}

.sys-rule--faint {
  background: linear-gradient(to right, transparent, var(--sys-line-soft), transparent);
}

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

/* The notched system window. Two elements: the outer paints the 1px border as
   its background, the inner covers all but that 1px. Both share the clip-path,
   which is what cuts the corners. drop-shadow (not box-shadow) for the glow,
   because filters follow the clipped silhouette while box-shadow gets sliced. */
.sys-card {
  padding: 1px;
  clip-path: polygon(
    var(--sys-notch) 0,
    100% 0,
    100% calc(100% - var(--sys-notch)),
    calc(100% - var(--sys-notch)) 100%,
    0 100%,
    0 var(--sys-notch)
  );
  /* A longer gradient than the border itself, animated by sliding its
     position — reads as a slow light continuously travelling the border
     rather than a fixed static edge. */
  background: linear-gradient(
    160deg,
    rgba(34, 211, 238, 0.9) 0%,
    rgba(34, 211, 238, 0.15) 20%,
    rgba(34, 211, 238, 0.75) 40%,
    rgba(34, 211, 238, 0.15) 60%,
    rgba(34, 211, 238, 0.75) 80%,
    rgba(34, 211, 238, 0.15) 100%
  );
  background-size: 320% 320%;
  animation: sys-card-shimmer 7s ease-in-out infinite;
  filter: drop-shadow(0 0 20px rgba(34, 211, 238, 0.18));
}

@keyframes sys-card-shimmer {
  0%, 100% { background-position: 0% 0%; }
  50% { background-position: 100% 100%; }
}

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

.sys-card__inner {
  position: relative;
  clip-path: inherit;
  padding: 20px;
  background: linear-gradient(170deg, var(--sys-surface), rgba(5, 8, 18, 0.97));
}

/* Scanlines. */
.sys-card__inner::before {
  content: "";
  position: absolute;
  inset: 0;
  pointer-events: none;
  background: repeating-linear-gradient(
    to bottom,
    rgba(255, 255, 255, 0.02) 0 1px,
    transparent 1px 3px
  );
}

.sys-card--flush .sys-card__inner {
  padding: 0;
}

/* Pins to the top of .sys-app's own padding box, so it docks exactly where it
   started instead of jumping. Above the scrolling cards below it, but below
   the tab bar (z-index: 10) and any open dropdown menu. */
.sys-card--sticky {
  position: sticky;
  top: calc(var(--sys-topbar-height) + 20px + env(safe-area-inset-top) - 4px);
  z-index: 4;
}

/* Rarity variants — used by chests and achievement badges. */
.sys-card--rare { background: linear-gradient(160deg, var(--sys-rare), rgba(74, 168, 255, 0.25)); }
.sys-card--epic { background: linear-gradient(160deg, var(--sys-epic), rgba(185, 139, 255, 0.25)); }
.sys-card--legendary { background: linear-gradient(160deg, var(--sys-legendary), rgba(255, 200, 74, 0.25)); }

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

.sys-btn {
  display: flex;
  align-items: center;
  justify-content: center;
  width: 100%;
  padding: 14px;
  border: 1px solid rgba(34, 211, 238, 0.45);
  border-radius: 2px;
  background: rgba(13, 20, 41, 0.6);
  color: #fff;
  font-family: inherit;
  font-size: 12px;
  line-height: 1.7;
  letter-spacing: 0.32em;
  text-indent: 0.32em;
  text-transform: uppercase;
  cursor: pointer;
  transition: border-color 0.2s ease, box-shadow 0.2s ease, background 0.2s ease;
}

.sys-btn:hover,
.sys-btn:focus-visible {
  border-color: var(--sys-cyan);
  background: rgba(20, 32, 62, 0.7);
  box-shadow: 0 0 24px rgba(34, 211, 238, 0.22);
  outline: none;
}

.sys-btn:active {
  transform: translateY(1px);
}

.sys-btn:disabled {
  opacity: 0.4;
  cursor: default;
}

.sys-btn--ghost {
  border-color: var(--sys-line-soft);
  background: transparent;
  color: var(--sys-text-dim);
}

.sys-btn--danger {
  border-color: rgba(255, 93, 115, 0.45);
  background: rgba(255, 93, 115, 0.07);
  color: #ffc7cf;
}

.sys-btn--danger:hover,
.sys-btn--danger:focus-visible {
  border-color: var(--sys-danger);
  background: rgba(255, 93, 115, 0.12);
  box-shadow: 0 0 20px rgba(255, 93, 115, 0.2);
}

.sys-btn--sm {
  padding: 9px 14px;
  font-size: 10px;
  letter-spacing: 0.22em;
  text-indent: 0.22em;
}

.sys-btn-row {
  display: flex;
  gap: 10px;
}

/* ------------------------------------------------------------------ bars -- */

/* Width is driven by --value (0–100) so it can be animated by transitioning
   the custom property target rather than rewriting the style attribute. */
.sys-bar {
  --value: 0;
  --bar-color: var(--sys-xp);
  position: relative;
  height: 8px;
  overflow: hidden;
  background: rgba(3, 7, 18, 0.8);
  border: 1px solid var(--sys-line-soft);
  border-radius: 1px;
}

.sys-bar__fill {
  position: relative;
  height: 100%;
  width: calc(var(--value) * 1%);
  overflow: hidden;
  background: linear-gradient(to right, color-mix(in srgb, var(--bar-color) 55%, transparent), var(--bar-color));
  box-shadow: 0 0 14px color-mix(in srgb, var(--bar-color) 70%, transparent);
  transition: width 1s cubic-bezier(0.16, 1, 0.3, 1);
}

/* A glinting highlight sweeping across the fill, same shine technique as the
   trophy chip — the bar reads as charged/energized rather than a flat
   progress indicator. */
.sys-bar__fill::after {
  content: "";
  position: absolute;
  inset: 0;
  background: linear-gradient(100deg, transparent 30%, rgba(255, 255, 255, 0.55) 48%, transparent 65%);
  background-size: 250% 100%;
  animation: sys-bar-shine 2.4s ease-in-out infinite;
}

@keyframes sys-bar-shine {
  0% { background-position: 150% 0; }
  100% { background-position: -50% 0; }
}

@media (prefers-reduced-motion: reduce) {
  .sys-bar__fill::after {
    animation: none;
  }
}

.sys-bar--tall {
  height: 12px;
}

.sys-bar-label {
  display: flex;
  justify-content: space-between;
  align-items: baseline;
  margin-bottom: 6px;
  font-size: 9px;
  letter-spacing: 0.24em;
  text-transform: uppercase;
  color: var(--sys-text-dim);
}

.sys-bar-label__value {
  font-variant-numeric: tabular-nums;
  letter-spacing: 0.1em;
  color: var(--sys-text);
}

/* ------------------------------------------------------------- currency -- */

.sys-chips {
  display: flex;
  flex-wrap: wrap;
  gap: 8px;
}

.sys-chip {
  display: inline-flex;
  align-items: center;
  gap: 7px;
  padding: 8px 13px;
  border: 1px solid var(--sys-line-soft);
  border-radius: 3px;
  background: linear-gradient(170deg, rgba(10, 17, 36, 0.75), rgba(3, 7, 18, 0.7));
  font-size: 13px;
  font-weight: 500;
  font-variant-numeric: tabular-nums;
  letter-spacing: 0.12em;
  color: var(--sys-text);
  box-shadow: inset 0 1px 0 rgba(232, 244, 255, 0.05);
}

/* -------------------------------------------------------------- sys-icon -- */

/* Real shapes (see ApplicationHelper#sys_icon) instead of the plain unicode
   glyphs above, each animated to feel alive rather than flat. */
.sys-icon {
  width: 15px;
  height: 15px;
  flex: none;
  overflow: visible;
}

.sys-icon path,
.sys-icon circle,
.sys-icon rect {
  fill: currentColor;
}

/* CSS-masked icons (see ApplicationHelper#sys_icon_mask) — a plain <img>
   can't follow currentColor, so these paint a solid block with
   background-color instead, shaped by the source PNG's alpha channel. */
.sys-icon--mask {
  display: inline-block;
  background-color: currentColor;
  -webkit-mask-size: contain;
  mask-size: contain;
  -webkit-mask-repeat: no-repeat;
  mask-repeat: no-repeat;
  -webkit-mask-position: center;
  mask-position: center;
}

.sys-icon--flame {
  width: 20px;
  height: 20px;
  color: var(--sys-cyan);
  animation: flame-pulse 1.2s ease-in-out infinite;
}

/* Matches the native boot sigil's flame: scale + glow breathing in and out,
   rather than the skew-based flicker this used before. */
@keyframes flame-pulse {
  0%, 100% { transform: scale(0.95); filter: drop-shadow(0 0 4px rgba(34, 211, 238, 0.5)); }
  50% { transform: scale(1.05); filter: drop-shadow(0 0 10px rgba(34, 211, 238, 0.9)); }
}

.sys-icon--xp {
  color: #eaf6ff;
  animation: icon-glow-pulse 1.8s ease-in-out infinite 0.15s;
}

.sys-icon--coin {
  color: var(--sys-gold);
  animation: icon-glow-pulse 1.8s ease-in-out infinite;
}

.sys-icon--coin .sys-icon__ring {
  fill: none;
  stroke: rgba(5, 8, 18, 0.5);
  stroke-width: 1.5;
}

.sys-icon--gem {
  color: var(--sys-gem);
  animation: icon-glow-pulse 1.8s ease-in-out infinite 0.3s;
}

.sys-icon--trophy {
  color: var(--sys-gold);
  filter: drop-shadow(0 0 3px rgba(255, 200, 74, 0.7));
}

@keyframes icon-glow-pulse {
  0%, 100% { filter: drop-shadow(0 0 1px currentColor); }
  50% { filter: drop-shadow(0 0 5px currentColor); }
}

/* Trophy chip gets its own metallic, sweeping shine instead of a static fill —
   the "silver shining background" the icon itself (gold) sits on top of. */
.sys-chip--trophy {
  position: relative;
  overflow: hidden;
  border-color: rgba(255, 200, 74, 0.35);
}

.sys-chip--trophy::before {
  content: "";
  position: absolute;
  inset: 0;
  background: linear-gradient(
    100deg,
    transparent 20%,
    rgba(232, 244, 255, 0.35) 45%,
    rgba(232, 244, 255, 0.05) 55%,
    transparent 80%
  );
  background-size: 250% 100%;
  animation: chip-shine 3.2s ease-in-out infinite;
}

@keyframes chip-shine {
  0% { background-position: 120% 0; }
  100% { background-position: -60% 0; }
}

@media (prefers-reduced-motion: reduce) {
  .sys-icon--flame,
  .sys-icon--coin,
  .sys-icon--gem,
  .sys-chip--trophy::before {
    animation: none;
  }
}

/* Rolling counters (reward screen) hold their width so the row doesn't jitter
   as digits change. */
.sys-count {
  font-variant-numeric: tabular-nums;
}

/* ----------------------------------------------------------------- badge -- */

.sys-badge {
  display: inline-block;
  padding: 3px 8px;
  border: 1px solid currentColor;
  border-radius: 2px;
  font-size: 9px;
  letter-spacing: 0.24em;
  text-indent: 0.24em;
  text-transform: uppercase;
}

.sys-badge--common { color: var(--sys-common); }
.sys-badge--rare { color: var(--sys-rare); }
.sys-badge--epic { color: var(--sys-epic); }
.sys-badge--legendary { color: var(--sys-legendary); }
.sys-badge--locked { color: var(--sys-text-faint); }

/* ---------------------------------------------------------------- sprite -- */

/* CraftPix sheets: every frame is 128x128 laid out in one row, but the frame
   count differs per sheet, so each usage sets --frames. */
.sys-sprite {
  --frames: 6;
  --frame-size: 128px;
  --sprite-scale: 2;
  --sprite-duration: 0.9s;
  width: calc(var(--frame-size) * var(--sprite-scale));
  height: calc(var(--frame-size) * var(--sprite-scale));
  background-repeat: no-repeat;
  background-size:
    calc(var(--frame-size) * var(--frames) * var(--sprite-scale))
    calc(var(--frame-size) * var(--sprite-scale));
  /* Keeps the pixel art crisp instead of smeared by the default smoothing. */
  image-rendering: pixelated;
  animation: sys-sprite-play var(--sprite-duration) steps(var(--frames)) infinite;
}

@keyframes sys-sprite-play {
  to {
    background-position: calc(var(--frame-size) * var(--frames) * var(--sprite-scale) * -1) 0;
  }
}

/* ---------------------------------------------------------------- topbar -- */

/* Flat rather than notched — mirroring the tab bar's notch onto the bottom
   of a top-fixed strip read as the cuts pointing the wrong way. A clean
   glowing edge reads better for a persistent status strip, and gives the
   two bars some visual variety instead of being identical shapes. */
.sys-topbar {
  position: fixed;
  left: 0;
  right: 0;
  top: 0;
  z-index: 10;
  border-bottom: 1px solid var(--sys-line);
  background: rgba(5, 8, 18, 0.94);
  backdrop-filter: blur(12px);
  box-shadow: 0 1px 20px rgba(34, 211, 238, 0.22);
}

/* Grid rather than flex + space-between: the brand mark needs to sit dead
   centre regardless of how wide the trophy chip or gear button are, which
   space-between can't guarantee when the two side items differ in width. */
.sys-topbar__inner {
  display: grid;
  grid-template-columns: 1fr auto 1fr;
  align-items: center;
  min-height: var(--sys-topbar-height);
  padding: calc(8px + env(safe-area-inset-top)) 16px 8px;
}

/* Slightly smaller than a default .sys-chip so it matches the gear
   button's size instead of visibly outweighing it. */
.sys-topbar__trophy {
  justify-self: start;
  padding: 6px 11px;
}

.sys-topbar__trophy .sys-icon--flame {
  width: 17px;
  height: 17px;
}

.sys-topbar__brand {
  justify-self: center;
  margin: 0;
  white-space: nowrap;
}

.sys-topbar__settings {
  justify-self: end;
}

/* ---------------------------------------------------------------- tabbar -- */

/* Same notched "system window" construction as .sys-card: an outer layer
   painted as a clipped gradient (the border), covered by an inner layer that
   holds the real dark, blurred fill. Only the top corners get notched — the
   bottom edge sits flush against the screen edge, so there's no edge there
   to decorate. */
.sys-tabbar {
  position: fixed;
  left: 0;
  right: 0;
  bottom: 0;
  z-index: 10;
  padding: 1px 1px 0;
  clip-path: polygon(16px 0, calc(100% - 16px) 0, 100% 16px, 100% 100%, 0 100%, 0 16px);
  background: linear-gradient(
    180deg,
    rgba(34, 211, 238, 0.6),
    rgba(34, 211, 238, 0.18) 55%,
    rgba(34, 211, 238, 0.45)
  );
  filter: drop-shadow(0 -6px 20px rgba(34, 211, 238, 0.14));
}

.sys-tabbar__inner {
  display: flex;
  clip-path: inherit;
  padding-bottom: env(safe-area-inset-bottom);
  background: rgba(5, 8, 18, 0.94);
  backdrop-filter: blur(12px);
}

.sys-tabbar__item {
  flex: 1;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: 4px;
  height: var(--sys-tabbar-height);
  font-size: 8px;
  letter-spacing: 0.2em;
  text-indent: 0.2em;
  text-transform: uppercase;
  color: var(--sys-text-faint);
  transition: color 0.2s ease;
}

.sys-tabbar__icon {
  display: flex;
  line-height: 1;
}

.sys-tabbar__icon .sys-icon {
  width: 18px;
  height: 18px;
}

.sys-tabbar__item--active {
  color: var(--sys-cyan);
  text-shadow: 0 0 12px rgba(34, 211, 238, 0.6);
}

/* The trophy tab keeps its own gold glow (matches the trophy chip elsewhere)
   rather than switching to cyan on active — it reads as a fixed "premium"
   accent rather than a state indicator. */
.sys-tabbar__item--active .sys-icon:not(.sys-icon--trophy) {
  filter: drop-shadow(0 0 6px currentColor);
}

/* Active marker sits on the top border. */
.sys-tabbar__item--active::before {
  content: "";
  position: absolute;
  top: 0;
  width: 26px;
  height: 2px;
  background: var(--sys-cyan);
  box-shadow: 0 0 10px var(--sys-cyan);
}

.sys-tabbar__item {
  position: relative;
}

/* ----------------------------------------------------------------- flash -- */

/* Fixed overlay so a flash message reads as a system alert dropped over
   whatever's on screen, centered, rather than pushing page content down. */
.sys-flash-stack {
  position: fixed;
  inset: 0;
  z-index: 100;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: 10px;
  padding: 24px;
  pointer-events: none;
}

/* Same panel as the native splash's "Reforge Your Life" button and the boot
   screen's "System Booting Up" text (SplashView.swift / SystemBootView.swift)
   — dark bordered box, pulsing edge, four corner brackets, tracked uppercase
   text — so a flash message reads as the same "system" voice as the rest of
   the app's chrome. */
.sys-flash {
  --flash-accent: var(--sys-cyan);
  position: relative;
  max-width: 320px;
  padding: 12px 28px;
  border: 1px solid var(--flash-accent);
  border-radius: 2px;
  background:
    linear-gradient(var(--flash-accent), var(--flash-accent)) 0 0 / 10px 1.5px no-repeat,
    linear-gradient(var(--flash-accent), var(--flash-accent)) 0 0 / 1.5px 10px no-repeat,
    linear-gradient(var(--flash-accent), var(--flash-accent)) 100% 0 / 10px 1.5px no-repeat,
    linear-gradient(var(--flash-accent), var(--flash-accent)) 100% 0 / 1.5px 10px no-repeat,
    linear-gradient(var(--flash-accent), var(--flash-accent)) 100% 100% / 10px 1.5px no-repeat,
    linear-gradient(var(--flash-accent), var(--flash-accent)) 100% 100% / 1.5px 10px no-repeat,
    linear-gradient(var(--flash-accent), var(--flash-accent)) 0 100% / 10px 1.5px no-repeat,
    linear-gradient(var(--flash-accent), var(--flash-accent)) 0 100% / 1.5px 10px no-repeat,
    rgba(13, 13, 31, 0.6);
  font-size: 13px;
  font-weight: 500;
  letter-spacing: 0.2em;
  text-indent: 0.2em;
  text-transform: uppercase;
  text-align: center;
  color: rgba(255, 255, 255, 0.9);
  animation: sys-flash-pulse 2.4s ease-in-out infinite;
}

.sys-flash--alert {
  --flash-accent: var(--sys-danger);
}

@keyframes sys-flash-pulse {
  0%, 100% {
    opacity: 0.75;
    border-color: color-mix(in srgb, var(--flash-accent) 40%, transparent);
  }
  50% {
    opacity: 1;
    border-color: color-mix(in srgb, var(--flash-accent) 90%, transparent);
  }
}

/* Fade-out triggered by the flash Stimulus controller after 5s. */
.sys-flash--out {
  animation: none;
  transition: opacity 0.3s ease, transform 0.3s ease;
  opacity: 0;
  transform: scale(0.96);
}

@media (prefers-reduced-motion: reduce) {
  .sys-flash {
    animation: none;
    opacity: 1;
  }
}

/* ------------------------------------------------------------------ a11y -- */

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

  .sys-bar__fill,
  .sys-btn,
  .sys-tabbar__item {
    transition: none;
  }
}

/* button_to wraps its button in a form, which would otherwise break the flex
   row the tab bar relies on. */
.sys-tabbar__form {
  flex: 1;
  display: flex;
  margin: 0;
}

.sys-tabbar__item--button {
  border: 0;
  background: none;
  font-family: inherit;
  cursor: pointer;
}

/* ============================================================== rewards == */

/* One screen at a time: the flow fills the viewport and only the active screen
   is laid out, so screens can't be scrolled past or peeked at. */
.reward-flow {
  position: fixed;
  inset: 0;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  padding: calc(46px + env(safe-area-inset-top)) 20px calc(28px + env(safe-area-inset-bottom));
  overflow-y: auto;
}

.reward-progress {
  position: absolute;
  top: calc(20px + env(safe-area-inset-top));
  left: 0;
  right: 0;
  display: flex;
  justify-content: center;
  gap: 6px;
}

.reward-progress__pip {
  width: 20px;
  height: 2px;
  background: var(--sys-line-soft);
  transition: background 0.3s ease, box-shadow 0.3s ease;
}

.reward-progress__pip--done {
  background: rgba(34, 211, 238, 0.5);
}

.reward-progress__pip--active {
  background: var(--sys-cyan);
  box-shadow: 0 0 10px var(--sys-cyan);
}

.reward-screen {
  display: none;
  width: 100%;
  max-width: 390px;
}

.reward-screen .sys-eyebrow {
  justify-content: center;
  margin-bottom: 12px;
}

.reward-screen--active {
  display: block;
  /* A slight overshoot instead of a flat ease — reads as the screen arriving
     with some weight behind it rather than just fading up. */
  animation: reward-screen-in 0.6s cubic-bezier(0.34, 1.56, 0.64, 1) both;
}

@keyframes reward-screen-in {
  from {
    opacity: 0;
    transform: translateY(18px) scale(0.96);
  }
}

/* Gold instead of plain white, and it stays alive with a slow breathing glow
   rather than settling into a static look once the entrance reveal ends —
   the previous version only glowed gold for its entrance, then faded back to
   the same white/cyan look every other panel title uses. */
.reward-screen .sys-panel__title {
  color: var(--sys-gold);
  animation:
    reward-title-in 0.7s cubic-bezier(0.16, 1, 0.3, 1) both,
    reward-title-glow 2.2s ease-in-out 0.7s infinite;
}

@keyframes reward-title-in {
  0% {
    opacity: 0;
    transform: scale(0.9);
    text-shadow: 0 0 46px rgba(255, 200, 74, 1);
  }
  100% {
    opacity: 1;
    transform: scale(1);
    text-shadow: 0 0 26px rgba(255, 200, 74, 0.65);
  }
}

@keyframes reward-title-glow {
  0%, 100% { text-shadow: 0 0 26px rgba(255, 200, 74, 0.65); }
  50% { text-shadow: 0 0 38px rgba(255, 200, 74, 0.95); }
}

@media (prefers-reduced-motion: reduce) {
  .reward-screen--active,
  .reward-screen .sys-panel__title {
    animation: none;
  }
}

.reward-next {
  margin-top: 22px;
  text-decoration: none;
}

.reward-loot {
  display: flex;
  justify-content: center;
  gap: 12px;
  flex-wrap: wrap;
}

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

.reward-loot__item {
  flex: 1 1 0;
  min-width: 88px;
  padding: 16px 10px;
  border: 1px solid var(--sys-line-soft);
  border-radius: 2px;
  background: rgba(3, 7, 18, 0.6);
  text-align: center;
}

.reward-loot__value {
  display: block;
  font-size: 28px;
  font-weight: 200;
  letter-spacing: 0.04em;
  color: #fff;
}

.reward-loot__item--xp .reward-loot__value { color: var(--sys-xp); text-shadow: 0 0 18px rgba(34, 211, 238, 0.5); }
.reward-loot__item--coins .reward-loot__value { color: var(--sys-gold); text-shadow: 0 0 18px rgba(255, 200, 74, 0.45); }
.reward-loot__item--gems .reward-loot__value { color: var(--sys-gem); text-shadow: 0 0 18px rgba(185, 139, 255, 0.45); }

.reward-loot__label {
  display: block;
  margin-top: 4px;
  font-size: 9px;
  letter-spacing: 0.28em;
  text-indent: 0.28em;
  text-transform: uppercase;
  color: var(--sys-text-dim);
}

.reward-xp {
  margin-top: 22px;
}

.reward-levelup {
  margin-top: 14px;
  text-align: center;
  font-size: 13px;
  letter-spacing: 0.24em;
  text-indent: 0.24em;
  text-transform: uppercase;
  color: var(--sys-gold);
  text-shadow: 0 0 20px rgba(255, 200, 74, 0.6);
  animation: reward-pulse 1.4s ease-in-out infinite;
}

@keyframes reward-pulse {
  50% { opacity: 0.65; }
}

.reward-stats {
  display: flex;
  flex-wrap: wrap;
  justify-content: center;
  gap: 8px;
  margin-top: 12px;
}

.reward-stats__item {
  padding: 5px 10px;
  border: 1px solid var(--sys-line-soft);
  border-radius: 2px;
  font-size: 10px;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  color: var(--sys-text-dim);
}

.reward-stats__item strong {
  margin-left: 4px;
  color: var(--sys-cyan);
}

/* Level-up washes the panel with gold for a beat. */
.reward--levelled .reward-screen--active .sys-panel__inner {
  animation: reward-levelflash 1.2s ease-out;
}

@keyframes reward-levelflash {
  0% { box-shadow: inset 0 0 0 rgba(255, 200, 74, 0); }
  25% { box-shadow: inset 0 0 90px rgba(255, 200, 74, 0.25); }
  100% { box-shadow: inset 0 0 0 rgba(255, 200, 74, 0); }
}

/* --------------------------------------------------- achievement screen -- */

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

.achievement-screen__index {
  margin-top: 8px;
  font-size: 10px;
  letter-spacing: 0.2em;
  color: var(--sys-text-faint);
}

.achievement-screen__seal {
  margin: 20px 0 14px;
  font-size: 40px;
  line-height: 1;
  color: var(--sys-gold);
  text-shadow: 0 0 30px rgba(255, 200, 74, 0.7);
  animation: achievement-seal-in 0.7s cubic-bezier(0.34, 1.56, 0.64, 1) both;
}

@keyframes achievement-seal-in {
  from { opacity: 0; transform: scale(0.4) rotate(-25deg); }
}

.achievement-screen__title {
  margin-bottom: 8px;
  font-size: 18px;
  font-weight: 300;
  letter-spacing: 0.18em;
  text-indent: 0.18em;
  text-transform: uppercase;
  color: var(--sys-gold);
  text-shadow: 0 0 22px rgba(255, 200, 74, 0.5);
}

.achievement-screen .sys-rule {
  margin: 18px 0;
}

/* ---------------------------------------------------------------- chest -- */

.chest {
  --chest-color: var(--sys-common);
  margin: 22px 0 4px;
  position: relative;
  display: flex;
  align-items: center;
  justify-content: center;
  height: 130px;
}

.chest--rare { --chest-color: var(--sys-rare); }
.chest--epic { --chest-color: var(--sys-epic); }
.chest--legendary { --chest-color: var(--sys-legendary); }

.chest--tappable {
  cursor: pointer;
}

.chest--tappable .chest__box {
  animation: chest-idle 2.4s ease-in-out infinite, chest-tap-glow 1.6s ease-in-out infinite;
}

@keyframes chest-tap-glow {
  50% { filter: drop-shadow(0 0 10px var(--chest-color)); }
}

/* The beat right before the lid pops — a rattling charge-up so the reveal
   has a moment of tension instead of cutting straight to the payoff. */
.chest--charge .chest__box {
  animation: chest-charge 0.22s ease-in-out;
}

.chest--charge .chest__glow {
  opacity: 1;
  animation: chest-charge-glow 0.22s ease-in-out;
}

@keyframes chest-charge {
  0%, 100% { transform: translateX(0) translateY(-4px); }
  20% { transform: translate(-3px, -5px) rotate(-2deg); }
  40% { transform: translate(3px, -3px) rotate(2deg); }
  60% { transform: translate(-2px, -5px) rotate(-1.5deg); }
  80% { transform: translate(2px, -4px) rotate(1deg); }
}

@keyframes chest-charge-glow {
  0% { opacity: 0; transform: scale(0.85); }
  100% { opacity: 0.9; transform: scale(1.05); }
}

/* Sits below the chest, invisible until the chest is actually waiting on a
   tap — .sys-eyebrow's proportions (size/tracking/case) without borrowing
   the class itself, since this needs its own absolute positioning. */
.chest__hint {
  position: absolute;
  bottom: -26px;
  left: 50%;
  transform: translateX(-50%);
  font-size: 9px;
  font-weight: 500;
  letter-spacing: 0.3em;
  text-transform: uppercase;
  color: var(--sys-cyan);
  white-space: nowrap;
  opacity: 0;
  transition: opacity 0.3s ease;
}

.chest--tappable .chest__hint {
  opacity: 0.85;
  animation: chest-hint-pulse 1.6s ease-in-out infinite;
}

@keyframes chest-hint-pulse {
  50% { opacity: 0.4; }
}

.chest__glow {
  position: absolute;
  width: 150px;
  height: 150px;
  border-radius: 50%;
  background: radial-gradient(circle, color-mix(in srgb, var(--chest-color) 40%, transparent), transparent 65%);
  opacity: 0;
  transition: opacity 0.5s ease;
}

.chest--epic .chest__glow { width: 175px; height: 175px; }
.chest--legendary .chest__glow { width: 200px; height: 200px; }

.chest--open .chest__glow {
  opacity: 1;
  animation: chest-glow 1.6s ease-in-out infinite;
}

@keyframes chest-glow {
  50% { transform: scale(1.15); }
}

/* A single shockwave ring that punches outward the instant the lid pops,
   distinct from the sparks and the lingering ambient glow. */
.chest__burst {
  position: absolute;
  width: 92px;
  height: 92px;
  border-radius: 50%;
  border: 2px solid var(--chest-color);
  opacity: 0;
}

.chest--open .chest__burst {
  animation: chest-burst 0.6s cubic-bezier(0.16, 1, 0.3, 1) forwards;
}

@keyframes chest-burst {
  0% { opacity: 0.9; transform: scale(0.4); }
  100% { opacity: 0; transform: scale(2.4); }
}

/* A shaft of light rising out of the chest — reserved for the higher
   rarities so a legendary drop reads as meaningfully bigger than a common
   one, not just a different tint. */
.chest__beam {
  position: absolute;
  bottom: 34px;
  width: 46px;
  height: 0;
  background: linear-gradient(to top, color-mix(in srgb, var(--chest-color) 55%, transparent), transparent 85%);
  clip-path: polygon(38% 100%, 62% 100%, 100% 0%, 0% 0%);
  opacity: 0;
  filter: blur(2px);
}

.chest--epic.chest--open .chest__beam,
.chest--legendary.chest--open .chest__beam {
  animation: chest-beam 1.4s cubic-bezier(0.16, 1, 0.3, 1) forwards;
}

.chest--legendary .chest__beam { width: 62px; }

@keyframes chest-beam {
  0% { height: 0; opacity: 0; }
  15% { opacity: 0.8; }
  100% { height: 180px; opacity: 0; }
}

/* The chest is drawn rather than an image, so it can be tinted per rarity from
   a single CSS custom property. */
.chest__box {
  position: relative;
  width: 92px;
  height: 72px;
  animation: chest-idle 2.4s ease-in-out infinite;
}

@keyframes chest-idle {
  50% { transform: translateY(-4px); }
}

.chest__body {
  position: absolute;
  bottom: 0;
  width: 92px;
  height: 46px;
  border: 2px solid var(--chest-color);
  border-radius: 3px;
  background: linear-gradient(170deg, rgba(10, 17, 36, 0.95), rgba(5, 8, 18, 0.98));
  box-shadow: inset 0 0 20px color-mix(in srgb, var(--chest-color) 22%, transparent);
}

/* Corner rivets — small studs so the box reads as crafted metal rather than
   a flat CSS rectangle. */
.chest__body::before,
.chest__body::after {
  content: "";
  position: absolute;
  top: 5px;
  width: 4px;
  height: 4px;
  border-radius: 50%;
  background: var(--chest-color);
  box-shadow: 0 0 4px var(--chest-color);
  opacity: 0.85;
}

.chest__body::before { left: 6px; }
.chest__body::after { right: 6px; }

.chest__lid {
  position: absolute;
  top: 0;
  width: 92px;
  height: 30px;
  border: 2px solid var(--chest-color);
  border-radius: 6px 6px 2px 2px;
  background: linear-gradient(170deg, rgba(16, 26, 52, 0.95), rgba(8, 13, 28, 0.98));
  transform-origin: bottom center;
  transition: transform 0.5s cubic-bezier(0.34, 1.56, 0.64, 1);
}

.chest--open .chest__lid {
  transform: rotateX(112deg) translateY(-6px);
}

.chest__lock {
  position: absolute;
  top: 24px;
  left: 50%;
  width: 12px;
  height: 14px;
  transform: translateX(-50%);
  border: 2px solid var(--chest-color);
  border-radius: 2px;
  background: rgba(5, 8, 18, 0.9);
  transition: opacity 0.3s ease;
}

.chest--open .chest__lock {
  opacity: 0;
}

/* Sparks fan out on open; each is rotated to its own angle by --spark, spread
   evenly around --spark-total so the fan stays even regardless of how many a
   given rarity spawns. */
.chest__spark {
  position: absolute;
  width: 3px;
  height: 3px;
  border-radius: 50%;
  background: var(--chest-color);
  opacity: 0;
  box-shadow: 0 0 8px var(--chest-color);
}

/* A little size/reach variance keeps the burst from reading as a mechanical,
   perfectly-identical fan of dots. */
.chest__spark:nth-child(3n) { width: 4px; height: 4px; }
.chest__spark:nth-child(3n + 1) { width: 2px; height: 2px; }

.chest--open .chest__spark {
  animation: chest-spark 1s ease-out forwards;
  animation-delay: calc(var(--spark) * (0.6s / var(--spark-total)));
  transform: rotate(calc(var(--spark) * (360deg / var(--spark-total))));
}

.chest--open .chest__spark:nth-child(3n) {
  animation-name: chest-spark-far;
}

@keyframes chest-spark {
  0% { opacity: 1; translate: 0 0; }
  100% { opacity: 0; translate: 0 -64px; }
}

@keyframes chest-spark-far {
  0% { opacity: 1; translate: 0 0; }
  100% { opacity: 0; translate: 0 -84px; }
}

.chest__contents {
  margin-top: 12px;
  text-align: center;
  opacity: 0;
  transform: translateY(8px) scale(0.94);
  transition: opacity 0.4s ease, transform 0.4s cubic-bezier(0.16, 1, 0.3, 1);
}

.chest__contents--visible {
  opacity: 1;
  transform: none;
}

.chest__reward {
  font-size: 24px;
  font-weight: 200;
  color: var(--chest-color, #fff);
  text-shadow: 0 0 18px currentColor;
}

/* Item names run longer than a 2-3 digit amount ever would — smaller and a
   touch heavier so longer names stay legible instead of overflowing. */
.chest__reward--item {
  font-size: 17px;
  font-weight: 500;
  letter-spacing: 0.02em;
}

.chest__reward-label {
  display: block;
  margin-top: 2px;
  font-size: 9px;
  letter-spacing: 0.28em;
  text-indent: 0.28em;
  text-transform: uppercase;
  color: var(--sys-text-dim);
}

/* --------------------------------------------------------- achievement -- */

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

.achievement-unlock .sys-eyebrow {
  justify-content: center;
}

.achievement-unlock__title {
  margin: 8px 0 6px;
  font-size: 16px;
  font-weight: 300;
  letter-spacing: 0.18em;
  text-indent: 0.18em;
  text-transform: uppercase;
  color: var(--sys-gold);
  text-shadow: 0 0 22px rgba(255, 200, 74, 0.5);
}

.achievement-unlock__chips {
  justify-content: center;
  margin-top: 12px;
}

@media (prefers-reduced-motion: reduce) {
  .chest__lid,
  .chest__contents { transition: none; }

  .reward-screen--active,
  .achievement-screen__seal,
  .chest__box,
  .chest__glow,
  .chest__spark,
  .chest__burst,
  .chest__beam,
  .chest__hint,
  .reward-levelup,
  .reward--levelled .reward-screen--active .sys-panel__inner { animation: none; }
}

/* ================================================================ guild == */

.hero {
  display: flex;
  gap: 14px;
  align-items: center;
}

/* Fixed box so the sprite's idle bob never nudges the layout. */
/* Tall enough to hold the whole sprite (128px frame at 1.1x = ~141px) plus the
   53px it is lifted off the base — so the character's feet land level with
   the XP bar instead of sinking below it. */
.hero__portrait {
  position: relative;
  flex: none;
  width: 92px;
  height: 197px;
  overflow: hidden;
}

/* Lifted off the base so the character reads as standing in the card rather
   than sinking out of it — the sheets carry empty rows under the feet, which
   made the old flush placement look low. */
.hero__portrait .sys-sprite {
  position: absolute;
  left: 50%;
  bottom: 53px;
  transform: translateX(-50%);
}

/* Same fix as the summon screen's class-option sprite: the male warrior
   sheet's art sits left of its own frame's centre, scaled here to this
   portrait's 1.1x instead of the summon screen's 1.4x (~30px natural -> 33px
   rendered, vs. ~30px -> 42px there). */
.hero__portrait .sys-sprite[data-class-type="warrior"][data-sprite-gender="male"] {
  transform: translateX(-50%) translateX(33px);
}

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

.hero__name {
  margin: 4px 0 12px;
  font-size: 27px;
  font-weight: 300;
  letter-spacing: 0.12em;
  color: #fff;
  text-shadow: 0 0 24px rgba(34, 211, 238, 0.55);
  animation: hero-name-glow 3s ease-in-out infinite;
}

@keyframes hero-name-glow {
  0%, 100% { text-shadow: 0 0 24px rgba(34, 211, 238, 0.55); }
  50% { text-shadow: 0 0 34px rgba(34, 211, 238, 0.8); }
}

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

/* Coins/gems stay grouped on the left; trophy sits alone on the right,
   pushed to the far edge instead of just tacked on after gems. */
.hero__chips {
  display: flex;
  align-items: center;
  justify-content: space-between;
  margin-top: 16px;
}

/* ----------------------------------------------------------- dropdown menu -- */

.dropdown-menu {
  position: relative;
}

.dropdown-menu__trigger {
  display: flex;
  align-items: center;
  justify-content: center;
  width: 34px;
  height: 34px;
  padding: 0;
  border: 1px solid var(--sys-line-soft);
  border-radius: 2px;
  background: rgba(3, 7, 18, 0.6);
  color: var(--sys-text-dim);
  cursor: pointer;
  transition: border-color 0.2s ease, color 0.2s ease;
}

.dropdown-menu__trigger:hover,
.dropdown-menu__trigger:focus-visible {
  border-color: var(--sys-cyan);
  color: var(--sys-text);
  outline: none;
}

.sys-icon--gear {
  width: 17px;
  height: 17px;
  transition: transform 0.3s ease;
}

.dropdown-menu__trigger:has(+ .dropdown-menu__panel--open) .sys-icon--gear {
  transform: rotate(45deg);
}

/* Fixed rather than absolute — its position is computed in JS from the
   trigger's actual screen position. The hero card clips its own contents to
   its notched shape (clip-path), so a menu opening downward from a trigger
   near the card's edge got cut off the instant it grew past that boundary;
   fixed positioning escapes that clipping entirely (clip-path on an ancestor
   doesn't create a containing block the way transform would).
   Same notched "system window" construction as .sys-card: this outer layer
   is the 1px border, painted via a clipped gradient background; the inner
   layer covers all but that 1px with the actual dark fill. A curtain drop
   rather than a plain fade — clipped by max-height so it unrolls downward
   from the trigger instead of just appearing. */
.dropdown-menu__panel {
  position: fixed;
  z-index: 20;
  min-width: 150px;
  max-height: 0;
  overflow: hidden;
  padding: 1px;
  clip-path: polygon(8px 0, 100% 0, 100% calc(100% - 8px), calc(100% - 8px) 100%, 0 100%, 0 8px);
  background: linear-gradient(160deg, rgba(34, 211, 238, 0.75), rgba(34, 211, 238, 0.22) 45%, rgba(34, 211, 238, 0.55));
  filter: drop-shadow(0 0 18px rgba(34, 211, 238, 0.22));
  opacity: 0;
  transition: max-height 0.25s ease, opacity 0.2s ease;
}

.dropdown-menu__panel--open {
  max-height: 120px;
  opacity: 1;
}

.dropdown-menu__panel-inner {
  clip-path: inherit;
  background: linear-gradient(170deg, var(--sys-surface), rgba(5, 8, 18, 0.98));
}

.dropdown-menu__form {
  margin: 0;
}

.dropdown-menu__item {
  display: block;
  width: 100%;
  padding: 12px 16px;
  border: 0;
  background: none;
  font-family: inherit;
  font-size: 11px;
  letter-spacing: 0.16em;
  text-transform: uppercase;
  text-align: left;
  color: var(--sys-danger);
  cursor: pointer;
  transition: background 0.15s ease;
}

.dropdown-menu__item:hover,
.dropdown-menu__item:focus-visible {
  background: rgba(255, 93, 115, 0.1);
  outline: none;
}

.section-head {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  margin: 4px 0 12px;
}

.section-head__count {
  font-size: 10px;
  font-variant-numeric: tabular-nums;
  letter-spacing: 0.16em;
  color: var(--sys-text-faint);
}

/* ---------------------------------------------------------------- quest -- */

.quest__head {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: 10px;
}

.quest__title {
  font-size: 14px;
  font-weight: 400;
  letter-spacing: 0.1em;
  color: var(--sys-text);
}

.quest__status {
  flex: none;
  font-size: 9px;
  letter-spacing: 0.2em;
  text-indent: 0.2em;
  text-transform: uppercase;
}

.quest__status--done { color: var(--sys-cyan); }
.quest__status--skipped { color: var(--sys-text-faint); }

.quest__description {
  margin-top: 8px;
  font-size: 12px;
  line-height: 1.6;
  letter-spacing: 0.03em;
  color: var(--sys-text-dim);
}

.quest__rewards {
  margin-top: 12px;
}

.quest__actions {
  margin-top: 16px;
}

/* button_to wraps each button in its own form; make the forms the flex items. */
.quest__form {
  flex: 1;
  margin: 0;
}

.quest__form--narrow {
  flex: 0 0 34%;
}

/* Completed and skipped quests recede rather than disappear. */
.quest--done,
.quest--skipped {
  filter: none;
  opacity: 0.72;
}

.quest--done {
  background: linear-gradient(160deg, rgba(34, 211, 238, 0.5), rgba(34, 211, 238, 0.14) 45%, rgba(34, 211, 238, 0.3));
}

.quest--skipped {
  background: linear-gradient(160deg, rgba(120, 140, 170, 0.4), rgba(120, 140, 170, 0.12));
  filter: none;
}

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

.quest-cleared .sys-eyebrow {
  justify-content: center;
  margin-bottom: 8px;
}

/* =============================================================== status == */

/* One unified "character sheet" panel — a bordered STATUS title, a big level
   number beside the job/name, HP/MP-style bracketed pill bars, and a dense
   two-column stat grid — closer to a classic RPG status window than the
   previous stacked-bar-per-stat list this replaced. */
.status-sheet {
  text-align: center;
}

.status-sheet__title {
  display: inline-block;
  margin-bottom: 0;
  padding: 5px 22px;
  border: 1px solid var(--sys-line);
  border-radius: 2px;
  font-size: 12px;
  font-weight: 500;
  letter-spacing: 0.4em;
  text-indent: 0.4em;
  text-transform: uppercase;
  color: var(--sys-text);
  box-shadow: 0 0 14px rgba(34, 211, 238, 0.2);
}

.status-sheet__stage {
  position: relative;
  height: 110px;
  margin: 0 0 2px;
}

.status-sheet__stage .sys-sprite {
  position: absolute;
  left: 50%;
  bottom: 16px;
  transform: translateX(-50%);
}

/* Same male-warrior-only fix as elsewhere (Guild's hero portrait, the summon
   screen) — the misalignment is a fixed 30px in the sprite's own 128px
   frame, so it scales linearly with whatever scale this context renders at
   (1.3x here: 30 * 1.3 = 39px). */
.status-sheet__stage .sys-sprite[data-class-type="warrior"][data-sprite-gender="male"] {
  transform: translateX(-50%) translateX(39px);
}

.status-sheet__stage::after {
  content: "";
  position: absolute;
  left: 50%;
  bottom: 2px;
  width: 100px;
  height: 11px;
  transform: translateX(-50%);
  border-radius: 50%;
  background: radial-gradient(ellipse, rgba(34, 211, 238, 0.35), transparent 70%);
}

.status-sheet__header {
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 20px;
  margin-top: 14px;
  padding: 14px 0;
  border-top: 1px solid var(--sys-line-soft);
  border-bottom: 1px solid var(--sys-line-soft);
}

.status-sheet__level {
  display: flex;
  flex-direction: column;
  align-items: center;
  flex: none;
}

.status-sheet__level-label {
  font-size: 9px;
  letter-spacing: 0.3em;
  text-transform: uppercase;
  color: var(--sys-text-faint);
}

.status-sheet__level-value {
  font-size: 40px;
  font-weight: 200;
  line-height: 1;
  color: #fff;
  text-shadow: 0 0 24px rgba(34, 211, 238, 0.55);
}

.status-sheet__identity {
  text-align: left;
}

.status-sheet__name {
  font-size: 18px;
  font-weight: 400;
  letter-spacing: 0.06em;
  color: var(--sys-text);
}

.status-sheet__job {
  margin-top: 3px;
  font-size: 10px;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  color: var(--sys-text-dim);
}

/* An HP/MP-style bracketed pill rather than the thin flat strip used
   everywhere else — this page's whole point is to feel like a distinct
   "status window," not a repeat of the Guild XP bar. */
.status-sheet__xp {
  display: flex;
  align-items: center;
  gap: 10px;
  margin-top: 16px;
}

.status-sheet__xp .sys-icon {
  flex: none;
}

.status-sheet__pill {
  position: relative;
  flex: 1;
  height: 20px;
  overflow: hidden;
  border: 1px solid var(--sys-line-soft);
  border-radius: 10px;
  background: rgba(3, 7, 18, 0.8);
}

.status-sheet__pill-fill {
  height: 100%;
  background: linear-gradient(to right, color-mix(in srgb, var(--sys-xp) 55%, transparent), var(--sys-xp));
  box-shadow: 0 0 12px color-mix(in srgb, var(--sys-xp) 60%, transparent);
  transition: width 1s cubic-bezier(0.16, 1, 0.3, 1);
}

.status-sheet__pill-label {
  position: absolute;
  inset: 0;
  display: flex;
  align-items: center;
  justify-content: center;
  font-size: 10px;
  font-variant-numeric: tabular-nums;
  letter-spacing: 0.06em;
  color: var(--sys-text);
  text-shadow: 0 1px 2px rgba(0, 0, 0, 0.8);
}

/* Dense icon+label+value grid instead of a bar per stat — closer to a
   classic stat sheet than a set of progress indicators (these values have
   no ceiling to show progress toward anyway). */
.status-sheet__stats {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: 10px;
  margin-top: 18px;
  text-align: left;
}

.status-sheet__stat {
  display: flex;
  align-items: center;
  gap: 8px;
  padding: 8px 10px;
  border: 1px solid var(--sys-line-soft);
  border-radius: 2px;
  background: rgba(3, 7, 18, 0.5);
}

.status-sheet__stat .sys-icon {
  flex: none;
  color: var(--sys-cyan);
}

.status-sheet__stat-label {
  flex: 1;
  font-size: 10px;
  letter-spacing: 0.1em;
  text-transform: uppercase;
  color: var(--sys-text-dim);
}

.status-sheet__stat-value {
  font-variant-numeric: tabular-nums;
  font-weight: 500;
  color: var(--sys-text);
}

.status-sheet__chips {
  justify-content: center;
  margin-top: 18px;
}

/* =============================================================== awards == */

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

.awards-summary .sys-eyebrow {
  justify-content: center;
}

/* A conic-gradient ring rather than the linear bar every other progress
   indicator in the app already uses — this page is the "trophy room," so it
   gets its own centrepiece instead of reusing .sys-bar again. */
.awards-ring {
  --pct: 0;
  position: relative;
  width: 168px;
  height: 168px;
  margin: 18px auto 4px;
  border-radius: 50%;
  background: conic-gradient(
    var(--sys-cyan) calc(var(--pct) * 1%),
    rgba(34, 211, 238, 0.12) 0
  );
  filter: drop-shadow(0 0 24px rgba(34, 211, 238, 0.35));
  animation: awards-ring-pulse 3s ease-in-out infinite;
}

.awards-ring::before {
  content: "";
  position: absolute;
  inset: 11px;
  border-radius: 50%;
  background: radial-gradient(circle, rgba(10, 17, 36, 0.97), rgba(5, 8, 18, 0.98));
  box-shadow: inset 0 1px 0 rgba(232, 244, 255, 0.06);
}

.awards-ring__center {
  position: absolute;
  inset: 0;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
}

.awards-ring__value {
  font-size: 42px;
  font-weight: 200;
  color: #fff;
  line-height: 1;
  text-shadow: 0 0 26px rgba(34, 211, 238, 0.65);
}

.awards-ring__total {
  margin-top: 4px;
  font-size: 12px;
  letter-spacing: 0.1em;
  color: var(--sys-text-faint);
}

@keyframes awards-ring-pulse {
  0%, 100% { filter: drop-shadow(0 0 24px rgba(34, 211, 238, 0.35)); }
  50% { filter: drop-shadow(0 0 34px rgba(34, 211, 238, 0.55)); }
}

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

/* Category headers get an icon matching their theme instead of plain text —
   most fall back to the trophy glyph, but the ones with an obvious match
   (level/streak/coins/gems) reuse the exact icon already seen for that stat
   everywhere else in the app. */
.awards-category {
  display: flex;
  align-items: center;
  gap: 7px;
}

.awards {
  display: grid;
  gap: 10px;
}

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

.shop-tagline {
  margin-top: 4px;
  font-size: 10px;
  letter-spacing: 0.1em;
  color: var(--sys-text-dim);
  text-align: center;
}

/* Browser-only fallback for the leaderboard — native shows its own richer
   version (see bridge/leaderboard_controller.js / LeaderboardView.swift). */
.leaderboard-fallback {
  list-style: none;
  padding: 0;
  margin: 16px 0 0;
}

.leaderboard-fallback li {
  display: flex;
  align-items: center;
  justify-content: space-between;
  padding: 10px 0;
  border-bottom: 1px solid var(--sys-line-soft);
  font-size: 13px;
  color: var(--sys-text);
}

.leaderboard-fallback li span:last-child {
  display: inline-flex;
  align-items: center;
  gap: 6px;
}

.shop-funds {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 12px;
  padding: 0 2px;
}

.shop-funds__label {
  font-size: 12px;
  letter-spacing: 0.06em;
  color: var(--sys-text-dim);
}

.shop-empty {
  padding: 18px 14px;
  border: 1px solid var(--sys-line-soft);
  border-radius: 2px;
  background: rgba(5, 8, 18, 0.5);
  font-size: 11px;
  letter-spacing: 0.06em;
  text-align: center;
  color: var(--sys-text-dim);
}

/* A dedicated vertical card grid instead of forcing the achievement/
   inventory row-card sideways into two narrow columns — that's what made
   the shop feel cramped, since that card was built for a single wide
   column, not half a row. */
.shop-grid {
  display: grid;
  grid-template-columns: repeat(2, minmax(0, 1fr));
  gap: 12px;
}

.shop-item {
  display: flex;
  flex-direction: column;
  align-items: center;
  /* Rows already stretch to equal height via CSS Grid's default — this is
     what makes the price footer land at the same bottom line on every card
     regardless of how much name/effect text precedes it, instead of
     leftover space just piling up under a short card's last line. */
  justify-content: space-between;
  gap: 8px;
  padding: 18px 14px;
  border: 1px solid var(--sys-line-soft);
  border-radius: 2px;
  background: rgba(5, 8, 18, 0.6);
  text-align: center;
}

.shop-item__icon {
  display: flex;
  align-items: center;
  justify-content: center;
  width: 52px;
  height: 52px;
  flex: none;
  border: 1px solid currentColor;
  border-radius: 50%;
  color: var(--sys-text-faint);
}

.shop-item__icon .sys-icon {
  width: 22px;
  height: 22px;
}

.shop-item--common .shop-item__icon {
  color: var(--sys-common);
}

.shop-item--rare .shop-item__icon {
  color: var(--sys-rare);
  box-shadow: 0 0 14px color-mix(in srgb, var(--sys-rare) 30%, transparent);
}

.shop-item--epic .shop-item__icon {
  color: var(--sys-epic);
  box-shadow: 0 0 14px color-mix(in srgb, var(--sys-epic) 30%, transparent);
}

.shop-item--legendary .shop-item__icon {
  color: var(--sys-legendary);
  box-shadow: 0 0 14px color-mix(in srgb, var(--sys-legendary) 35%, transparent);
}

.shop-item__name {
  font-size: 13px;
  font-weight: 400;
  letter-spacing: 0.06em;
  color: var(--sys-text);
}

.shop-item__effect {
  font-size: 10.5px;
  line-height: 1.5;
  color: var(--sys-text-dim);
}

.shop-item__price {
  display: flex;
  gap: 8px;
  width: 100%;
  margin-top: 4px;
  padding-top: 10px;
  border-top: 1px solid var(--sys-line-soft);
}

/* button_to wraps each chip in its own <form>; stretch those equally so both
   coin/gem pills stay the same size regardless of digit count. */
.shop-item__price > form {
  flex: 1;
  min-width: 0;
}

.shop-item__price .sys-chip {
  width: 100%;
  justify-content: center;
}

.award {
  position: relative;
  display: flex;
  gap: 14px;
  padding: 14px;
  overflow: hidden;
  border: 1px solid var(--sys-line-soft);
  border-radius: 2px;
  background: rgba(5, 8, 18, 0.6);
  opacity: 0.5;
  filter: saturate(0.6);
}

.award--unlocked {
  opacity: 1;
  filter: none;
  border-color: rgba(255, 200, 74, 0.4);
  background: linear-gradient(170deg, rgba(24, 19, 8, 0.85), rgba(5, 8, 18, 0.92));
}

/* Same sweeping-shine technique as .sys-chip--trophy, scaled up across the
   whole card — an unlocked achievement reads as a rewarded, still-glinting
   object, not just a card with a different border color. */
.award--unlocked::before {
  content: "";
  position: absolute;
  inset: 0;
  background: linear-gradient(
    100deg,
    transparent 30%,
    rgba(255, 226, 150, 0.16) 48%,
    transparent 66%
  );
  background-size: 250% 100%;
  animation: chip-shine 4.5s ease-in-out infinite;
  pointer-events: none;
}

/* A round medallion instead of the small square glyph box — sized and
   bordered like a real seal, with the same trophy shape used everywhere
   else in the app rather than a plain unicode mark. */
.award__seal {
  position: relative;
  flex: none;
  width: 40px;
  height: 40px;
  display: flex;
  align-items: center;
  justify-content: center;
  border: 1px solid currentColor;
  border-radius: 50%;
  color: var(--sys-text-faint);
}

.award__seal .sys-icon {
  width: 18px;
  height: 18px;
}

.award--unlocked .award__seal {
  color: var(--sys-gold);
  box-shadow: 0 0 18px rgba(255, 200, 74, 0.4), inset 0 0 10px rgba(255, 200, 74, 0.15);
}

/* Rarity badge sits above the name, not beside it — a real vertical stack
   with its own gap rather than sharing a row. */
.award__heading {
  display: flex;
  flex-direction: column;
  align-items: flex-start;
  gap: 6px;
}

.award__title {
  font-size: 13px;
  font-weight: 400;
  letter-spacing: 0.1em;
  color: var(--sys-text);
}

.award--unlocked .award__title {
  color: var(--sys-gold);
}

.award__description {
  margin-top: 4px;
  font-size: 11px;
  line-height: 1.5;
  color: var(--sys-text-dim);
}

/* How many unused copies of a potion are left — disappears along with the
   Use button once the count hits zero. */
.award__quantity {
  margin-top: 6px;
  font-size: 10px;
  font-weight: 500;
  letter-spacing: 0.1em;
  color: var(--sys-cyan);
}

.award__chips {
  margin-top: 10px;
}

/* Extra breathing room on the right so the equip/unequip/use button
   (absolutely positioned there) never sits over the title/description
   text. */
.award--actionable {
  padding-right: 92px;
}

/* Docked to the right edge and vertically centered against the seal+body
   row, rather than stacked under the description. */
.award__equip-form {
  position: absolute;
  top: 50%;
  right: 14px;
  transform: translateY(-50%);
  margin: 0;
}

/* Bracketed HUD-style button — corner marks instead of a plain filled
   rect, matching the "system window" language used everywhere else
   (status pill, panel notches) rather than a generic form button. */
.award__equip {
  position: relative;
  width: auto;
  padding: 8px 14px;
  border: 1px solid var(--sys-line);
  border-radius: 0;
  background: rgba(5, 8, 18, 0.7);
  color: var(--sys-cyan);
  font-family: inherit;
  font-size: 9px;
  font-weight: 500;
  letter-spacing: 0.22em;
  text-indent: 0.22em;
  text-transform: uppercase;
  cursor: pointer;
  transition: background 0.2s ease, box-shadow 0.2s ease;
}

.award__equip::before,
.award__equip::after {
  content: "";
  position: absolute;
  width: 6px;
  height: 6px;
  pointer-events: none;
}

.award__equip::before {
  top: -1px;
  left: -1px;
  border-top: 1px solid var(--sys-cyan);
  border-left: 1px solid var(--sys-cyan);
}

.award__equip::after {
  bottom: -1px;
  right: -1px;
  border-bottom: 1px solid var(--sys-cyan);
  border-right: 1px solid var(--sys-cyan);
}

.award__equip:hover,
.award__equip:focus-visible {
  background: rgba(20, 32, 62, 0.85);
  box-shadow: 0 0 16px rgba(34, 211, 238, 0.25);
  outline: none;
}

/* Undiscovered items — same dimmed base as a locked achievement, plus a
   drifting static overlay and a flickering "?" in place of any real
   identity, so nothing about an item you don't own reads as real yet. */
.award--mystery::after {
  content: "";
  position: absolute;
  inset: 0;
  background: repeating-linear-gradient(
    115deg,
    transparent 0 6px,
    rgba(232, 244, 255, 0.05) 6px 7px,
    transparent 7px 14px
  );
  background-size: 200% 200%;
  animation: mystery-static 6s linear infinite;
  pointer-events: none;
  mix-blend-mode: overlay;
}

@keyframes mystery-static {
  0% { background-position: 0 0; }
  100% { background-position: 140% 60%; }
}

.award__mystery-mark {
  font-size: 16px;
  font-weight: 600;
  color: var(--sys-text-faint);
  animation: mystery-flicker 3.2s ease-in-out infinite;
}

@keyframes mystery-flicker {
  0%, 92%, 100% { opacity: 0.7; }
  94% { opacity: 0.15; }
  96% { opacity: 0.7; }
  98% { opacity: 0.3; }
}

@media (prefers-reduced-motion: reduce) {
  .award--mystery::after,
  .award__mystery-mark {
    animation: none;
  }
}

/* Cadence chip on daily quests ("Daily", "4×/week") — reads as metadata, not
   as a reward, so it stays neutral rather than taking a currency colour. */
.sys-chip--cadence {
  color: var(--sys-text-dim);
  letter-spacing: 0.16em;
  text-transform: uppercase;
  font-size: 10px;
}

/* =========================================================== schedule == */

.section-head__edit {
  width: auto;
  margin-left: auto;
  padding-inline: 14px;
  text-decoration: none;
}

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

.schedule-intro .sys-eyebrow {
  justify-content: center;
  margin-bottom: 8px;
}

.schedule-row__head {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 10px;
  margin-bottom: 12px;
}

.schedule-row__title {
  font-size: 14px;
  font-weight: 400;
  letter-spacing: 0.08em;
  color: var(--sys-text);
}

.schedule-days {
  display: grid;
  grid-template-columns: repeat(7, 1fr);
  gap: 6px;
}

/* The checkbox itself is hidden; the label is the control, so the tap target
   stays a comfortable square rather than a tiny native box. */
.schedule-day {
  display: flex;
  align-items: center;
  justify-content: center;
  aspect-ratio: 1;
  border: 1px solid var(--sys-line-soft);
  border-radius: 2px;
  background: rgba(3, 7, 18, 0.6);
  font-size: 11px;
  letter-spacing: 0.04em;
  color: var(--sys-text-faint);
  cursor: pointer;
  transition: color 0.2s ease, border-color 0.2s ease, box-shadow 0.2s ease, background 0.2s ease;
}

.schedule-day__input {
  position: absolute;
  opacity: 0;
  pointer-events: none;
}

.schedule-day:has(.schedule-day__input:checked) {
  border-color: var(--sys-cyan);
  background: rgba(34, 211, 238, 0.12);
  color: #fff;
  box-shadow: 0 0 14px rgba(34, 211, 238, 0.22);
}

.schedule-day:has(.schedule-day__input:focus-visible) {
  outline: 1px solid var(--sys-cyan);
  outline-offset: 2px;
}

.schedule-actions {
  display: flex;
  gap: 10px;
  margin-top: 18px;
}

/* ---------------------------------------------------------- calendar -- */

.calendar__month {
  margin-bottom: 12px;
  text-align: center;
  font-size: 10px;
  letter-spacing: 0.3em;
  text-indent: 0.3em;
  text-transform: uppercase;
  color: var(--sys-text-dim);
}

.calendar__grid {
  display: grid;
  grid-template-columns: repeat(7, 1fr);
  gap: 4px;
}

.calendar__weekday {
  padding-bottom: 4px;
  text-align: center;
  font-size: 9px;
  letter-spacing: 0.1em;
  text-transform: uppercase;
  color: var(--sys-text-faint);
}

.calendar__cell {
  position: relative;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: 1px;
  aspect-ratio: 1;
  border: 1px solid var(--sys-line-soft);
  border-radius: 2px;
  /* --intensity (0-1) comes from the view: how busy this weekday is relative to
     the busiest one, so the month reads as a pattern rather than a wall of numbers. */
  background: rgba(34, 211, 238, calc(var(--intensity, 0) * 0.22));
  font-size: 10px;
  color: var(--sys-text-dim);
}

.calendar__cell--empty {
  border: 0;
  background: none;
}

.calendar__cell--rest {
  border-style: dashed;
  border-color: rgba(232, 244, 255, 0.08);
  color: var(--sys-text-faint);
}

.calendar__cell--today {
  border-color: var(--sys-cyan);
  box-shadow: 0 0 12px rgba(34, 211, 238, 0.28);
  color: #fff;
}

.calendar__day {
  font-variant-numeric: tabular-nums;
  line-height: 1;
}

.calendar__count {
  font-size: 8px;
  line-height: 1;
  letter-spacing: 0.06em;
  color: var(--sys-cyan);
}

.calendar__legend {
  margin-top: 10px;
  text-align: center;
  font-size: 10px;
}

.schedule-intro__add {
  width: auto;
  margin: 14px auto 0;
  padding-inline: 18px;
  text-decoration: none;
}

.draft-form__row {
  display: flex;
  gap: 8px;
  align-items: stretch;
}

.draft-form__row .form-control {
  flex: 1;
}

.draft-form__go {
  width: auto;
  flex: 0 0 auto;
  padding-inline: 16px;
}

/* =============================================================== buffs == */

.buffs {
  margin-top: 14px;
  padding-top: 12px;
  border-top: 1px solid var(--sys-line-soft);
}

.buffs__heading {
  margin-bottom: 8px;
  font-size: 9px;
  font-weight: 500;
  letter-spacing: 0.3em;
  text-transform: uppercase;
  color: var(--sys-text-dim);
}

.buffs__list {
  display: flex;
  flex-wrap: wrap;
  gap: 6px;
}

/* Tinted by the item's rarity, so a legendary buff reads as one at a glance
   without repeating the rarity in text.

   flex: none and nowrap are load-bearing, not tidiness: as a shrinkable flex
   child a crowded chip collapses below its own text and the label wraps to one
   character per line, which renders as a tall empty sliver. Holding its width
   makes the row wrap between chips instead, which is what it should have been
   doing all along. */
.buff {
  display: inline-flex;
  flex: none;
  align-items: center;
  gap: 5px;
  padding: 5px 9px;
  border: 1px solid currentColor;
  border-radius: 2px;
  background: rgba(3, 7, 18, 0.5);
  font-size: 10px;
  letter-spacing: 0.1em;
  white-space: nowrap;
}

.buff--common { color: var(--sys-common); }
.buff--rare { color: var(--sys-rare); }
.buff--epic { color: var(--sys-epic); }
.buff--legendary { color: var(--sys-legendary); }

/* The chip's own icon, a size below the stat chips' — it sits beside 10px text
   here rather than the hero card's 15px numbers. */
.buff .sys-icon {
  width: 11px;
  height: 11px;
  flex: none;
}

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

/* Dimmer than the number: the percentage is the news, "XP" is the label. */
.buff__effect {
  color: var(--sys-text-dim);
  letter-spacing: 0.08em;
}

/* How many items feed this total. Set apart by a hairline rather than more
   text, so it reads as an aside to the number rather than part of it. */
.buff__count {
  padding-left: 5px;
  border-left: 1px solid var(--sys-line-soft);
  font-size: 9px;
  font-variant-numeric: tabular-nums;
  color: var(--sys-text-faint);
}

.reward-buffs {
  margin-top: 18px;
  padding-top: 14px;
  border-top: 1px solid var(--sys-line-soft);
  text-align: center;
}

.reward-buffs__heading {
  margin-bottom: 8px;
  font-size: 9px;
  letter-spacing: 0.3em;
  text-indent: 0.3em;
  text-transform: uppercase;
  color: var(--sys-text-dim);
}

.reward-buffs .buffs__list {
  justify-content: center;
}

.reward-buffs__expired {
  margin-top: 10px;
  font-size: 10px;
  letter-spacing: 0.08em;
  color: var(--sys-text-faint);
}

/* ------------------------------------------------------------- streak -- */

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

/* sys_icon(:flame) is a PNG carrying its own blue-white gradient (matching the
   app icon), so this only scales it and adds the glow — `color` would do
   nothing to a raster image. */
.streak-flame {
  display: flex;
  justify-content: center;
  margin: 22px 0 4px;
  animation: streak-pulse 1.2s ease-in-out infinite alternate;
}

.streak-flame .sys-icon {
  width: 54px;
  height: 68px;
}

@keyframes streak-pulse {
  from { transform: scale(0.96); filter: drop-shadow(0 0 10px rgba(26, 115, 255, 0.35)); }
  to   { transform: scale(1.06); filter: drop-shadow(0 0 20px rgba(26, 115, 255, 0.7)); }
}

.streak-count {
  margin-top: 10px;
  font-size: 44px;
  font-weight: 300;
  line-height: 1;
  color: #fff;
  text-shadow: 0 0 24px rgba(26, 115, 255, 0.6);
}

.streak-label {
  margin-top: 4px;
  font-size: 9px;
  font-weight: 500;
  letter-spacing: 0.3em;
  text-indent: 0.3em;
  text-transform: uppercase;
  color: var(--sys-text-dim);
}

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

/* ================================================================ legal == */

/* The privacy policy is the one screen still rendered by Rails, so it has to
   pass for the native panels around it. This mirrors RewardPanel.swift: a
   beveled gold frame, an inset hairline, a sheen falling off the top edge, and
   a gem stud at each corner. */

.legal-page {
  min-height: 100dvh;
  padding: 20px 16px 40px;
  background: #090909;
}

.legal-panel {
  position: relative;
  max-width: 390px;
  margin: 0 auto;
  padding: 34px 26px 28px;
  border-radius: 16px;
  /* Two stops, top lighter — the same vertical fall the native panel uses. */
  background:
    linear-gradient(to bottom, rgba(255, 255, 255, 0.05), transparent 35%),
    linear-gradient(to bottom, #1c1c1c, #0d0d0d);
  box-shadow:
    0 0 24px rgba(255, 200, 74, 0.25),
    0 8px 14px rgba(0, 0, 0, 0.5);
}

/* The frame itself. A conic gradient stands in for the native AngularGradient:
   bright gold at top-left, dark at bottom-right, which is what reads as
   beveled metal rather than a flat outline. */
.legal-panel::before {
  content: "";
  position: absolute;
  inset: 0;
  border-radius: 16px;
  padding: 2px;
  background: conic-gradient(
    from 225deg,
    rgba(255, 200, 74, 0.95),
    rgba(255, 200, 74, 0.5),
    rgba(0, 0, 0, 0.5),
    rgba(255, 200, 74, 0.5),
    rgba(255, 200, 74, 0.95)
  );
  /* Mask out the middle so only the 2px ring paints. */
  -webkit-mask: linear-gradient(#000 0 0) content-box, linear-gradient(#000 0 0);
  -webkit-mask-composite: xor;
  mask: linear-gradient(#000 0 0) content-box, linear-gradient(#000 0 0);
  mask-composite: exclude;
  pointer-events: none;
}

/* The inset hairline sitting 4px inside the frame. */
.legal-panel::after {
  content: "";
  position: absolute;
  inset: 4px;
  border: 1px solid rgba(255, 200, 74, 0.18);
  border-radius: 12px;
  pointer-events: none;
}

/* Gem studs. Four rotated squares pinned to the corners, lit from the upper
   left like the native ones. */
.legal-panel__stud {
  position: absolute;
  width: 9px;
  height: 9px;
  transform: rotate(45deg);
  background: radial-gradient(circle at 35% 30%, rgba(255, 255, 255, 0.9), var(--sys-gold) 45%, rgba(255, 200, 74, 0.6));
  box-shadow: 0 0 6px rgba(255, 200, 74, 0.5);
  pointer-events: none;
}

.legal-panel__stud--tl { top: 7px; left: 7px; }
.legal-panel__stud--tr { top: 7px; right: 7px; }
.legal-panel__stud--bl { bottom: 7px; left: 7px; }
.legal-panel__stud--br { bottom: 7px; right: 7px; }

.legal__eyebrow {
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 10px;
  font-size: 11px;
  font-weight: 600;
  letter-spacing: 0.3em;
  text-indent: 0.3em;
  text-transform: uppercase;
  color: var(--sys-rare);
}

.legal__eyebrow::before,
.legal__eyebrow::after {
  content: "";
  width: 5px;
  height: 5px;
  background: var(--sys-gold);
  transform: rotate(45deg);
}

.legal__title {
  margin-top: 10px;
  font-size: 26px;
  font-weight: 700;
  letter-spacing: 0.06em;
  text-align: center;
  color: var(--sys-gold);
}

/* Long-form text, unlike every other screen in the app — so it gets real
   paragraph rhythm rather than the dense HUD spacing. */
.legal h2 {
  display: flex;
  align-items: center;
  gap: 8px;
  margin-top: 24px;
  margin-bottom: 10px;
  font-size: 11px;
  font-weight: 600;
  letter-spacing: 0.22em;
  text-transform: uppercase;
  color: var(--sys-gold);
}

/* The same diamond that flanks every eyebrow in the app, so these headings
   read as part of the panel vocabulary rather than plain web headings. */
.legal h2::before {
  content: "";
  width: 5px;
  height: 5px;
  flex: none;
  background: var(--sys-gold);
  transform: rotate(45deg);
}

/* And a hairline running out to the edge, the same one sys-rule uses. */
.legal h2::after {
  content: "";
  flex: 1;
  height: 1px;
  background: linear-gradient(to right, var(--sys-line-soft), transparent);
}

.legal p,
.legal li {
  font-size: 12px;
  line-height: 1.7;
  color: var(--sys-text-dim);
}

.legal p + p {
  margin-top: 10px;
}

.legal ul {
  margin: 8px 0 0;
  padding-left: 18px;
  display: flex;
  flex-direction: column;
  gap: 8px;
}

.legal strong {
  color: var(--sys-text);
}

.legal a {
  color: var(--sys-rare);
}

.legal__updated {
  margin-top: 6px;
  font-size: 10px;
  letter-spacing: 0.08em;
  color: var(--sys-text-faint);
}
