/* ═══════════════════════════════════════════════════════════════
   Fenris Labs — Companions
   Same design language as fenrislabs.io: gold on near-black, Space
   Grotesk over Inter, 13px radii, uppercase pills. This is a
   subsection of that site and should read as one.
   ─────────────────────────────────────────────────────────────── */

:root {
  color-scheme: dark;

  /* palette — lifted from fenrislabs.io/assets/css/style.css */
  --ink:       #08090b;
  --ink-2:     #0c0e12;
  --panel:     #12151b;
  --panel-2:   #171b23;
  --panel-3:   #1d222c;
  --line:      #232935;
  --line-2:    #333b4a;
  --line-soft: #1a1f28;

  --text:      #eceef4;
  --muted:     #97a0af;
  --faint:     #7f8897;   /* 5.2:1 on ink; the old #6d7684 was 4.3:1, under AA, on 10.5px text */

  --gold:      #d9a441;
  --gold-soft: #f0c479;
  --gold-dim:  rgba(217,164,65,.12);
  --gold-line: rgba(217,164,65,.32);
  --accent:    var(--gold);

  --ok:        #7fd18f;
  --ok-dim:    rgba(127,209,143,.13);
  --info:      #66c0f4;
  --info-dim:  rgba(102,192,244,.13);
  --warn:      #e0a94f;
  --warn-dim:  rgba(224,169,79,.13);
  --danger:    #ff7a6b;
  --danger-dim: rgba(255,122,107,.13);

  --wrap:      1180px;
  --bar-h:     54px;
  --gap:       13px;
  --r:         13px;
  --r-sm:      9px;

  --ease:      cubic-bezier(.22,.68,.32,1);
  --font-d:    'Space Grotesk', 'Segoe UI', system-ui, sans-serif;
  --font-b:    'Inter', 'Segoe UI', system-ui, -apple-system, sans-serif;
  --mono:      ui-monospace, 'JetBrains Mono', 'Cascadia Code', Consolas, monospace;

  --shadow:    0 12px 30px -18px rgba(0,0,0,.9);
  --shadow-lg: 0 24px 56px -26px rgba(0,0,0,.95);

  --sidebar:   232px;
}

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

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

body {
  background: var(--ink);
  color: var(--text);
  font-family: var(--font-b);
  font-size: 15.5px;
  line-height: 1.55;
  -webkit-font-smoothing: antialiased;
  min-height: 100vh;
  overflow-x: hidden;
}

h1, h2, h3, h4 {
  font-family: var(--font-d);
  font-weight: 600; line-height: 1.15; letter-spacing: -.01em;
}
h1 { font-size: clamp(1.5rem, 3.4vw, 2rem); }
h2 { font-size: clamp(1.25rem, 2.6vw, 1.6rem); }
h3 { font-size: 1.05rem; }
h4 { font-size: .95rem; }

a { color: var(--gold); text-decoration: none; transition: color .2s var(--ease); }
a:hover { color: var(--gold-soft); }
/* A submit button that reads as a link. For the places where the action
   WRITES and therefore has to be a POST carrying a CSRF token, but where a
   button would shout -- the "use her defaults" skip on first-run setup. */
button.linklike {
  background: none; border: 0; padding: 0; font: inherit; cursor: pointer;
  color: var(--gold); text-decoration: none;
  transition: color .2s var(--ease);
}
button.linklike:hover { color: var(--gold-soft); }
button.linklike:focus-visible { outline: 2px solid var(--gold); outline-offset: 2px; }
img, svg, video { display: block; max-width: 100%; height: auto; }
input, button, select, textarea { font: inherit; color: inherit; }
button { cursor: pointer; }
code, pre, .mono { font-family: var(--mono); font-size: .86em; }

::selection { background: rgba(217,164,65,.3); color: #fff; }
:focus-visible { outline: 2px solid var(--gold); outline-offset: 3px; border-radius: 6px; }

* { scrollbar-width: thin; scrollbar-color: var(--line-2) transparent; }
*::-webkit-scrollbar { width: 10px; height: 10px; }
*::-webkit-scrollbar-track { background: transparent; }
*::-webkit-scrollbar-thumb {
  background: var(--line-2); border-radius: 6px;
  border: 3px solid transparent; background-clip: content-box;
}

.sr, .sr-only {
  position: absolute; width: 1px; height: 1px; padding: 0; margin: -1px;
  overflow: hidden; clip: rect(0 0 0 0); white-space: nowrap; border: 0;
}
.skip-link {
  position: absolute; left: 12px; top: -60px; z-index: 999;
  background: var(--gold); color: #14110a; font-weight: 600;
  padding: 9px 15px; border-radius: 8px;
  transition: top .2s var(--ease);
}
.skip-link:focus { top: 10px; }

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

/* ═══════════ sticky bar (public) ═══════════ */
.bar {
  position: sticky; top: 0; z-index: 50;
  height: var(--bar-h);
  display: flex; align-items: center;
  background: rgba(8,9,11,.72);
  backdrop-filter: blur(14px) saturate(140%);
  -webkit-backdrop-filter: blur(14px) saturate(140%);
  border-bottom: 1px solid transparent;
  transition: border-color .3s var(--ease), background .3s var(--ease);
}
.bar.is-stuck { background: rgba(8,9,11,.92); border-bottom-color: var(--line); }
.bar__inner {
  width: 100%; max-width: var(--wrap); margin-inline: auto;
  padding-inline: 20px;
  display: flex; align-items: center; gap: 16px;
}
.brand { display: flex; align-items: center; gap: 10px; margin-right: auto; color: var(--text); }
.brand:hover { color: var(--text); }
.brand img { width: 30px; height: 30px; transition: transform .5s var(--ease); }
.brand:hover img { transform: rotate(-12deg) scale(1.07); }
.brand span {
  font-family: var(--font-d); font-weight: 700; font-size: 15.5px;
  letter-spacing: .13em; text-transform: uppercase; white-space: nowrap;
}
.brand .dim { color: var(--muted); font-weight: 500; letter-spacing: .13em; }
.bar__links { display: flex; align-items: center; gap: 3px; }
.bar__links a {
  padding: 7px 12px; border-radius: var(--r-sm);
  color: var(--muted); font-size: 13.5px; font-weight: 500;
  transition: color .2s var(--ease), background .2s var(--ease);
}
.bar__links a:hover { color: var(--text); background: rgba(255,255,255,.07); }
.bar__cta {
  padding: 7px 14px !important; margin-left: 6px;
  background: rgba(217,164,65,.12); color: var(--gold-soft) !important;
  border: 1px solid var(--gold-line);
  font-weight: 600 !important;
}
.bar__cta:hover { background: rgba(217,164,65,.2) !important; color: var(--gold-soft) !important; }

/* ═══════════ hero ═══════════ */
.hero {
  position: relative; overflow: hidden;
  padding: clamp(46px, 8vw, 88px) 20px clamp(38px, 6vw, 62px);
  background: radial-gradient(115% 130% at 50% -30%, #1a2130 0%, #0d1016 45%, var(--ink) 76%);
  border-bottom: 1px solid var(--line);
}
.hero__stars { position: absolute; inset: 0; width: 100%; height: 100%; }
.hero__glow {
  position: absolute; top: -110%; left: 50%; transform: translateX(-50%);
  width: min(680px, 120vw); aspect-ratio: 1;
  background: radial-gradient(circle, rgba(159,182,216,.16), transparent 62%);
  pointer-events: none;
}
.hero__inner {
  position: relative; z-index: 1;
  max-width: var(--wrap); margin-inline: auto;
}
.hero__mark {
  width: clamp(54px, 7vw, 70px);
  filter: drop-shadow(0 0 30px rgba(159,182,216,.35));
  margin-bottom: 16px;
}
.hero h1 {
  font-size: clamp(1.9rem, 5.2vw, 3.1rem);
  font-weight: 700; letter-spacing: .03em; line-height: 1.08;
  max-width: 18ch;
  background: linear-gradient(180deg, #fff 20%, #b4becf 100%);
  -webkit-background-clip: text; background-clip: text; color: transparent;
}
.hero h1 em {
  font-style: normal;
  background: linear-gradient(180deg, var(--gold-soft) 10%, var(--gold) 100%);
  -webkit-background-clip: text; background-clip: text; color: transparent;
}
.hero p {
  margin-top: 14px; max-width: 56ch;
  color: var(--muted); font-size: clamp(.95rem, 1.8vw, 1.06rem); line-height: 1.6;
}
.hero .cta { margin-top: 26px; display: flex; gap: 10px; flex-wrap: wrap; }
.hero.center { text-align: center; }
.hero.center h1, .hero.center p { margin-inline: auto; }
.hero.center .cta { justify-content: center; }

/* ═══════════ layout ═══════════ */
.wrap { width: 100%; max-width: var(--wrap); margin-inline: auto; padding-inline: 20px; }
.section { padding: clamp(34px, 6vw, 62px) 0; }
.section-head { margin-bottom: 26px; }
.section-head h2 { font-size: clamp(1.3rem, 3vw, 1.8rem); }
.section-head p { color: var(--muted); margin-top: 8px; max-width: 62ch; font-size: .95rem; }

.shell { display: flex; min-height: 100vh; }
.sidebar {
  width: var(--sidebar); flex: 0 0 var(--sidebar);
  background: var(--ink-2);
  border-right: 1px solid var(--line);
  display: flex; flex-direction: column;
  position: sticky; top: 0; height: 100vh; overflow-y: auto;
}
.sidebar-brand {
  display: flex; align-items: center; gap: 10px;
  padding: 13px 16px 11px; color: var(--text);
}
.sidebar-brand:hover { color: var(--text); }
.sidebar-brand img { width: 28px; height: 28px; }
.sidebar-brand b {
  font-family: var(--font-d); font-weight: 700; font-size: 13.5px;
  letter-spacing: .13em; text-transform: uppercase; display: block;
}
.sidebar-brand small {
  display: block; font-size: 10.5px; font-weight: 500; color: var(--gold);
  letter-spacing: .14em; text-transform: uppercase;
}
.nav { padding: 4px 8px 16px; display: flex; flex-direction: column; gap: 1px; }
.nav-label {
  padding: 14px 8px 6px;
  font-family: var(--font-d);
  font-size: 10.5px; font-weight: 600; letter-spacing: .12em;
  text-transform: uppercase; color: var(--faint);
}
.nav a {
  display: flex; align-items: center; gap: 9px;
  padding: 7px 9px; border-radius: var(--r-sm);
  color: var(--muted); font-size: 13.5px; font-weight: 500;
  transition: background .18s var(--ease), color .18s var(--ease);
}
.nav a:hover { background: rgba(255,255,255,.055); color: var(--text); }
.nav a.active {
  background: var(--gold-dim); color: var(--gold-soft); font-weight: 600;
  box-shadow: inset 2px 0 0 var(--gold);
}
.nav a .count {
  margin-left: auto; font-size: 10.5px; color: var(--faint);
  background: var(--panel-2); padding: 1px 6px; border-radius: 999px;
}
.sidebar-foot {
  margin-top: auto; padding: 11px 14px;
  border-top: 1px solid var(--line);
  font-size: 12px; color: var(--muted);
  display: flex; align-items: center; gap: 8px;
}
.main { flex: 1 1 auto; min-width: 0; display: flex; flex-direction: column; }
.topbar {
  display: flex; align-items: center; gap: 12px;
  height: var(--bar-h); padding: 0 20px;
  border-bottom: 1px solid var(--line);
  background: rgba(8,9,11,.86);
  backdrop-filter: blur(14px) saturate(140%);
  position: sticky; top: 0; z-index: 30;
}
.topbar h1 { font-size: 1.05rem; font-weight: 600; letter-spacing: -.005em; }
.page { padding: 22px 20px 70px; max-width: var(--wrap); width: 100%; }
.page-head { margin-bottom: 20px; }
.page-head p { color: var(--muted); margin-top: 6px; max-width: 68ch; font-size: .92rem; }
.menu-btn { display: none; }
.spacer { flex: 1; }

@media (max-width: 900px) {
  .sidebar {
    position: fixed; z-index: 60; inset: 0 auto 0 0;
    transform: translateX(-100%);
    transition: transform .24s var(--ease);
    box-shadow: var(--shadow-lg);
  }
  body.nav-open .sidebar { transform: none; }
  body.nav-open::after {
    content: ""; position: fixed; inset: 0; z-index: 55; background: rgba(0,0,0,.6);
  }
  .menu-btn { display: grid; place-items: center; }
  .page { padding: 16px 15px 70px; }
  .topbar { padding: 0 15px; }
  .wrap { padding-inline: 15px; }
}

/* ═══════════ primitives ═══════════ */
.card {
  background: var(--panel);
  border: 1px solid var(--line);
  border-radius: var(--r);
  padding: 15px 16px;
}
.card.pad-0 { padding: 0; }
/* A card's heading is the same size whether it is an h2 or an h3, so a page
   can fix its heading order without every card title changing size. Every
   rule below that sizes a heading in a panel names both levels for the same
   reason. This one sits ABOVE .card-head so the head's smaller size wins. */
.card h2, .card h3 { font-size: 1.05rem; }
.card-head {
  display: flex; align-items: center; gap: 10px;
  padding: 12px 16px; border-bottom: 1px solid var(--line);
}
.card-head h2, .card-head h3 { font-size: .97rem; }
.card-body { padding: 15px 16px; }
.card-foot {
  padding: 11px 16px; border-top: 1px solid var(--line);
  background: var(--ink-2);
  border-radius: 0 0 var(--r) var(--r);
  display: flex; gap: 9px; align-items: center;
}

.grid { display: grid; gap: var(--gap); }
.grid.cols-2 { grid-template-columns: repeat(auto-fit, minmax(320px, 1fr)); }
.grid.cols-3 { grid-template-columns: repeat(auto-fit, minmax(250px, 1fr)); }
.grid.cols-4 { grid-template-columns: repeat(auto-fit, minmax(186px, 1fr)); }
.row { display: flex; gap: 9px; align-items: center; flex-wrap: wrap; }
.row.tight { gap: 5px; }
.row.between { justify-content: space-between; }
.stack { display: flex; flex-direction: column; gap: var(--gap); }
.stack.tight { gap: 7px; }

.muted { color: var(--muted); }
.faint { color: var(--faint); }
.small { font-size: .855rem; }
.tiny  { font-size: .745rem; }
.nowrap { white-space: nowrap; }
.right { text-align: right; }
.center { text-align: center; }
hr { border: 0; border-top: 1px solid var(--line); margin: 16px 0; }

/* stat tiles */
.stat {
  background: var(--panel); border: 1px solid var(--line);
  border-radius: var(--r); padding: 12px 14px;
}
.stat .k {
  font-family: var(--font-d);
  font-size: 10.5px; font-weight: 600; letter-spacing: .12em;
  text-transform: uppercase; color: var(--faint);
}
.stat .v {
  font-family: var(--font-d);
  font-size: 1.7rem; font-weight: 600; letter-spacing: -.02em;
  margin-top: 3px; font-variant-numeric: tabular-nums; line-height: 1.1;
}
.stat .sub { font-size: .76rem; color: var(--muted); margin-top: 3px; line-height: 1.35; }
.stat.accent .v { color: var(--gold-soft); }
.stat.ok .v { color: var(--ok); }
.stat.warn .v { color: var(--warn); }
.stat.danger .v { color: var(--danger); }

/* ═══════════ buttons ═══════════ */
.btn {
  display: inline-flex; align-items: center; justify-content: center; gap: 7px;
  padding: 8px 15px;
  border: 1px solid var(--line-2);
  border-radius: var(--r-sm);
  background: var(--panel-2); color: var(--text);
  font-size: .855rem; font-weight: 550;
  transition: background .2s var(--ease), border-color .2s var(--ease), transform .12s var(--ease);
  white-space: nowrap;
}
.btn:hover { background: var(--panel-3); color: var(--text); border-color: var(--line-2); }
.btn:active { transform: translateY(1px); }
.btn[disabled], .btn.disabled { opacity: .42; pointer-events: none; }
.btn-primary {
  background: linear-gradient(180deg, var(--gold-soft), var(--gold));
  border-color: var(--gold); color: #17130a; font-weight: 650;
  box-shadow: 0 8px 22px -12px rgba(217,164,65,.9);
}
.btn-primary:hover {
  background: linear-gradient(180deg, #f6d093, var(--gold-soft));
  color: #17130a; border-color: var(--gold-soft);
}
.btn-danger { color: var(--danger); border-color: rgba(255,122,107,.35); }
.btn-danger:hover { background: var(--danger-dim); color: var(--danger); }
.btn-ghost { background: transparent; border-color: transparent; color: var(--muted); }
.btn-ghost:hover { background: rgba(255,255,255,.06); color: var(--text); }
.btn-sm { padding: 5px 10px; font-size: .78rem; min-height: 28px; }
.btn-lg { padding: 11px 22px; font-size: .97rem; }
.btn-block { width: 100%; }

.btn-group { display: inline-flex; max-width: 100%; }
.btn-group .btn { border-radius: 0; margin-left: -1px; }
.btn-group .btn:first-child { border-radius: var(--r-sm) 0 0 var(--r-sm); margin-left: 0; }
.btn-group .btn:last-child { border-radius: 0 var(--r-sm) var(--r-sm) 0; }
.btn-group .btn.active {
  background: var(--gold-dim); color: var(--gold-soft);
  border-color: var(--gold-line); z-index: 1;
}
/* a filter row must never widen the page on a phone — scroll it instead */
.btn-group.scrollable {
  overflow-x: auto; scrollbar-width: none; -webkit-overflow-scrolling: touch;
}
.btn-group.scrollable::-webkit-scrollbar { display: none; }
.btn-group.scrollable .btn { flex: 0 0 auto; }

/* ═══════════ pills / badges ═══════════ */
.badge, .pill {
  display: inline-flex; align-items: center; gap: 5px;
  padding: 3px 9px; border-radius: 999px;
  font-size: 10.5px; font-weight: 600; letter-spacing: .07em;
  text-transform: uppercase; font-style: normal;
  border: 1px solid rgba(255,255,255,.17);
  background: rgba(10,12,16,.6);
  color: #d3d9e4; white-space: nowrap;
}
.badge.ok, .pill--live   { border-color: rgba(127,209,143,.55); color: var(--ok); }
.badge.info, .pill--good { border-color: rgba(102,192,244,.5);  color: #8ed3f7; }
.badge.warn, .pill--soon { border-color: rgba(224,169,79,.55);  color: var(--gold-soft); }
.badge.danger { border-color: rgba(255,122,107,.5); color: var(--danger); }
.badge.accent { border-color: var(--gold-line); color: var(--gold-soft); background: var(--gold-dim); }

.tag {
  display: inline-block;
  font-size: 10.5px; font-weight: 500; padding: 2px 8px; border-radius: 999px;
  background: rgba(10,12,16,.6); color: var(--muted);
  border: 1px solid rgba(255,255,255,.1);
}
.tag-row { display: flex; gap: 5px; flex-wrap: wrap; }

.dot { width: 7px; height: 7px; border-radius: 50%; background: var(--faint); flex: 0 0 7px; }
.dot.ok { background: var(--ok); }
.dot.warn { background: var(--warn); }
.dot.danger { background: var(--danger); }
.dot.live { animation: ping 2.2s var(--ease) infinite; }
@keyframes ping {
  0%       { box-shadow: 0 0 0 0 rgba(126,222,149,.6); }
  70%,100% { box-shadow: 0 0 0 6px rgba(126,222,149,0); }
}

/* ═══════════ forms ═══════════ */
.field { display: flex; flex-direction: column; gap: 5px; margin-bottom: 14px; }
.field > label {
  font-size: .855rem; font-weight: 600; color: var(--text);
  display: flex; align-items: center; gap: 7px;
}
.field .help { font-size: .765rem; color: var(--muted); line-height: 1.45; }
.field .err { font-size: .765rem; color: var(--danger); }
/* Stands in for a label in a row of fields where one control has none, so
   the inputs beside it still line up. The height is the label's line box. */
.field-spacer { display: block; height: 1.3em; }

input[type=text], input[type=number], input[type=url], input[type=email],
input[type=search], input[type=date], input[type=time], input[type=tel],
input[type=password], input[type=datetime-local], select, textarea {
  width: 100%;
  padding: 8px 11px;
  background: var(--ink-2);
  border: 1px solid var(--line);
  border-radius: var(--r-sm);
  color: var(--text);
  transition: border-color .2s var(--ease), box-shadow .2s var(--ease);
}
input:hover, select:hover, textarea:hover { border-color: var(--line-2); }
input:focus, select:focus, textarea:focus {
  outline: none; border-color: var(--gold);
  box-shadow: 0 0 0 3px var(--gold-dim);
}
input[type=search] { -webkit-appearance: none; appearance: none; }
input[readonly], input:disabled, select:disabled, textarea[readonly], textarea:disabled {
  background: var(--panel); color: var(--muted); cursor: not-allowed;
}
textarea { resize: vertical; min-height: 88px; line-height: 1.55; }
textarea.tall { min-height: 250px; }
/* app.js sizes a box to its content and sets an explicit height. min-height
   would beat that for anything shorter than the floor, so a field that has
   been measured gives up its floor -- the JS clamps it instead. */
textarea.is-autosized, textarea.tall.is-autosized { min-height: 0; }
textarea.mono { font-family: var(--mono); font-size: .82rem; }
select {
  appearance: none;
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='12' height='12' viewBox='0 0 12 12'%3E%3Cpath d='M2.5 4.5L6 8l3.5-3.5' fill='none' stroke='%2397a0af' stroke-width='1.5' stroke-linecap='round'/%3E%3C/svg%3E");
  background-repeat: no-repeat; background-position: right 10px center;
  padding-right: 30px;
}
.check { display: flex; align-items: flex-start; gap: 9px; cursor: pointer; }
.check input[type=checkbox] {
  width: 16px; height: 16px; margin-top: 3px; flex: 0 0 16px;
  accent-color: var(--gold); cursor: pointer;
}
.check span { font-size: .855rem; }
.check .help { display: block; color: var(--muted); font-size: .765rem; margin-top: 2px; }
.counter {
  margin-left: auto; font-size: .72rem; color: var(--faint);
  font-variant-numeric: tabular-nums;
}
.counter.warn { color: var(--warn); }
.counter.over { color: var(--danger); font-weight: 600; }
fieldset { border: 0; padding: 0; }
legend {
  font-family: var(--font-d);
  font-size: 10.5px; font-weight: 600; letter-spacing: .12em;
  text-transform: uppercase; color: var(--faint); padding: 0 0 7px;
}

/* ═══════════ tables ═══════════ */
.table-wrap { overflow-x: auto; border-radius: var(--r); }
table { width: 100%; border-collapse: collapse; font-size: .855rem; }
thead th {
  text-align: left; padding: 9px 13px;
  font-family: var(--font-d);
  font-size: 10.5px; font-weight: 600; letter-spacing: .1em;
  text-transform: uppercase; color: var(--faint);
  border-bottom: 1px solid var(--line); white-space: nowrap;
}
tbody td { padding: 10px 13px; border-bottom: 1px solid var(--line-soft); vertical-align: middle; }
tbody tr:last-child td { border-bottom: 0; }
tbody tr { transition: background .15s var(--ease); }
tbody tr:hover { background: rgba(255,255,255,.022); }
td.num, th.num { text-align: right; font-variant-numeric: tabular-nums; }
td.mono { font-family: var(--mono); font-size: .8rem; }

/* ═══════════ tabs ═══════════ */
.tabs {
  /* Wrap rather than scroll. It WAS overflow-x:auto with the scrollbar
     hidden, which meant the last tabs existed but nothing on screen said so
     -- "Renter permissions" was simply clipped off the right edge. Wrapping
     can't hide a tab at any width. */
  display: flex; flex-wrap: wrap; gap: 2px 0;
  border-bottom: 1px solid var(--line); margin-bottom: 20px;
}
.tabs a {
  padding: 9px 13px; font-size: .855rem; font-weight: 550;
  color: var(--muted); border-bottom: 2px solid transparent;
  white-space: nowrap; transition: color .2s var(--ease), border-color .2s var(--ease);
}
.tabs a:hover { color: var(--text); }
.tabs a.active { color: var(--gold-soft); border-bottom-color: var(--gold); }

/* ═══════════ notices ═══════════ */
.notice {
  display: flex; gap: 10px; align-items: flex-start;
  padding: 11px 14px; border-radius: var(--r-sm);
  font-size: .855rem; line-height: 1.5;
  border: 1px solid transparent; margin-bottom: var(--gap);
}
.notice .ico { flex: 0 0 auto; font-weight: 700; }
.notice.info   { background: var(--info-dim);   color: #9ed4fa; border-color: rgba(102,192,244,.25); }
.notice.ok     { background: var(--ok-dim);     color: var(--ok); border-color: rgba(127,209,143,.25); }
.notice.warn   { background: var(--warn-dim);   color: var(--gold-soft); border-color: var(--gold-line); }
.notice.danger { background: var(--danger-dim); color: var(--danger); border-color: rgba(255,122,107,.28); }
.notice strong { font-weight: 650; }
.notice a:not(.btn) { color: inherit; text-decoration: underline; }
/* The close control: a small ghost "x" at the right edge. Negative vertical
   margins keep a one-line notice the height it was without the button. */
.notice [data-dismiss] {
  flex: 0 0 auto; align-self: flex-start;
  margin: -4px -6px -4px auto;
  min-width: 28px; min-height: 28px; padding: 0 6px;
  background: none; border: 0; border-radius: 6px;
  color: inherit; opacity: .6; cursor: pointer;
  font-size: 1.15rem; line-height: 1;
  display: inline-flex; align-items: center; justify-content: center;
  transition: opacity .15s var(--ease), background .15s var(--ease);
}
.notice [data-dismiss]:hover { opacity: 1; background: rgba(255,255,255,.08); }

.empty {
  text-align: center; padding: 46px 24px; color: var(--muted);
  border: 1px dashed var(--line-2); border-radius: var(--r);
}
.empty h2, .empty h3 { color: var(--text); margin-bottom: 6px; }
.empty p { max-width: 44ch; margin: 0 auto 14px; font-size: .875rem; }

/* ═══════════ diff ═══════════ */
.diff {
  font-family: var(--mono); font-size: .78rem; line-height: 1.55;
  background: var(--ink-2); border: 1px solid var(--line);
  border-radius: var(--r-sm); overflow-x: auto; padding: 9px 0;
}
.diff div { padding: 0 13px; white-space: pre-wrap; word-break: break-word; }
.diff .add  { background: var(--ok-dim); color: var(--ok); }
.diff .del  { background: var(--danger-dim); color: var(--danger); }
.diff .hunk { color: #8ed3f7; background: var(--info-dim); }
.diff .meta { color: var(--faint); }
.diff .ctx  { color: var(--muted); }

/* ═══════════ charts ═══════════ */
.chart { width: 100%; height: 170px; display: block; }
.chart-legend {
  display: flex; gap: 16px; flex-wrap: wrap;
  font-size: .74rem; color: var(--muted); margin-top: 7px;
}
.chart-legend i { width: 9px; height: 9px; border-radius: 2px; display: inline-block; margin-right: 6px; }
.bar-track { height: 6px; background: var(--panel-2); border-radius: 4px; overflow: hidden; min-width: 60px; }
.bar-fill { height: 100%; background: linear-gradient(90deg, var(--gold), var(--gold-soft)); border-radius: 4px; }
.bar-fill.ok { background: var(--ok); }
.bar-fill.warn { background: var(--warn); }
.bar-fill.danger { background: var(--danger); }

/* ═══════════ storefront: cast tiles ═══════════ */
.cast-grid {
  display: grid; gap: var(--gap);
  grid-template-columns: repeat(auto-fill, minmax(262px, 1fr));
}
.cast-card {
  position: relative; overflow: hidden; isolation: isolate;
  border-radius: var(--r); border: 1px solid var(--line);
  background: var(--ink-2);
  display: flex; flex-direction: column;
  transition: border-color .25s var(--ease), transform .25s var(--ease), box-shadow .25s var(--ease);
}
.cast-card:hover {
  transform: translateY(-3px);
  border-color: color-mix(in srgb, var(--accent) 55%, var(--line));
  box-shadow: 0 18px 40px -22px color-mix(in srgb, var(--accent) 60%, #000);
}
/* Arriving from the Discord button, which links to #ch-<slug>. The browser
   scrolls her into view and nothing else says which of nine cards was meant. */
.cast-card:target {
  outline: 2px solid var(--accent);
  outline-offset: 2px;
  scroll-margin-top: 84px;
}
/* The grid has to keep ONE frame or the cards go ragged, but the art inside
   it is commissioned at whatever ratio the artist worked in -- 0.667 to 0.800
   across the current roster. `cover` filled the frame by cropping, which took
   feet and tails off the taller pieces; the character page shows them whole,
   so the grid disagreed with the page it links to.
   3/4 is the most common ratio here (three of six), so it is the frame that
   letterboxes least, and `contain` guarantees nothing is ever cut. */
.cast-art { aspect-ratio: 3 / 4; position: relative; overflow: hidden; background: var(--panel); }
.cast-art img { width: 100%; height: 100%; object-fit: contain; transition: transform .5s var(--ease); }
.cast-card:hover .cast-art img { transform: scale(1.035); }
.cast-art .placeholder {
  width: 100%; height: 100%; display: grid; place-items: center;
  font-family: var(--font-d); font-size: 2.6rem; font-weight: 600; color: var(--line-2);
  background: linear-gradient(150deg, #1c2331, #0b0d12 72%);
}
.cast-art .badge {
  position: absolute; top: 11px; right: 11px;
  backdrop-filter: blur(6px);
}
.cast-art::after {
  content: ''; position: absolute; inset: 0; z-index: 1; pointer-events: none;
  background: linear-gradient(to top, rgba(6,7,10,.55) 0%, transparent 34%);
}
.cast-body {
  padding: 12px 14px 14px; display: flex; flex-direction: column; gap: 6px; flex: 1;
  background: var(--panel); border-top: 1px solid var(--line);
}
.cast-body h2, .cast-body h3 { font-size: 1.28rem; font-weight: 600; letter-spacing: -.01em; }
.cast-body .tagline { font-size: .845rem; color: #c6cdda; line-height: 1.4; flex: 1; }
.cast-body .foot {
  display: flex; align-items: center; gap: 8px;
  padding-top: 9px; margin-top: auto; border-top: 1px solid var(--line);
  font-size: .755rem; color: var(--faint);
}
/* "Yours — DM" under the foot. Its own line rather than a third item inside
   the foot: that row is already count-on-the-left, price-on-the-right, and
   squeezing ownership between them buried the one fact that is about YOU.
   The element is rendered on EVERY card, empty when you own nothing, so the
   line it occupies is reserved and the "N with her" row above it sits at the
   same height right across the grid. Present-but-empty is what keeps the row
   level; making it conditional pushed the foot up on exactly the cards you
   most wanted to compare. */
.cast-body .cast-owned {
  margin-top: 6px; font-size: .755rem; font-weight: 600;
  color: var(--ok); letter-spacing: .01em;
  min-height: 1.05em; line-height: 1.05em;
}
/* Two across on a phone. One full-width card per screen made browsing nine
   companions nine screens of scrolling, and the art column is tall enough
   that a 3/4 frame still reads at 170px. Type steps down with the card. */
@media (max-width: 640px) and (min-width: 400px) {
  .cast-grid { grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 10px; }
  .cast-body { padding: 10px 11px 12px; gap: 5px; }
  .cast-body h2, .cast-body h3 { font-size: 1.05rem; }
  .cast-body .tagline { font-size: .78rem; }
  .cast-body .foot { font-size: .7rem; gap: 6px; }
  .cast-body .price { font-size: .85rem; }
  .cast-art .badge { top: 8px; right: 8px; font-size: 9.5px; padding: 2px 7px; }
  .tag-row { max-height: 1.6em; overflow: hidden; }
}
.cast-body .price {
  margin-left: auto; color: var(--text); font-weight: 600;
  font-family: var(--font-d); font-size: .95rem;
}

/* character page */
.char-hero {
  display: grid; grid-template-columns: minmax(0, 5fr) minmax(0, 6fr);
  gap: 34px; align-items: start; padding: 30px 0;
}
@media (max-width: 820px) { .char-hero { grid-template-columns: 1fr; gap: 20px; } }
/* minmax(0,1fr), not the default auto track. An auto track's floor is the
   min-content of what is in it, and the tier cards below the picture have a
   hard floor (see .tiers-side) that is WIDER than this column ever gets --
   .wrap caps at 1180, so the 5fr picture column never exceeds 503px while the
   two tier cards demanded 521px. The gallery box stayed 503 and its contents
   drew at 521, so the picture and the buttons hung 18px into the 34px gutter
   at wide widths and straight over the bio text below 1220px. minmax(0,1fr)
   lets the track shrink to the column and keeps everything inside it. */
.gallery { display: grid; grid-template-columns: minmax(0, 1fr); gap: 9px; }
/* No fixed aspect box on the lead image. It was 4/5 with object-fit:cover,
   which crops anything taller -- Svetlana's art is 2:3 full-body and lost her
   feet and half her tail. Character art is commissioned at whatever ratio the
   artist worked in, so the frame follows the picture rather than the picture
   being trimmed to fit the frame. */
.gallery .lead {
  border-radius: var(--r); overflow: hidden; background: var(--panel);
  border: 1px solid var(--line);
}
.gallery .lead img { width: 100%; height: auto; display: block; }
.gallery .thumbs { display: grid; grid-template-columns: repeat(4, 1fr); gap: 9px; }
/* Each thumb is a <button> around its <img>, so the keyboard reaches it. The
   frame -- radius, border, dimming -- is on the button; the picture fills it. */
.gallery .thumbs button {
  display: block; width: 100%; padding: 0; margin: 0;
  border-radius: var(--r-sm); aspect-ratio: 1; overflow: hidden;
  cursor: pointer; opacity: .62; border: 1px solid var(--line);
  background: var(--panel);
  transition: opacity .2s var(--ease), border-color .2s var(--ease);
}
.gallery .thumbs button img { width: 100%; height: 100%; object-fit: cover; }
.gallery .thumbs button:hover, .gallery .thumbs button.active {
  opacity: 1; border-color: var(--gold-line);
}

/* The rent controls, stacked under the picture. Narrower column than the old
   two-across layout, so they go vertical instead of squeezing. */
.tiers-side { display: grid; gap: 10px; margin-top: 6px; }
@media (min-width: 1100px) {
  /* Wide enough for two again, and side by side beats a tall stack that
     pushes the second tier below the fold.

     minmax(0,1fr) rather than 1fr. Plain 1fr is minmax(auto,1fr), so each
     track refuses to go under its card's min-content -- 256px, set by a
     nowrap button like "Opens Aug 15, 2026, 12:00 PM". Two of those plus the
     gap is 521px, and this column is at most 503px. */
  .tiers-side { grid-template-columns: minmax(0, 1fr) minmax(0, 1fr); align-items: start; }
  .tiers-side > .notice { grid-column: 1 / -1; }
}
/* .btn is nowrap everywhere else and should stay that way -- a button that
   wraps mid-row looks broken. In this column it is the opposite: the buttons
   are full-width and carry sentences ("Reserved -- she's being held for
   someone"), and nowrap made them the widest thing on the page. */
.tiers-side .btn { white-space: normal; }

.tier-card {
  border: 1px solid var(--line); border-radius: var(--r);
  padding: 15px 16px; background: var(--panel);
  display: flex; flex-direction: column; gap: 8px;
  transition: border-color .25s var(--ease), transform .25s var(--ease);
}
.tier-card:hover { transform: translateY(-2px); border-color: var(--line-2); }
.tier-card.featured {
  border-color: var(--gold-line);
  background: linear-gradient(180deg, var(--gold-dim), transparent 55%), var(--panel);
}
.tier-card.featured:hover {
  border-color: var(--gold);
  box-shadow: 0 18px 40px -24px rgba(217,164,65,.7);
}
.tier-card .price {
  font-family: var(--font-d);
  font-size: 1.85rem; font-weight: 600; letter-spacing: -.02em;
}
.tier-card .price small { font-size: .8rem; font-weight: 500; color: var(--muted); }
.tier-card ul {
  list-style: none; padding: 0; display: flex; flex-direction: column; gap: 5px;
  font-size: .855rem; color: var(--muted);
}
.tier-card li { display: flex; gap: 8px; }
.tier-card li::before { content: "✓"; color: var(--ok); font-weight: 700; flex: 0 0 auto; }

.steps {
  display: grid; gap: 20px;
  grid-template-columns: repeat(auto-fit, minmax(215px, 1fr)); counter-reset: step;
}
.step { position: relative; padding-left: 42px; }
.step::before {
  counter-increment: step; content: counter(step);
  position: absolute; left: 0; top: -2px;
  width: 30px; height: 30px; border-radius: var(--r-sm);
  background: var(--gold-dim); color: var(--gold-soft);
  border: 1px solid var(--gold-line);
  display: grid; place-items: center;
  font-family: var(--font-d); font-weight: 700; font-size: .82rem;
}
.step h4 { margin-bottom: 4px; }
.step p { color: var(--muted); font-size: .855rem; }

.foot {
  border-top: 1px solid var(--line);
  padding: 20px 0 30px; margin-top: 40px;
  color: var(--faint); font-size: .8rem;
}
.foot a { color: var(--muted); }
.foot a:hover { color: var(--gold); }
.foot .links { display: flex; gap: 16px; flex-wrap: wrap; margin-top: 7px; }

/* ═══════════ site links (_sitelinks.html) ═══════════
   How it works / Fenris Labs / My companions, on every page of both
   shells. These must never be hidden by a breakpoint: when the bar runs out
   of room the group scrolls sideways instead, so the links get narrower and
   stay reachable. */
.sitelinks {
  display: flex; align-items: center; gap: 3px;
  min-width: 0; flex-shrink: 1;
}
.sitelinks a {
  padding: 7px 12px; border-radius: var(--r-sm);
  color: var(--muted); font-size: 13.5px; font-weight: 500;
  white-space: nowrap;
  transition: color .2s var(--ease), background .2s var(--ease);
}
.sitelinks a:hover { color: var(--text); background: rgba(255,255,255,.07); }
.sitelinks a.active { color: var(--text); }
.sitelinks__cta {
  padding: 7px 14px !important; margin-left: 6px;
  background: rgba(217,164,65,.12); color: var(--gold-soft) !important;
  border: 1px solid var(--gold-line);
  font-weight: 600 !important;
}
.sitelinks__cta:hover {
  background: rgba(217,164,65,.2) !important; color: var(--gold-soft) !important;
}
/* In the signed-in topbar the page heading is the thing that yields, not the
   navigation: a truncated title is readable, a missing link is not. */
.topbar h1 { min-width: 0; overflow: hidden; text-overflow: ellipsis;
             white-space: nowrap; }
.topbar .sitelinks { flex-shrink: 0; }

/* ═══════════ utility ═══════════ */
.hide { display: none !important; }
/* Signing in makes the link row longer -- "My companions" is a wide CTA, and
   the owner carries "Backend" on top of it -- so the bar runs out of room at
   a width where the signed-out row still has plenty. The links are the part
   that has to survive (see _sitelinks.html), so the wordmark gives up its
   room first, the same way the signed-in topbar truncates its heading rather
   than drop a link. `--dense` is set by _public.html when the viewer is
   signed in; signed out the wordmark keeps its old breakpoints. */
@media (max-width: 800px) { .bar__inner--dense .brand .dim { display: none; } }
@media (max-width: 700px) {
  .hide-sm { display: none !important; }
  .bar__inner--dense .brand span { display: none; }
  .brand span { font-size: 13.5px; letter-spacing: .09em; }
  .brand img { width: 26px; height: 26px; }
  .bar__links a { padding: 6px 9px; font-size: 12.5px; }
  .sitelinks {
    gap: 0;
    overflow-x: auto; overscroll-behavior-x: contain;
    -webkit-overflow-scrolling: touch;
    scrollbar-width: none;
  }
  .sitelinks::-webkit-scrollbar { display: none; }
  /* The desktop app link downloads a Windows installer. A phone cannot run
     it, so it is the one link this row drops. See _sitelinks.html. */
  .sitelinks .sitelinks__app { display: none; }
  .sitelinks a { padding: 6px 8px; font-size: 12.5px; }
  .sitelinks__cta { padding: 6px 10px !important; margin-left: 3px; }
  /* the brand stops hogging the row once the links need it. `margin-right`
     was 8px on top of the 16px flex gap -- 24px of empty bar between the
     wolf and the first link, on the one screen with no room to spare. */
  .brand { margin-right: 0; }
  .bar__inner { gap: 10px; }
  .bar__inner .sitelinks { margin-left: auto; }
}
@media (max-width: 520px) {
  /* Decoration, and the bar is tight from here down on every phone. */
  .ext-arrow { display: none; }
  /* 6px vertical, not 5: at 11px type the link was 27px tall, and the
     targets in this row have to stay at least 28px. Height is free here --
     the bar is 54px -- so the row gets no wider for it. */
  .sitelinks a { padding: 6px 5px; font-size: 11px; }
  .sitelinks__cta { padding: 6px 7px !important; margin-left: 2px; }
  .topbar h1 { font-size: .95rem; }
  /* The signed-in topbar carries more than the storefront bar does -- the
     hamburger, the heading, and a CTA reading "My companions" rather than
     "Sign in". Measured at 420px incompressible, which overflows every phone
     in use, so the chrome around the links gives up its room instead. */
  .topbar { gap: 4px; padding: 0 10px; }
  .topbar .sitelinks { gap: 1px; }
}
@media (max-width: 400px) {
  .sitelinks a { padding: 6px 4px; font-size: 10.5px; }
  .sitelinks__cta { padding: 6px 6px !important; }
  /* The wordmark yields to the links. At 375px the two together overflowed
     the bar and pushed the CTA off the end -- the wolf still goes home, and
     a link you have to scroll sideways to find is not "always available". */
  .brand span { display: none; }
  .sidebar-brand span { display: none; }
}
@media (max-width: 460px) {
  .bar__inner { padding-inline: 15px; }
  .hero { padding-inline: 15px; }
  /* Signed out too, by now: at 412px the full wordmark left the row 3px
     short. "FENRIS" beside the wolf still reads as the mark. */
  .brand .dim { display: none; }
}

/* ═══════════════════════════════════════════════════════════════
   Setting rows, wizard, progressive disclosure
   A dense grid of inputs is unreadable. Every setting gets a name,
   a plain-English sentence saying what it does, and enough space
   between it and the next one to tell them apart.
   ─────────────────────────────────────────────────────────────── */

.setting {
  display: grid;
  grid-template-columns: minmax(0, 1fr) minmax(210px, 300px);
  gap: 14px 26px; align-items: start;
  padding: 16px 0;
  border-bottom: 1px solid var(--line-soft);
}
.setting:last-child { border-bottom: 0; padding-bottom: 4px; }
.setting:first-child { padding-top: 4px; }
.setting__label { min-width: 0; }
.setting__label > b {
  display: block; font-family: var(--font-d); font-weight: 600;
  font-size: .96rem; letter-spacing: -.005em; margin-bottom: 3px;
}
.setting__label p {
  font-size: .84rem; color: var(--muted); line-height: 1.5; max-width: 58ch;
}
.setting__label .hint {
  display: block; margin-top: 6px; font-size: .78rem; color: var(--faint);
}
.setting__label .warn-note {
  display: block; margin-top: 6px; font-size: .78rem; color: var(--gold-soft);
}
.setting__control { min-width: 0; }
.setting__control .field { margin-bottom: 0; }
.setting__control .field + .field { margin-top: 10px; }
.setting.stacked { grid-template-columns: 1fr; gap: 10px; }
@media (max-width: 760px) {
  .setting { grid-template-columns: 1fr; gap: 10px; padding: 14px 0; }
}
/* The same collapse when the GROUP is narrow rather than the window. Inside
   the two-column character tabs a settings card is ~500px wide, and the
   label/control split left the label ~120px -- paragraphs fifteen lines tall
   beside one checkbox. A container query reads the card, not the screen. */
.settings-group { container-type: inline-size; }
@container (max-width: 560px) {
  .setting { grid-template-columns: 1fr; gap: 10px; }
}

/* a group of settings with a heading that explains the whole group */
/* A group is a CARD, not a run of text with a bold line above it. Sections
   used to be separated by nothing but 22px of margin, so on a long page they
   read as one continuous column and you could not tell where "You" ended and
   "Relationship" began. The surface, border and ruled header do that work. */
.settings-group {
  margin-bottom: 22px;
  background: var(--ink-2);
  border: 1px solid var(--line);
  border-radius: var(--r);
  padding: 18px 20px 20px;
}
.settings-group > header {
  margin: -18px -20px 16px;             /* bleed to the card edges */
  padding: 13px 20px;
  border-bottom: 1px solid var(--line);
  background: var(--ink);               /* fallback for no color-mix */
  background: color-mix(in oklab, var(--ink-2) 82%, var(--ink) 18%);
  border-radius: var(--r) var(--r) 0 0;
  display: flex; align-items: baseline; gap: 12px; flex-wrap: wrap;
}
.settings-group > header h2,
.settings-group > header h3 {
  font-size: 1.12rem; letter-spacing: -.015em;
  display: flex; align-items: center; gap: 9px;
}
.settings-group > header p {
  color: var(--muted); font-size: .875rem; margin-top: 5px; max-width: 70ch;
  flex-basis: 100%;
}
.settings-group > .card { margin-top: 13px; }
/* A card inside a group card would be a box in a box: flatten the inner one
   to a ruled block instead. */
.settings-group > .settings-body > .card,
.settings-group > .card {
  background: transparent; border: 0; padding: 0;
}

/* ── desktop layout for the character tabs ──────────────────────────────
   The tabs are authored as one honest vertical column, which is exactly right
   on a phone and leaves two thirds of a desktop screen empty. Rather than
   rewrite twelve templates into a desktop shape, the columns are added here
   and `.tabwrap` opts a page in.

   Two levels, because the tabs nest differently: some put cards straight into
   the save form, some wrap them in a `.stack`, some sit outside the form
   entirely. Gridding the wrapper AND the form catches all three without
   caring which one a given tab used.

   Nothing here changes on mobile -- below the breakpoint it is the same single
   column it always was, with the same markup and the same behaviour. */
@media (min-width: 1080px) {
  .tabwrap,
  .tabwrap > form {
    display: grid;
    grid-template-columns: repeat(2, minmax(0, 1fr));
    gap: 0 22px;
    align-items: start;
  }
  /* A form is one item of the outer grid and a grid of its own inside. */
  .tabwrap > form { grid-column: 1 / -1; }
  .tabwrap > *,
  .tabwrap > form > * { min-width: 0; }

  /* Full width for anything that genuinely needs the room, or that would be
     absurd in half of it: wide tables, the long prose editors, a row of
     buttons, and any explicit opt-out. */
  .tabwrap > .wide,          .tabwrap > form > .wide,
  .tabwrap > .card-foot,     .tabwrap > form > .card-foot,
  .tabwrap > .notice,        .tabwrap > form > .notice,
  .tabwrap > button,         .tabwrap > form > button,
  .tabwrap > .row,           .tabwrap > form > .row,
  .tabwrap > details,        .tabwrap > form > details,
  .tabwrap > .table-wrap,    .tabwrap > form > .table-wrap,
  /* `.grid` is already a responsive multi-column container -- halving it just
     squeezes its own columns. Let it have the width and do its own job. */
  .tabwrap > .grid,          .tabwrap > form > .grid,
  /* Same reason: .dash lays out its own columns. */
  .tabwrap > .dash,          .tabwrap > form > .dash,
  .tabwrap > .stat-row,      .tabwrap > form > .stat-row,
  .tabwrap > .tabs,          .tabwrap > form > .tabs {
    grid-column: 1 / -1;
  }
  .tabwrap > *:has(table),        .tabwrap > form > *:has(table),
  .tabwrap > *:has(textarea.tall), .tabwrap > form > *:has(textarea.tall),
  .tabwrap > *:has(.chart),        .tabwrap > form > *:has(.chart) {
    grid-column: 1 / -1;
  }
}

/* The console is a dense tool, not an article: give it the screen. The public
   site keeps the narrower measure, which is correct for reading. */
@media (min-width: 1400px) {
  .page { max-width: 1560px; }
}
@media (min-width: 1800px) {
  .page { max-width: 1760px; }
  .tabwrap, .tabwrap > form { grid-template-columns: repeat(3, minmax(0, 1fr)); }
  .tabwrap > form { grid-column: 1 / -1; }
}

/* ── desktop dashboard ──────────────────────────────────────────────────
   Wide screens get columns. The page is authored as one honest vertical
   stack -- which is exactly right on a phone -- so the multi-column layout
   is added here rather than baked into the markup, and `.dash` opts a page
   in. Groups are never split across columns. */
@media (min-width: 1100px) {
  .dash {
    display: grid;
    grid-template-columns: repeat(2, minmax(0, 1fr));
    gap: 0 22px;
    align-items: start;
  }
  /* Sections that hold a wide table, a chart or a long editor need the full
     width; everything else pairs up. */
  .dash > .settings-group.wide,
  .dash > .settings-group:has(table),
  .dash > .settings-group:has(textarea.tall),
  .dash > .settings-group:has(.chart) { grid-column: 1 / -1; }
}
@media (min-width: 1600px) {
  .dash { grid-template-columns: repeat(3, minmax(0, 1fr)); }
  .dash > .settings-group.wide,
  .dash > .settings-group:has(table),
  .dash > .settings-group:has(textarea.tall),
  .dash > .settings-group:has(.chart) { grid-column: 1 / -1; }
}

/* progressive disclosure: the 90% case is visible, the rest is one click */
details.advanced {
  border: 1px solid var(--line); border-radius: var(--r);
  background: var(--ink-2); margin-top: 13px;
}
details.advanced > summary {
  cursor: pointer; padding: 12px 16px; list-style: none;
  display: flex; align-items: center; gap: 9px;
  font-family: var(--font-d); font-weight: 600; font-size: .9rem;
  color: var(--muted); transition: color .2s var(--ease);
}
details.advanced > summary::-webkit-details-marker { display: none; }
details.advanced > summary::before {
  content: "▸"; color: var(--gold); font-size: .8rem;
  transition: transform .2s var(--ease); display: inline-block;
}
details.advanced[open] > summary::before { transform: rotate(90deg); }
details.advanced > summary:hover { color: var(--text); }
details.advanced > summary small {
  margin-left: auto; font-weight: 400; font-size: .78rem; color: var(--faint);
}
details.advanced > .inner { padding: 4px 16px 16px; border-top: 1px solid var(--line); }

/* ---- wizard ------------------------------------------------------------ */
.wizard { max-width: 720px; margin: 0 auto; }
.wizard__steps {
  display: flex; align-items: center; gap: 6px;
  margin-bottom: 26px; justify-content: center; flex-wrap: wrap;
}
.wizard__steps i {
  width: 26px; height: 26px; border-radius: 50%;
  display: grid; place-items: center; font-style: normal;
  font-family: var(--font-d); font-size: .76rem; font-weight: 700;
  background: var(--panel-2); color: var(--faint);
  border: 1px solid var(--line);
}
.wizard__steps i.done { background: var(--gold-dim); color: var(--gold-soft); border-color: var(--gold-line); }
.wizard__steps i.now {
  background: linear-gradient(180deg, var(--gold-soft), var(--gold));
  color: #17130a; border-color: var(--gold);
  box-shadow: 0 6px 18px -8px rgba(217,164,65,.8);
}
.wizard__steps span { width: 22px; height: 1px; background: var(--line); }
.wizard__head { text-align: center; margin-bottom: 26px; }
.wizard__head h2 { font-size: clamp(1.4rem, 3.4vw, 1.95rem); letter-spacing: -.025em; }
.wizard__head p { color: var(--muted); margin-top: 8px; font-size: .95rem; max-width: 52ch; margin-inline: auto; }
.wizard__nav { display: flex; gap: 10px; align-items: center; margin-top: 22px; }
.wizard__nav .spacer { flex: 1; }

/* big friendly picker cards, for choices a wizard asks about */
.pick { display: grid; gap: 10px; }
.pick.cols-2 { grid-template-columns: repeat(auto-fit, minmax(220px, 1fr)); }
.pick label {
  position: relative; display: block; cursor: pointer;
  border: 1px solid var(--line); border-radius: var(--r);
  background: var(--panel); padding: 14px 16px;
  transition: border-color .2s var(--ease), background .2s var(--ease), transform .12s var(--ease);
}
.pick label:hover { border-color: var(--line-2); transform: translateY(-1px); }
.pick input { position: absolute; opacity: 0; pointer-events: none; }
.pick input:checked + .pick__body {
  color: var(--text);
}
.pick label:has(input:checked) {
  border-color: var(--gold); background: linear-gradient(180deg, var(--gold-dim), transparent 70%), var(--panel);
}
.pick label:has(input:checked)::after {
  content: "✓"; position: absolute; top: 12px; right: 14px;
  color: var(--gold); font-weight: 700;
}
.pick__body b {
  display: block; font-family: var(--font-d); font-weight: 600;
  font-size: .97rem; margin-bottom: 4px;
}
.pick__body p { font-size: .83rem; color: var(--muted); line-height: 1.45; }
.pick__body .price {
  display: block; margin-top: 7px; font-family: var(--font-d);
  font-weight: 600; color: var(--gold-soft);
}

/* a plain-English status line: green tick / amber warning / grey neutral */
.checkline {
  display: flex; align-items: flex-start; gap: 9px;
  padding: 9px 0; font-size: .875rem; line-height: 1.5;
  border-bottom: 1px solid var(--line-soft);
}
.checkline:last-child { border-bottom: 0; }
.checkline .mark { flex: 0 0 18px; font-weight: 700; text-align: center; }
.checkline.ok .mark { color: var(--ok); }
.checkline.warn .mark { color: var(--warn); }
.checkline.no .mark { color: var(--danger); }
.checkline.pending .mark { color: var(--faint); }
.checkline span small { display: block; color: var(--muted); font-size: .8rem; margin-top: 2px; }

/* compact storefront hero: a blurb, not a billboard */
.hero.compact { padding: clamp(26px, 4vw, 44px) 20px clamp(20px, 3vw, 30px); }
.hero.compact h1 {
  font-size: clamp(1.5rem, 3.6vw, 2.25rem); max-width: none; letter-spacing: .01em;
}
.hero.compact p { margin-top: 9px; font-size: clamp(.9rem, 1.6vw, 1rem); max-width: 64ch; }
.hero.compact .hero__mark { width: clamp(40px, 5vw, 52px); margin-bottom: 11px; }
.hero__facts {
  display: flex; gap: 18px; flex-wrap: wrap; margin-top: 16px;
  font-size: .83rem; color: var(--muted);
}
.hero__facts b { color: var(--text); font-weight: 600; }

/* ---- bulk edit --------------------------------------------------------- */
/* A row is a tick plus the value it would write. Dimmed until ticked, so
   what will actually change is obvious at a glance rather than something you
   work out by reading every input. */
.bulk-row {
  display: grid;
  grid-template-columns: minmax(0, 1fr) minmax(0, 18rem);
  gap: .6rem 1rem;
  align-items: center;
  padding: .5rem 0;
  border-bottom: 1px solid var(--hairline, rgba(255,255,255,.06));
}
.bulk-row:last-child { border-bottom: 0; }
.bulk-row .bulk-apply { margin: 0; opacity: .65; transition: opacity .12s; }
.bulk-row.is-on .bulk-apply { opacity: 1; font-weight: 600; }
.bulk-row .bulk-value { opacity: .4; transition: opacity .12s; }
.bulk-row.is-on .bulk-value { opacity: 1; }
.bulk-row .bulk-value > * { width: 100%; }
.bulk-row .hint {
  grid-column: 1 / -1;
  margin-top: -.15rem;
  color: var(--muted);
  font-size: .755rem;
  line-height: 1.45;
  max-width: 68ch;
}

@media (max-width: 640px) {
  .bulk-row { grid-template-columns: 1fr; }
}

/* Hints inside a plain .field (defaults page, and anywhere else that wants
   one). .setting__label .hint already covers the settings page's own layout;
   this is the same treatment for the simpler field wrapper. */
.field > .hint {
  display: block;
  margin-top: .25rem;
  color: var(--muted);
  font-size: .755rem;
  line-height: 1.45;
  max-width: 68ch;
}

/* ---- help pages -------------------------------------------------------- */
.settings-body ul.tight, .settings-body ol.tight {
  margin: .3rem 0 .3rem 1.15rem;
  padding: 0;
}
.settings-body ul.tight > li, .settings-body ol.tight > li {
  margin-bottom: .55rem;
  line-height: 1.55;
}
.settings-body ul.tight > li:last-child,
.settings-body ol.tight > li:last-child { margin-bottom: 0; }
.settings-body pre.code {
  background: rgba(0, 0, 0, .28);
  border: 1px solid var(--hairline, rgba(255, 255, 255, .07));
  border-radius: 6px;
  padding: .6rem .75rem;
  overflow-x: auto;
  font-size: .78rem;
  margin: .5rem 0;
}

/* ---- logs -------------------------------------------------------------- */
.logbox {
  background: rgba(0, 0, 0, .34);
  border: 1px solid var(--hairline, rgba(255, 255, 255, .07));
  border-radius: 6px;
  padding: .7rem .85rem;
  overflow-x: auto;
  max-height: 32rem;
  overflow-y: auto;
  font-size: .74rem;
  line-height: 1.5;
  white-space: pre;
  font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
}
.logbox.crash {
  border-color: rgba(220, 80, 80, .45);
  background: rgba(120, 20, 20, .16);
  color: #ffc9c9;
}

/* ---- charts ------------------------------------------------------------ */
svg.chart { width: 100%; height: 160px; display: block; color: var(--muted); }
svg.chart.accent { color: var(--accent, #e0803c); }
.chart-axis {
  display: flex; justify-content: space-between;
  font-size: .7rem; color: var(--muted); margin-top: .25rem;
}
.stat-row { display: flex; gap: 1.1rem; flex-wrap: wrap; }
.stat-row .stat {
  background: rgba(255, 255, 255, .03);
  border: 1px solid var(--hairline, rgba(255, 255, 255, .07));
  border-radius: 8px; padding: .7rem 1rem; min-width: 8rem;
}
.stat-row .stat b { display: block; font-size: 1.35rem; line-height: 1.2; }
.stat-row .stat span { font-size: .72rem; color: var(--muted); }

/* ---- media library ----------------------------------------------------- */
.media-grid {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(160px, 1fr));
  gap: .8rem;
}
.media-item {
  margin: 0;
  border: 1px solid var(--hairline, rgba(255, 255, 255, .07));
  border-radius: 8px; overflow: hidden;
  background: rgba(0, 0, 0, .2);
}
.media-item img {
  display: block; width: 100%; height: 150px;
  object-fit: cover; background: rgba(0, 0, 0, .35);
}
/* The picker wraps each picture in a button so it is a Tab stop. */
.media-item .media-pick {
  display: block; width: 100%; padding: 0; margin: 0; border: 0;
  background: none; cursor: pointer;
}
.media-item figcaption {
  display: flex; align-items: center; gap: .35rem;
  padding: .4rem .5rem; flex-wrap: wrap;
}
.media-item figcaption .tiny { margin-left: auto; }

/* five stat cards now that month-to-date sits beside the 24h figure */
.grid.cols-5 { display: grid; grid-template-columns: repeat(5, minmax(0, 1fr)); gap: 12px; }
@media (max-width: 1100px) { .grid.cols-5 { grid-template-columns: repeat(3, minmax(0, 1fr)); } }
@media (max-width: 680px)  { .grid.cols-5 { grid-template-columns: repeat(2, minmax(0, 1fr)); } }

/* ---- pop-out editor ---------------------------------------------------- */
.expand-btn { margin-top: .35rem; }
body.modal-open { overflow: hidden; }
.editor-overlay {
  position: fixed; inset: 0; z-index: 200;
  background: rgba(0, 0, 0, .62);
  display: flex; align-items: center; justify-content: center;
  padding: 2.5vh 2vw;
}
.editor-panel {
  background: var(--panel, #12151c);
  border: 1px solid var(--hairline, rgba(255, 255, 255, .1));
  border-radius: 10px;
  width: min(1100px, 100%); height: 95vh;
  display: flex; flex-direction: column;
  box-shadow: 0 24px 60px rgba(0, 0, 0, .5);
}
.editor-panel > header {
  padding: .85rem 1.1rem; font-weight: 600;
  border-bottom: 1px solid var(--hairline, rgba(255, 255, 255, .08));
}
.editor-panel > textarea {
  flex: 1; width: 100%; resize: none; border: 0; outline: 0;
  background: transparent; color: inherit;
  padding: 1rem 1.1rem;
  font: .875rem/1.65 ui-monospace, SFMono-Regular, Menlo, monospace;
}
.editor-panel > footer {
  display: flex; align-items: center; gap: .6rem;
  padding: .7rem 1.1rem;
  border-top: 1px solid var(--hairline, rgba(255, 255, 255, .08));
}
.editor-panel > footer .tiny { margin-right: auto; }
.editor-panel > footer .over { color: var(--danger, #e06c6c); font-weight: 600; }

/* ---- view-as-renter bar ------------------------------------------------ */
/* Loud on purpose. Forgetting the flag is set and then wondering why the
   console 404s is exactly the confusion this is meant to prevent. */
.renter-view-bar {
  position: sticky; top: 0; z-index: 150;
  display: flex; align-items: center; gap: .7rem; flex-wrap: wrap;
  padding: .5rem .9rem;
  background: repeating-linear-gradient(45deg,
    rgba(224,128,60,.16) 0 12px, rgba(224,128,60,.09) 12px 24px);
  border-bottom: 1px solid rgba(224,128,60,.5);
  font-size: .8rem;
}
.renter-view-bar span { color: var(--muted); }
.renter-view-bar a {
  margin-left: auto; font-weight: 600;
  white-space: nowrap;
}
body.as-renter { --renter-testing: 1; }

/* ---- chart axis + hover tooltip ---------------------------------------- */
.chart-wrap { position: relative; padding-left: 3.6rem; }
.chart-wrap svg.chart { cursor: crosshair; }
.chart-yaxis {
  position: absolute; left: 0; top: 0; bottom: 0; width: 3.3rem;
  display: flex; flex-direction: column; justify-content: space-between;
  align-items: flex-end;
  font-size: .66rem; color: var(--muted);
  pointer-events: none;
  padding: 2px 0;
}
.chart-tip {
  position: absolute; bottom: calc(100% - 6px);
  background: var(--panel, #12151c);
  border: 1px solid var(--hairline, rgba(255,255,255,.14));
  border-radius: 6px;
  padding: .35rem .55rem;
  font-size: .72rem; line-height: 1.35;
  white-space: nowrap; pointer-events: none;
  box-shadow: 0 6px 18px rgba(0,0,0,.45);
  z-index: 5;
}
.chart-tip b { display: block; font-size: .82rem; }
.chart-tip span { display: block; color: var(--muted); }

/* ---- hover explainer on a stat ----------------------------------------
   A panel that opens on the tile itself rather than on a little "?" inside
   it. Two reasons: the tile is already a link, so it is already focusable
   and :focus-visible gives keyboard users the same panel for free -- and a
   button inside an anchor is invalid markup that browsers unnest in their
   own ways. No script anywhere near it; script-src is 'self' and a tooltip
   is not worth a bundle. */
.explain { position: relative; }
.explain__pop {
  position: absolute; z-index: 40;
  top: calc(100% + 8px); left: 0; min-width: 17rem; max-width: 22rem;
  background: var(--panel, #12151c);
  border: 1px solid var(--line);
  border-radius: var(--r, 10px);
  padding: .7rem .8rem;
  box-shadow: 0 18px 44px -16px rgba(0,0,0,.75);
  opacity: 0; visibility: hidden; transform: translateY(-4px);
  transition: opacity .16s var(--ease, ease), transform .16s var(--ease, ease),
              visibility .16s;
  pointer-events: none;         /* never eats the click on the tile */
  text-align: left; cursor: default;
}
.explain:hover .explain__pop,
.explain:focus-visible .explain__pop {
  opacity: 1; visibility: visible; transform: translateY(0);
}
.explain__pop b {
  display: block; font-size: .82rem; margin-bottom: .25rem;
  color: var(--text);
}
.explain__pop p {
  margin: 0 0 .55rem; font-size: .72rem; line-height: 1.45;
  color: var(--muted);
}
.explain__pop p.warn { color: var(--warn); margin: .5rem 0 0; }
.explain__pop dl { margin: 0; font-size: .74rem; }
.explain__pop dl > div {
  display: flex; justify-content: space-between; gap: 1rem;
  padding: 3px 0;
}
.explain__pop dt { color: var(--muted); }
.explain__pop dd { margin: 0; font-variant-numeric: tabular-nums; }
.explain__pop dd.out { color: var(--danger); }
.explain__pop .tot {
  border-top: 1px solid var(--line); margin-top: 3px; padding-top: 5px;
  font-weight: 600;
}
.explain__pop .tot dt { color: var(--text); }
/* The last tile in a row would push its panel off the right edge. */
.grid > .explain:last-child .explain__pop { left: auto; right: 0; }
@media (max-width: 640px) {
  .explain__pop { left: 0; right: 0; min-width: 0; max-width: none; }
}
/* A touch device has no hover, and a panel that needs one is a panel nobody
   on a phone ever sees. Shown inline underneath instead. */
@media (hover: none) {
  .explain__pop {
    position: static; opacity: 1; visibility: visible; transform: none;
    margin-top: .6rem; box-shadow: none; max-width: none;
  }
}

/* Small round portrait for list rows. Enough to tell two instances of the
   same character apart at a glance without loading a full card. */
.avatar-xs {
  width: 28px; height: 28px; border-radius: 50%;
  object-fit: cover; flex: 0 0 auto;
  border: 1px solid var(--line); background: var(--panel-2);
}
.avatar-lg {
  width: 56px; height: 56px; border-radius: 50%;
  object-fit: cover; flex: 0 0 auto;
  border: 1px solid var(--line); background: var(--panel-2);
}
.badge.tiny { font-size: .66rem; padding: 1px 5px; }

/* Channel checklist. Scrolls rather than growing without limit -- a busy guild
   has fifty channels and this sits inside a two-column card. */
.chan-pick {
  max-height: 260px; overflow-y: auto;
  border: 1px solid var(--line); border-radius: var(--r-sm);
  background: var(--panel-2); padding: 6px 8px;
}
.chan-cat + .chan-cat { margin-top: 8px; }
.chan-cat__name {
  font-size: .68rem; letter-spacing: .08em; text-transform: uppercase;
  color: var(--faint); font-weight: 600; padding: 3px 2px;
  position: sticky; top: -6px; background: var(--panel-2);
}
.chan-row {
  display: flex; align-items: center; gap: 7px;
  padding: 3px 2px; border-radius: 4px; cursor: pointer;
  font-size: .86rem;
}
.chan-row:hover { background: var(--panel); }
.chan-row input { flex: 0 0 auto; margin: 0; }

/* Read-only text preview. Wraps rather than scrolling sideways -- a system
   prompt with a horizontal scrollbar is unreadable. */
.preview-pane {
  white-space: pre-wrap; word-break: break-word;
  font-size: .78rem; line-height: 1.5;
  max-height: 420px; overflow-y: auto;
  padding: 10px 12px; margin: 0;
  border: 1px solid var(--line); border-radius: var(--r-sm);
  background: var(--panel-2); color: var(--muted);
  font-family: var(--font-m, ui-monospace, monospace);
}

/* Memory bars. A share-of-the-box bar is easier to read at a glance than a
   column of megabytes, so the number stays but the bar carries the meaning. */
.membar { margin-bottom: 11px; }
.membar:last-child { margin-bottom: 0; }
.membar__head {
  display: flex; justify-content: space-between; align-items: baseline;
  gap: 10px; font-size: .86rem; margin-bottom: 4px;
}
.membar__track {
  height: 8px; border-radius: 999px; overflow: hidden;
  background: var(--panel-2); border: 1px solid var(--line);
}
.membar__fill {
  height: 100%; border-radius: 999px; min-width: 2px;
  background: var(--faint);
  transition: width .3s var(--ease);
}
.membar__fill.is-ours { background: var(--gold, #c8a24a); }
.membar__fill.is-platform { background: var(--accent, #5bc8f5); }

/* In-page section nav. Sticks under the topbar so a long instance page stays
   navigable instead of being a single scroll. */
.secnav {
  position: sticky; top: 0; z-index: 5;
  display: flex; flex-wrap: wrap; align-items: center; gap: 2px 10px;
  padding: 9px 12px; margin: 0 0 18px;
  border: 1px solid var(--line); border-radius: var(--r);
  background: color-mix(in srgb, var(--panel) 92%, transparent);
  backdrop-filter: blur(6px);
  font-size: .82rem;
}
.secnav__group {
  font-size: .64rem; letter-spacing: .09em; text-transform: uppercase;
  color: var(--faint); font-weight: 600;
  padding-left: 6px; border-left: 1px solid var(--line);
}
.secnav__group:first-child { padding-left: 0; border-left: 0; }
.secnav a { color: var(--muted); text-decoration: none; padding: 2px 0; }
.secnav a:hover { color: var(--text); }

/* Anchored sections need room under the sticky bar when jumped to. */
.settings-group[id] { scroll-margin-top: 64px; }

/* A save that is retrying should look like it is working, not like it was
   ignored -- otherwise people click it again and race themselves. */
.btn.is-busy { position: relative; padding-left: 30px; opacity: .85; cursor: progress; }
.btn.is-busy::before {
  content: ""; position: absolute; left: 11px; top: 50%;
  width: 11px; height: 11px; margin-top: -6px;
  border: 2px solid currentColor; border-right-color: transparent;
  border-radius: 50%; animation: btnspin .6s linear infinite;
}
@keyframes btnspin { to { transform: rotate(360deg); } }

/* A summary row that opens. Signals clickability, because a table row that
   does something invisible is a row nobody clicks. */
.grouprow { cursor: pointer; }
.grouprow:hover { background: var(--panel-2); }
.grouprow.is-open { background: var(--panel-2); }

/* A tier that cannot be bought right now. Dimmed rather than hidden: knowing
   she exists at this tier, and what it costs, is worth showing even when the
   answer today is no. */
.tier-card.is-unavailable { opacity: .62; }
.tier-card.is-unavailable .price { color: var(--muted); }
.tier-card.is-unavailable:hover { opacity: .78; }

/* ══════════════════════════════════════════════════ how it works ════════
   Scoped to the explanation page. Everything here exists because the page
   has to SHOW three things prose is bad at: what an unprompted message
   actually looks like, where a piece of memory lives, and who owns which
   part of her. */

.how-lede {
  font-size: clamp(1.02rem, 2vw, 1.16rem); line-height: 1.6;
  color: var(--text); max-width: 58ch;
}
.how-lede + .how-lede { margin-top: .8rem; }
/* The quieter follow-up line. Was set with an inline style on every single
   paragraph that used it, which is both repetitive and impossible to tune. */
.how-lede.sub { color: var(--muted); font-size: .95rem; line-height: 1.55; }

/* A short pull-out. One idea, given room to land, instead of a third
   paragraph nobody reads. */
.how-note {
  border-left: 2px solid var(--gold-line); padding: 2px 0 2px 15px;
  margin-top: 1.1rem; color: var(--muted); font-size: .92rem;
  line-height: 1.55; max-width: 56ch;
}
.how-note b { color: #dfe5ee; font-weight: 600; }

/* A numbered section header. The number is the spine of the page -- it tells
   you how far through you are without a progress bar. */
.how-step { display: flex; align-items: baseline; gap: 14px; margin-bottom: 18px; }
.how-step__n {
  font-family: var(--font-d); font-size: .74rem; font-weight: 700;
  letter-spacing: .16em; color: var(--gold);
  border: 1px solid var(--gold-line); background: var(--gold-dim);
  border-radius: 999px; padding: 5px 11px; flex: none;
  line-height: 1; position: relative; top: -2px;
}
.how-step h2 { font-size: clamp(1.25rem, 2.6vw, 1.62rem); letter-spacing: -.02em; }
.how-step p { color: var(--muted); margin-top: 7px; max-width: 62ch; font-size: .95rem; }

/* Two columns where the left explains and the right shows. Collapses to one
   on a phone with the picture UNDER its explanation, which is the order it
   makes sense in when you cannot see both at once. */
.how-split { display: grid; gap: clamp(20px, 3vw, 34px); align-items: start; }
@media (min-width: 900px) {
  .how-split { grid-template-columns: minmax(0, 1fr) minmax(0, 1fr); }
  .how-split.wide-right { grid-template-columns: minmax(0, 5fr) minmax(0, 7fr); }
  .how-split.wide-left  { grid-template-columns: minmax(0, 7fr) minmax(0, 5fr); }
}

.how-figure { margin: 0; }
.how-figure figcaption {
  color: var(--faint); font-size: .78rem; margin-top: 9px; line-height: 1.5;
}
.how-figure svg { display: block; width: 100%; height: auto; }

/* ---- a face, not a mock-up ------------------------------------------
   This was a hand-built replica of a Discord conversation. It looked
   convincing, which was the problem: a fabricated screenshot on a page whose
   whole argument is "she is not a trick" undercuts itself. The art is real,
   so the art goes here. */
.how-figure.portrait img {
  width: 100%; height: auto; border-radius: var(--r);
  border: 1px solid var(--line);
  /* The illustrations are tall; without a ceiling one portrait sets the
     height of an entire section. */
  max-height: 520px; object-fit: cover; object-position: center 12%;
}

/* ---- who-owns-what ----------------------------------------------------- */
.owns { width: 100%; border-collapse: collapse; font-size: .875rem; }
.owns th, .owns td {
  text-align: left; padding: 10px 12px;
  border-bottom: 1px solid var(--line-soft); vertical-align: top;
}
.owns thead th {
  font-family: var(--font-d); font-size: .7rem; letter-spacing: .11em;
  text-transform: uppercase; color: var(--faint); font-weight: 700;
  border-bottom-color: var(--line);
}
.owns tbody tr:last-child td { border-bottom: 0; }
.owns td:first-child { color: #dfe5ee; font-weight: 600; white-space: nowrap; }
.owns .sub { display: block; color: var(--faint); font-size: .78rem;
             font-weight: 400; margin-top: 3px; white-space: normal; }
.tag {
  display: inline-block; font-size: .68rem; font-weight: 700;
  letter-spacing: .06em; text-transform: uppercase; font-family: var(--font-d);
  border-radius: 999px; padding: 3px 8px; white-space: nowrap;
  border: 1px solid var(--line-2); color: var(--muted);
}
.tag.yours { border-color: var(--gold-line); color: var(--gold-soft);
             background: var(--gold-dim); }
.tag.hers  { border-color: #2c3a4e; color: #8fb3d9; background: rgba(80,140,210,.1); }
.tag.locked { border-color: #4a3550; color: #c79ad3; background: rgba(170,110,190,.1); }
/* A tag that is a LINK is a target, and a 22px pill is too small a one on a
   phone. Only the anchors grow; a plain label tag keeps its size. */
a.tag { display: inline-flex; align-items: center; min-height: 28px; }
.tag-row { align-items: center; }

/* On a phone this stops being a table.
   Three columns in 375px gave the description a 103px ribbon, made the
   first row 309px tall, and STILL overflowed the screen by 77px -- so the
   answer to "what is mine" was a horizontal scrollbar. Each row becomes a
   card instead: the name and its tag on one line, the explanation under
   both. Nothing is hidden and nothing scrolls sideways. */
@media (max-width: 720px) {
  .owns, .owns tbody, .owns tr, .owns td { display: block; }
  /* Off-screen rather than display:none -- the header still names the
     columns for a screen reader reading the rows. */
  .owns thead {
    position: absolute; width: 1px; height: 1px;
    overflow: hidden; clip: rect(0 0 0 0); white-space: nowrap;
  }
  .owns tr {
    display: grid; grid-template-columns: minmax(0, 1fr) auto;
    gap: 4px 10px; padding: 13px 15px;
    border-bottom: 1px solid var(--line-soft);
  }
  .owns tbody tr:last-child { border-bottom: 0; }
  .owns td { border: 0; padding: 0; }
  .owns td:first-child {
    grid-column: 1; grid-row: 1; align-self: center;
    white-space: normal;              /* or long names force a wide column */
    font-size: .95rem;
  }
  .owns td:last-child {
    grid-column: 2; grid-row: 1; align-self: center; justify-self: end;
  }
  .owns td:nth-child(2) {
    grid-column: 1 / -1; grid-row: 2; color: var(--muted); line-height: 1.5;
  }
  .owns .sub { margin-top: 5px; }
}

/* ---- the two tiers ----------------------------------------------------- */
.how-tiers { display: grid; gap: 18px; }
@media (min-width: 820px) { .how-tiers { grid-template-columns: 1fr 1fr; } }
.how-tier { display: flex; flex-direction: column; }
.how-tier h2, .how-tier h3 { font-size: 1.06rem; display: flex; align-items: center; gap: 9px; }
.how-tier .price {
  font-family: var(--font-d); font-weight: 700; color: var(--gold-soft);
  font-size: .95rem;
}
.how-tier ul { list-style: none; margin: 13px 0 0; display: grid; gap: 9px; }
/* NOT flex. In a flex li the <b> lead-in and the sentence after it each
   become their own flex item and sit side by side -- so "Her memory is yours
   alone." wrapped into a narrow column with its explanation stranded next to
   it. Absolute bullet keeps the text one normal paragraph. */
.how-tier li {
  position: relative; padding-left: 15px;
  font-size: .885rem; color: var(--muted); line-height: 1.5;
}
.how-tier li b { display: block; color: #dfe5ee; font-weight: 600; }
.how-tier li::before {
  content: ""; position: absolute; left: 0; top: .56em;
  width: 5px; height: 5px; border-radius: 50%;
  background: var(--gold); opacity: .8;
}
.how-tier li.no::before { background: var(--line-2); }

/* ---- closing call to action -------------------------------------------- */
.how-cta {
  border: 1px solid var(--gold-line); border-radius: var(--r);
  background:
    radial-gradient(120% 160% at 50% -40%, rgba(217,164,65,.14), transparent 62%),
    var(--panel);
  padding: clamp(24px, 4vw, 40px); text-align: center;
}
.how-cta h2 { font-size: clamp(1.3rem, 3vw, 1.75rem); }
.how-cta p { color: var(--muted); margin: 10px auto 20px; max-width: 52ch; }
.how-cta .row { justify-content: center; }
/* Same block, dialled down. The home page carries this offer under the cast
   where it must not outweigh the companions themselves; The Den gets the
   full-size one. Identical treatment, smaller everything. */
.how-cta--slim { padding: clamp(16px, 2.4vw, 26px); }
.how-cta--slim h2, .how-cta--slim h3 { font-size: clamp(1.02rem, 2vw, 1.2rem); }
.how-cta--slim p { margin: 7px auto 14px; font-size: .92rem; }
.how-faces { display: flex; justify-content: center; margin-bottom: 20px; }
.how-faces img {
  width: 86px; height: 86px; border-radius: 50%; object-fit: cover;
  border: 3px solid var(--panel); margin-left: -22px; background: var(--panel-2);
  transition: transform .25s var(--ease);
}
.how-faces img:first-child { margin-left: 0; }
.how-faces:hover img { transform: translateY(-3px); }
@media (max-width: 520px) {
  .how-faces img { width: 64px; height: 64px; margin-left: -16px; }
}

/* ---- the jump bar ------------------------------------------------------
   Twelve sections is a long page, and a long page with no map reads as one
   thing you either finish or abandon. This makes it a set of answers: every
   section is one tap away and the current one is marked.

   Sticky UNDER the site bar, not at 0 -- `.bar` is sticky at top:0 and 54px
   tall, so a second sticky at 0 renders behind it. */
.how-nav {
  position: sticky; top: var(--bar-h); z-index: 40;
  background: rgba(8,9,11,.9);
  backdrop-filter: blur(14px) saturate(140%);
  -webkit-backdrop-filter: blur(14px) saturate(140%);
  border-bottom: 1px solid var(--line);
}
.how-nav__inner {
  width: 100%; max-width: var(--wrap); margin-inline: auto;
  padding: 8px 20px;
  display: flex; gap: 6px; flex-wrap: wrap;
}
.how-nav a {
  font-family: var(--font-d); font-size: .74rem; font-weight: 600;
  letter-spacing: .04em; color: var(--muted);
  padding: 5px 11px; border-radius: 999px;
  border: 1px solid transparent; white-space: nowrap;
  transition: color .18s var(--ease), background .18s var(--ease),
              border-color .18s var(--ease);
}
.how-nav a:hover { color: var(--text); background: var(--panel-2); }
.how-nav a.is-current {
  color: var(--gold-soft); background: var(--gold-dim);
  border-color: var(--gold-line);
}
/* On a phone the bar becomes one scrolling row rather than four stacked
   ones -- four rows of pills is a third of the screen gone before the page
   starts. app.js keeps the current pill scrolled into view. */
@media (max-width: 900px) {
  .how-nav__inner {
    flex-wrap: nowrap; overflow-x: auto; padding: 7px 14px;
    scrollbar-width: none; -ms-overflow-style: none;
    -webkit-overflow-scrolling: touch;
    scroll-padding-inline: 14px;
  }
  .how-nav__inner::-webkit-scrollbar { display: none; }
}
/* Both sticky bars, plus a little air. Without this an anchor jump lands
   with the heading hidden behind the jump bar that sent you there.

   --how-jump is measured and set by app.js, because the jump bar's height
   is not a constant: it is one scrolling row on a phone and one OR TWO rows
   of pills on a desktop, depending on how many sections the page has and
   how wide the window is. The fallback here is the one-row case, so the
   page is still usable in the moment before the script runs. */
.how-sec { scroll-margin-top: var(--how-jump, calc(var(--bar-h) + 58px)); }

/* .section pads top AND bottom, so twelve of them in a row double every
   gap -- roughly 120px of nothing between one section and the next. Only
   the first keeps its top padding. This was an inline padding-top:0 on
   every section but the first, which is eleven places to forget. */
.how-sec + .how-sec { padding-top: 0; }

/* ---- three tiers, not two ---------------------------------------------
   The dynamic subscription is the third way to have her, so it sits beside
   the other two rather than in a footnote. Three cards need a wider break
   than two: at 820px three columns are 250px each and every bullet wraps
   after four words. */
@media (min-width: 1040px) {
  .how-tiers--3 { grid-template-columns: repeat(3, minmax(0, 1fr)); }
}
@media (min-width: 820px) and (max-width: 1039px) {
  /* Two up, and the feature card takes the full width underneath rather
     than sitting in a half-width column with a button in it. */
  .how-tiers--3 .how-tier--feature { grid-column: 1 / -1; }
}
.how-tier--feature {
  position: relative;
  border-color: var(--gold-line);
  background:
    radial-gradient(120% 150% at 50% -30%, rgba(217,164,65,.1), transparent 60%),
    var(--panel);
}
.how-tier__flag {
  position: absolute; top: -9px; right: 14px;
  font-family: var(--font-d); font-size: .62rem; font-weight: 700;
  letter-spacing: .11em; text-transform: uppercase;
  color: #1a1206; background: var(--gold);
  border-radius: 999px; padding: 3px 9px; line-height: 1.35;
}

/* ---- the whole cast, at the end ---------------------------------------
   Was three overlapping circles, hardcoded to three files on disk. The cast
   grows; a page still showing the same three faces under-sells the roster
   and links nowhere. Every listed companion, each one a link to her page. */
.how-cast {
  display: flex; flex-wrap: wrap; justify-content: center;
  gap: 16px 14px; margin-bottom: 26px;
}
.how-cast__one {
  width: 92px; display: flex; flex-direction: column; align-items: center;
  gap: 7px; color: var(--muted);
}
.how-cast__one img, .how-cast__blank {
  width: 84px; height: 84px; border-radius: 50%; object-fit: cover;
  border: 2px solid var(--line); background: var(--panel-2);
  transition: transform .25s var(--ease), border-color .25s var(--ease);
}
.how-cast__blank {
  display: grid; place-items: center;
  font-family: var(--font-d); font-size: 1.5rem; color: var(--faint);
}
.how-cast__one:hover img, .how-cast__one:hover .how-cast__blank {
  transform: translateY(-4px); border-color: var(--gold-line);
}
.how-cast__name {
  font-size: .76rem; font-weight: 600; text-align: center;
  line-height: 1.25; max-width: 100%;
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.how-cast__one:hover .how-cast__name { color: var(--gold-soft); }
@media (max-width: 520px) {
  .how-cast { gap: 14px 10px; }
  .how-cast__one { width: 72px; }
  .how-cast__one img, .how-cast__blank { width: 66px; height: 66px; }
  .how-cast__name { font-size: .7rem; }
}

/* sortable table headers */
.th-sort { cursor: pointer; user-select: none; position: relative; }
.th-sort:hover { color: var(--text); }
.th-sort::after {
  content: "\2195"; opacity: .28; margin-left: .35em; font-size: .85em;
}
.th-sort.is-sorted::after { opacity: .95; }
.th-sort[data-dir="asc"]::after  { content: "\2191"; }
.th-sort[data-dir="desc"]::after { content: "\2193"; }
.th-sort:focus-visible { outline: 2px solid var(--gold-line); outline-offset: -2px; }

/* webhook payload editor: one line per field, not a stack of full-width
   inputs. The fixed meta fields read as data; the two editable kinds get
   controls, so what can be changed is obvious at a glance. */
.wh-row {
  display: flex; align-items: center; gap: 8px;
  padding: 5px 8px; border-radius: var(--r-sm);
  border: 1px solid transparent; min-height: 34px;
}
.wh-row + .wh-row { margin-top: 2px; }
.wh-row .wh-name {
  font-size: .78rem; color: var(--muted);
  min-width: 8.6rem; flex: 0 0 auto;
}
.wh-row .wh-note { font-size: .72rem; color: var(--faint); }
.wh-row input[type=text], .wh-row input[type=number], .wh-row select {
  height: 28px; padding: 2px 8px; font-size: .8rem; width: auto; flex: 1 1 6rem;
  min-width: 0;
}
.wh-row select { flex: 0 0 7.5rem; }
.wh-row .wh-val { flex: 1 1 6rem; display: flex; min-width: 0; }
.wh-row .wh-val input[type=text], .wh-row .wh-val input[type=number] { width: 100%; }
.wh-row .btn { flex: 0 0 auto; }
/* the one field she writes -- called out, because it is the only value a
   prompt can shape */
.wh-row--hers {
  background: var(--panel-2); border-color: var(--gold-line);
}
.wh-row--hers .wh-name { color: var(--text); font-weight: 600; }
.wh-row--own { background: var(--panel-2); }

/* ---- a note you can open ----------------------------------------------- */
/* "read" was a bare grey word in a table cell, which reads as data, not as a
   control -- so it did not get clicked. It is a pill with a chevron now, it
   says what it will do, and a line of the note itself shows underneath so it
   is obvious there is something behind it. */
.note-open > summary {
  cursor: pointer; list-style: none;
  display: inline-flex; align-items: center; gap: 6px;
  padding: 3px 10px; border: 1px solid var(--line-2); border-radius: 999px;
  background: var(--panel-2); color: var(--muted);
  font-size: .76rem; font-weight: 600; line-height: 1.45; white-space: nowrap;
  transition: color .18s var(--ease), border-color .18s var(--ease),
              background .18s var(--ease);
}
.note-open > summary::-webkit-details-marker { display: none; }
.note-open > summary::before {
  content: "▸"; color: var(--gold); font-size: .72rem;
  transition: transform .18s var(--ease); display: inline-block;
}
.note-open[open] > summary::before { transform: rotate(90deg); }
.note-open > summary::after { content: "Read her note"; }
.note-open[open] > summary::after { content: "Hide"; }
.note-open > summary:hover {
  color: var(--text); border-color: var(--gold-line); background: var(--ink-2);
}
.note-open > summary:focus-visible { outline: 2px solid var(--gold); outline-offset: 2px; }
.note-open > .note-body {
  margin-top: .45rem; padding-left: .7rem; white-space: pre-wrap;
  border-left: 2px solid var(--gold-line); color: var(--text);
}
/* the teaser under the pill, gone once the real thing is open */
.note-peek { color: var(--faint); margin-top: .35rem; cursor: pointer; }
.note-open[open] + .note-peek { display: none; }

/* ---- credit banner ------------------------------------------------------ */
/* Attribution for a character who came from somebody else's work. It was a
   btn-sm above the name, which read as a stray tag and got no clicks. A
   banner is the honest size for a credit: it is not a UI control competing
   with the buy button, it is a statement about whose work she is. */
.credit-banner {
  display: flex; align-items: center; gap: 14px; flex-wrap: wrap;
  width: 100%; box-sizing: border-box;
  margin: 0 0 18px; padding: 14px 18px;
  border: 1px solid var(--gold-line); border-left: 4px solid var(--gold);
  border-radius: var(--r);
  background: linear-gradient(90deg,
      color-mix(in srgb, var(--gold) 10%, transparent), var(--panel-2) 55%);
  color: var(--text); text-decoration: none;
  transition: border-color .18s var(--ease), background .18s var(--ease),
              transform .18s var(--ease);
}
.credit-banner:hover {
  border-color: var(--gold); transform: translateY(-1px);
  background: linear-gradient(90deg,
      color-mix(in srgb, var(--gold) 18%, transparent), var(--panel-2) 55%);
}
.credit-banner:focus-visible { outline: 2px solid var(--gold); outline-offset: 3px; }
.credit-banner__k {
  font-family: var(--font-d); font-size: .68rem; letter-spacing: .16em;
  text-transform: uppercase; color: var(--gold); white-space: nowrap;
}
.credit-banner__t {
  flex: 1 1 14rem; min-width: 0;
  font-size: 1.04rem; font-weight: 600; line-height: 1.35;
}
.credit-banner__go {
  font-size: .86rem; font-weight: 600; color: var(--gold); white-space: nowrap;
}
.credit-banner.is-static { cursor: default; }
@media (max-width: 560px) {
  .credit-banner { gap: 6px; }
  .credit-banner__t { flex: 1 1 100%; font-size: .98rem; }
}

/* ---- exception list ----------------------------------------------------- */
/* One row per exception, scannable. The previous layout gave each one a full
   stat strip and two open code blocks, so three of them filled the screen and
   you had to scroll to learn how many there were. Long things are behind a
   disclosure; what stays visible is what you triage on. */
.alert-list { display: flex; flex-direction: column; gap: 10px; }
.alert-row {
  display: flex; align-items: stretch; gap: 0;
  border: 1px solid var(--line); border-radius: var(--r);
  background: var(--panel); overflow: hidden;
}
.alert-row.is-done { opacity: .6; }
/* Severity at a glance, down the left edge: red for something that has
   happened more than once, amber for a one-off, green for one that fixed
   itself. */
.alert-row__bar { flex: 0 0 4px; background: var(--line-2); }
.alert-row__bar.bad  { background: var(--danger); }
.alert-row__bar.warn { background: var(--gold); }
.alert-row__bar.ok   { background: var(--ok, #3fb950); }
.alert-row__body { flex: 1 1 auto; min-width: 0; padding: 12px 14px; }
.alert-row__head {
  display: flex; align-items: flex-start; gap: 10px; justify-content: space-between;
}
.alert-row__what {
  font-family: var(--font-m); font-size: .88rem; line-height: 1.45;
  color: var(--text); word-break: break-word; min-width: 0;
}
.alert-row__meta { display: flex; gap: 5px; flex: 0 0 auto; }
.alert-row__facts {
  margin-top: 6px; display: flex; flex-wrap: wrap; gap: 6px;
  font-size: .78rem; color: var(--muted);
}
.alert-row__facts a { color: var(--gold); text-decoration: none; }
.alert-row__facts a:hover { text-decoration: underline; }
.alert-row__more { margin-top: 8px; }
.alert-row__more > summary {
  cursor: pointer; font-size: .78rem; color: var(--muted);
  list-style: none; display: inline-flex; align-items: center; gap: 6px;
}
.alert-row__more > summary::-webkit-details-marker { display: none; }
.alert-row__more > summary::before {
  content: "▸"; color: var(--gold); font-size: .7rem;
  transition: transform .18s var(--ease); display: inline-block;
}
.alert-row__more[open] > summary::before { transform: rotate(90deg); }
.alert-row__more > summary:hover { color: var(--text); }
.alert-row__more pre {
  margin: 8px 0 0; padding: 10px; background: var(--ink-2);
  border-radius: var(--r-sm); overflow-x: auto;
  font-size: .74rem; line-height: 1.5; white-space: pre;
}
.alert-row__act {
  flex: 0 0 auto; display: flex; align-items: center; padding: 0 12px 0 6px;
}
@media (max-width: 640px) {
  .alert-row { flex-wrap: wrap; }
  .alert-row__act { padding: 0 14px 12px; }
}

/* ── the custom-character builder ─────────────────────────────────────── */
/* Wider than the setup wizard on purpose: these are paragraphs someone is
   writing, not radio buttons they are picking, and a 720px column makes an
   eight-row textarea feel like a comment box. */
.wizard--build { max-width: 860px; }
.wizard--build .card { margin-bottom: 14px; }
.wizard--build textarea { width: 100%; resize: vertical; }
.wizard--build .card.is-advanced { opacity: .78; }
.wizard--build .card.is-advanced:focus-within { opacity: 1; }

.q-head { display: flex; align-items: center; gap: 10px; }
.q-head h2, .q-head h3 { flex: 1; margin: 0; }
.q-copy {
  flex: none; width: 30px; height: 30px; border-radius: var(--r-sm);
  border: 1px solid var(--line); background: var(--panel-2);
  color: var(--muted); cursor: pointer; font-size: .95rem; line-height: 1;
}
.q-copy:hover { color: var(--gold-soft); border-color: var(--gold-line); }

/* copy-from-a-character row */
.tpl-row { display: flex; flex-wrap: wrap; gap: 8px; }
.tpl-card {
  display: flex; align-items: center; gap: 9px; text-align: left;
  padding: 7px 11px 7px 7px; border-radius: var(--r-sm);
  border: 1px solid var(--line); background: var(--panel-2);
  color: var(--text); cursor: pointer; max-width: 260px;
}
.tpl-card:hover { border-color: var(--gold-line); background: var(--panel-3); }
.tpl-card.is-on { border-color: var(--gold); background: var(--gold-dim); }
.tpl-card img { border-radius: 8px; object-fit: cover; flex: none; }
.tpl-card__blank {
  width: 44px; height: 44px; border-radius: 8px; flex: none;
  display: grid; place-items: center;
  background: var(--panel-3); color: var(--faint); font-weight: 600;
}
.tpl-card__text { display: flex; flex-direction: column; min-width: 0; }
.tpl-card__text b { font-size: .9rem; }
.tpl-card__text span {
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}

.tpl-pick {
  display: flex; flex-wrap: wrap; gap: 6px; margin-top: 8px;
  padding: 9px; border-radius: var(--r-sm);
  border: 1px dashed var(--line-2); background: var(--ink-2);
}
.tpl-mini {
  display: inline-flex; align-items: center; gap: 6px;
  padding: 4px 9px 4px 4px; border-radius: 999px;
  border: 1px solid var(--line); background: var(--panel-2);
  color: var(--text); cursor: pointer; font-size: .82rem;
}
.tpl-mini:hover { border-color: var(--gold-line); color: var(--gold-soft); }
.tpl-mini img { border-radius: 50%; object-fit: cover; }

/* preset starters. Deliberately styled as BUTTONS THAT FILL A BOX rather than
   as a selected/unselected choice -- they insert text you then edit, and
   anything that looked like a radio would imply the list was the menu. */
.chips { display: flex; flex-wrap: wrap; gap: 6px; align-items: center;
         margin-bottom: 8px; }
.chip {
  padding: 5px 11px; border-radius: 999px; cursor: pointer; font-size: .84rem;
  border: 1px dashed var(--line-2); background: transparent;
  color: var(--muted);
}
.chip:hover { border-style: solid; border-color: var(--gold-line);
              color: var(--gold-soft); background: var(--gold-dim); }
.chip--radio { border-style: solid; }
.chip--radio input { position: absolute; opacity: 0; pointer-events: none; }
.chip--radio:has(input:checked) {
  border-color: var(--gold); background: var(--gold-dim); color: var(--gold-soft);
}

/* a filled box should be visibly the thing that just changed */
.just-filled {
  animation: fillflash .9s ease-out;
}
@keyframes fillflash {
  from { border-color: var(--gold); box-shadow: 0 0 0 3px var(--gold-dim); }
  to   { border-color: var(--line); box-shadow: none; }
}

.prompt-block {
  white-space: pre-wrap; word-break: break-word; font-size: .82rem;
  line-height: 1.5; max-height: 340px; overflow: auto; margin: 8px 0 0;
  padding: 11px 13px; border-radius: var(--r-sm);
  border: 1px solid var(--line); background: var(--ink-2); color: var(--muted);
}
.wizard--build details { margin-bottom: 8px; }
.wizard--build summary { cursor: pointer; display: flex; gap: 9px;
                         align-items: baseline; }

.wizard__actions {
  display: flex; gap: 10px; justify-content: flex-end;
  position: sticky; bottom: 0; padding: 12px 0;
  background: linear-gradient(to top, var(--ink) 62%, transparent);
}
@media (max-width: 640px) {
  .tpl-card { max-width: 100%; width: 100%; }
  .wizard__actions .btn { flex: 1; }
}

/* ── custom-slot disclosure modal ─────────────────────────────────────── */
/* Reuses .editor-overlay for the backdrop. The box is its own thing because
   the pop-out editor's is sized for a textarea, and this is prose that has to
   stay readable on a phone -- the whole reason this became a modal is that the
   same text inside a 430px card column was unreadable. */
/* MUST come with the overlay: .editor-overlay sets display:flex, and an
   author `display` beats the UA stylesheet's [hidden]{display:none}. Without
   this the modal is visible the moment the page loads and setting
   gate.hidden = true does nothing at all. */
.slot-gate[hidden] { display: none; }
.slot-gate__box {
  width: min(560px, 100%); max-height: 90vh; overflow-y: auto;
  background: var(--panel); border: 1px solid var(--line);
  border-radius: var(--r); padding: 26px 28px;
  box-shadow: 0 24px 60px rgba(0, 0, 0, .55);
}
.slot-gate__box h2 { font-size: 1.35rem; letter-spacing: -.02em; margin: 0; }
/* One limitation. Shared by the modal on her storefront page and the block
   on checkout, so the two can never drift into saying the same thing in two
   different shapes. Plain block layout is the POINT: both places previously
   used .notice, which is display:flex, and the paragraphs inside it laid
   themselves out as unreadable side-by-side columns. */
.limit-point {
  padding: 12px 0; border-top: 1px solid var(--line-soft);
}
.limit-point b { display: block; margin-bottom: 4px; color: var(--gold-soft); }
.limit-point p {
  margin: 0; color: var(--muted); font-size: .875rem; line-height: 1.55;
}
.limit-point--soft b { color: var(--text); }

/* The checkout version: a bordered block rather than a floating dialog. */
.limits {
  margin: 1.2rem 0; padding: 14px 16px;
  border: 1px solid var(--gold-line); border-radius: var(--r-sm);
  background: var(--gold-dim);
}
.limits__head { margin: 0 0 .2rem; font-size: .9rem; line-height: 1.5; }
.limits .limit-point:first-of-type { border-top: none; padding-top: 6px; }
.slot-gate__actions {
  display: flex; gap: 10px; justify-content: flex-end;
  margin-top: 20px; padding-top: 16px; border-top: 1px solid var(--line);
}
@media (max-width: 560px) {
  .slot-gate__box { padding: 20px 18px; }
  .slot-gate__actions { flex-direction: column-reverse; }
  .slot-gate__actions .btn { width: 100%; }
}

/* ── "notify me when one opens" ───────────────────────────────────────── */
/* Same trap as .slot-gate: .editor-overlay sets display:flex, and an author
   display beats the UA stylesheet's [hidden]{display:none}, so without this
   the dialog is painted on load and hiding it does nothing. It reuses
   .slot-gate__box for the panel itself, so only the backdrop rule is new. */
/* ---- the backend nav, in collapsible groups ------------------------- */
.nav-top {
  display: block;
  font-weight: 600;
  margin-bottom: .35rem;
}
.navgroup { margin-bottom: .1rem; }
.navgroup > summary {
  display: flex;
  align-items: center;
  gap: .4rem;
  cursor: pointer;
  list-style: none;
  padding: .34rem .5rem;
  border-radius: 7px;
  font-size: .74rem;
  letter-spacing: .06em;
  text-transform: uppercase;
  color: var(--muted);
  user-select: none;
}
.navgroup > summary::-webkit-details-marker { display: none; }
.navgroup > summary:hover { background: var(--bg-soft); color: var(--text); }
.navgroup__label { flex: 1 1 auto; }
/* Drawn, not typed: a glyph caret renders as a dash in some fonts, which
   reads as "disabled" rather than "opens". */
.navgroup__caret {
  width: 0; height: 0;
  border-left: 4px solid transparent;
  border-right: 4px solid transparent;
  border-top: 5px solid currentColor;
  transition: transform .16s;
  opacity: .55;
}
.navgroup[open] > summary .navgroup__caret { transform: rotate(180deg); }
/* The links themselves keep whatever the sidebar already gives them; they
   are only indented so a shut group reads as one line rather than a gap. */
.navgroup > a { padding-left: 1.1rem; }

/* ===================================================================
   The Dynamic Companion page
   ===================================================================
   The product is "all of them", so the faces have to carry the page. It
   was a wall of prose with the cast reduced to name badges, which
   described the thing instead of showing it.

   Two effects, both pure CSS. No JS: script-src is 'self' with no
   'unsafe-inline', and an effect that needs a script is an effect that
   silently does not happen.                                             */

/* Text only now -- the art is the strip below it, and nothing else. */
.dyn-hero { margin-bottom: clamp(16px, 3vw, 26px); }
.dyn-hero__text { min-width: 0; }

/* The three points, in a row on a wide screen and stacked on a narrow one.
   These are what the storefront modal used to hold: it was deleted once this
   page existed, so the wording lives in exactly one place. */
.dyn-points {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(min(100%, 15rem), 1fr));
  gap: 12px;
  margin: 0 0 1.4rem;
}
.dyn-point {
  display: flex;
  gap: .7rem;
  align-items: flex-start;
  background: var(--bg-soft);
  border: 1px solid var(--line-soft);
  border-radius: 12px;
  padding: 14px 16px;
}
.dyn-point__n {
  flex: 0 0 auto;
  width: 26px; height: 26px;
  border-radius: 50%;
  display: grid; place-items: center;
  background: var(--accent);
  color: #10120f;
  font-weight: 800;
  font-size: .85rem;
}
.dyn-point b { display: block; margin-bottom: .15rem; }
.dyn-point .muted { font-size: .88rem; }
.dyn-hero__text h2 { margin: 0 0 .4rem; font-size: clamp(1.5rem, 4vw, 2.1rem); }

.dyn-price { display: flex; align-items: baseline; gap: .4rem; margin: .6rem 0 .3rem; }
.dyn-price__amt { font-size: clamp(2rem, 6vw, 2.8rem); font-weight: 800; color: var(--accent); line-height: 1; }
.dyn-price__per { color: var(--muted); }

/* ---- the strip ----------------------------------------------------
   Everyone, sliding past. Duplicated in the template so the run can be
   translated exactly -50% and start again with no seam.                */
.dyn-strip {
  position: relative;
  overflow: hidden;
  margin: 0 0 1rem;
  /* Fade both edges so faces enter and leave rather than being cut. */
  -webkit-mask-image: linear-gradient(90deg, transparent, #000 8%, #000 92%, transparent);
          mask-image: linear-gradient(90deg, transparent, #000 8%, #000 92%, transparent);
}
.dyn-strip__track {
  display: flex;
  gap: 12px;
  width: max-content;
  animation: dyn-slide 42s linear infinite;
}
.dyn-strip:hover .dyn-strip__track { animation-play-state: paused; }
@keyframes dyn-slide {
  from { transform: translateX(0); }
  to   { transform: translateX(-50%); }
}
.dyn-strip__item {
  flex: 0 0 auto;
  width: clamp(96px, 20vw, 132px);
  text-decoration: none;
  color: inherit;
}
.dyn-strip__item img {
  width: 100%;
  aspect-ratio: 3 / 4;
  object-fit: cover;
  border-radius: 12px;
  border: 1px solid var(--line-soft);
  display: block;
  transition: transform .25s, border-color .25s;
}
.dyn-strip__item:hover img {
  transform: translateY(-4px) scale(1.03);
  border-color: var(--accent);
}
.dyn-strip__name {
  display: block;
  text-align: center;
  font-size: .82rem;
  color: var(--muted);
  margin-top: .35rem;
}

/* Somebody who has asked for less motion gets the faces without the
   movement, not a broken layout: the track stops and scrolls by hand. */
@media (prefers-reduced-motion: reduce) {
  .dyn-strip__track { animation: none; }
  .dyn-strip { overflow-x: auto; }
}

.notify-gate[hidden] { display: none; }

/* The call to action. A row on a wide screen, stacked on a phone -- the
   button must never end up beside a two-line paragraph in a 320px column. */
/* Sits ABOVE the cast on both pages, so the spacing is a lead-in to the grid
   rather than a gap after it. */
.notify-cta {
  display: flex; flex-wrap: wrap; gap: 16px;
  align-items: center; justify-content: space-between;
  margin: 0 0 .25rem; padding: 20px 22px;
  border: 1px solid var(--gold-line); border-radius: var(--r);
  background: var(--gold-dim);
}
.notify-cta h2, .notify-cta h3 { margin: 0 0 4px; font-size: 1.05rem; }
.notify-cta p {
  margin: 0; max-width: 62ch;
  color: var(--muted); font-size: .875rem; line-height: 1.55;
}
.notify-cta form { margin: 0; }
.notify-cta .btn { white-space: nowrap; }
/* The two strips above the cast -- the opening alert and the dynamic offer --
   side by side on a wide screen. Stacked they were two full-width panels
   between the hero and the first face, a screen of pitch before the product. */
.cta-pair { display: grid; gap: 12px; }
.cta-pair > .notify-cta { margin: 0; }
@media (min-width: 900px) {
  .cta-pair { grid-template-columns: repeat(2, minmax(0, 1fr)); align-items: stretch; }
  .cta-pair > .notify-cta { flex-direction: column; align-items: flex-start; gap: 12px; }
  .cta-pair > .notify-cta .btn { white-space: normal; }
}
/* The home-page variant: quieter, because it sits under the cast with the
   join-the-server panel directly below it and two loud boxes in a row read
   as an advert. */
.notify-cta--slim {
  padding: 14px 18px;
  border-color: var(--line); background: transparent;
}
.notify-cta--slim h2, .notify-cta--slim h3 { font-size: .95rem; }
.notify-cta--slim p { font-size: .82rem; }
@media (max-width: 640px) {
  .notify-cta { flex-direction: column; align-items: stretch; }
  .notify-cta .btn { width: 100%; }
}

/* ── rich confirm modal ───────────────────────────────────────────────── */
/* [hidden] MUST be redeclared: .editor-overlay sets display:flex and an
   author display beats the UA stylesheet's [hidden]{display:none}, so
   without this the dialog is painted on page load and hiding it does
   nothing. Same trap as .slot-gate. */
.confirm-gate[hidden] { display: none; }
.confirm-box {
  width: min(460px, 100%);
  background: var(--panel); border: 1px solid var(--line);
  border-radius: var(--r); padding: 22px 24px;
  box-shadow: 0 24px 60px rgba(0, 0, 0, .55);
}
.confirm-box h2 { font-size: 1.2rem; letter-spacing: -.02em; margin: 0; }
.confirm-box > p { margin: .4rem 0 0; font-size: .875rem; }

/* The identifying facts, as a label/value grid -- this is the part that
   answers "which one am I about to end". */
.confirm-facts {
  display: grid; grid-template-columns: auto 1fr; gap: 6px 14px;
  margin: 16px 0 0; padding: 12px 14px;
  border: 1px solid var(--line); border-radius: var(--r-sm);
  background: var(--ink-2);
}
.confirm-facts dt {
  color: var(--faint); font-size: .78rem; text-transform: uppercase;
  letter-spacing: .04em; align-self: center;
}
.confirm-facts dd {
  margin: 0; color: var(--text); font-size: .9rem; font-weight: 550;
  word-break: break-word;
}
.confirm-actions {
  display: flex; gap: 10px; justify-content: flex-end; margin-top: 18px;
}
@media (max-width: 460px) {
  .confirm-actions { flex-direction: column-reverse; }
  .confirm-actions .btn { width: 100%; }
}

/* ---- rich hover panels (the cost breakdowns) ---------------------------
   A native title= is unstyleable: one run of flat text, no wrapping control,
   no emphasis. The cost breakdowns outgrew it -- six lines of numbers with
   nothing to tell a heading from a caveat.

   The markup sits in the cell, hidden, and app.js lifts a copy into a fixed
   panel on hover or focus. Fixed rather than absolute so a table that scrolls
   inside .table-wrap cannot clip it.                                      */
.has-tip { position: relative; cursor: help; }
.has-tip > .tip-src { display: none; }

.tip-panel {
  position: fixed; z-index: 90;
  min-width: 17rem; max-width: 25rem;
  background: var(--panel-2); color: var(--text);
  border: 1px solid var(--line-2); border-radius: var(--r-sm);
  box-shadow: var(--shadow-lg);
  padding: .6rem .7rem;
  font-family: var(--font-b); font-size: .74rem; line-height: 1.45;
  pointer-events: none;
  opacity: 0; transform: translateY(3px);
  transition: opacity .12s var(--ease), transform .12s var(--ease);
}
.tip-panel.is-on { opacity: 1; transform: none; }
/* The panel must never depend on an animation having run to be readable. */
@media (prefers-reduced-motion: reduce) {
  .tip-panel { transition: none; transform: none; }
}

.tip-h {
  display: block; font-family: var(--font-d); font-size: .78rem;
  font-weight: 600; color: var(--text);
  padding-bottom: .35rem; margin-bottom: .4rem;
  border-bottom: 1px solid var(--line);
}
.tip-h .tip-hv { float: right; color: var(--gold-soft); }

/* label on the left, figure on the right, so a column of them lines up */
.tip-row {
  display: flex; justify-content: space-between; gap: 1.2rem;
  padding: .08rem 0;
}
.tip-row > span:first-child { color: var(--muted); }
.tip-row > span:last-child { font-variant-numeric: tabular-nums; }
.tip-row.sum {
  margin-top: .3rem; padding-top: .3rem;
  border-top: 1px solid var(--line-soft); font-weight: 600;
}
.tip-row.sum > span:first-child { color: var(--text); }

.tip-note {
  display: block; margin-top: .45rem; padding-top: .4rem;
  border-top: 1px solid var(--line-soft);
  color: var(--muted); font-size: .7rem;
}
.tip-note + .tip-note { margin-top: .3rem; padding-top: 0; border-top: 0; }
.tip-note b { color: var(--text); font-weight: 600; }
.tip-note.warn { color: var(--warn); }
.tip-note.warn b { color: var(--warn); }
.tip-note.info b { color: var(--info); }
.tip-note ul { margin: .2rem 0 0; padding-left: .95rem; }
.tip-note li { margin: .1rem 0; }

.tip-val-up { color: var(--warn); }

/* The spend panel (owner/_spend.html): a heading with its share, one line
   saying what it is, then its jobs and their chats or games, indented. */
.tip-row.tip-head { margin-top: .4rem; font-weight: 600; }
.tip-row.tip-head > span:first-child { color: var(--text); }
.tip-row.tip-head.is-lens > span:first-child { color: var(--gold-soft); }
.tip-pct { color: var(--muted); font-weight: 400; margin-left: .45rem; }
.tip-where {
  display: block; color: var(--muted); font-size: .68rem;
  margin: -.05rem 0 .1rem;
}
.tip-row.tip-sub { padding-left: .75rem; font-size: .7rem; }
.tip-row.tip-sub2 { padding-left: 1.5rem; font-size: .68rem; }

/* The instance page's spend table: the same headings as the panel. */
.spend-table tr.spend-head td { border-top: 1px solid var(--line); }
.spend-table tr.spend-kind td:first-child { padding-left: 1.2rem; }
.spend-table tr.spend-detail td { color: var(--muted); font-size: .8rem; }
.spend-table tr.spend-detail td:first-child { padding-left: 2.4rem; }

/* Server-sorted headers: a real link, so the sort covers every page. */
th.th-sort > a.th-link { color: inherit; text-decoration: none; }
th.th-sort > a.th-link:hover { text-decoration: underline; }
.tip-val-ok { color: var(--ok); }

/* ==========================================================================
   FREE TRIAL
   ========================================================================== */

/* ---- the bottom-right card on the storefront --------------------------- */
.trial-toast {
  position: fixed;
  right: 18px; bottom: 18px;
  z-index: 60;
  width: min(330px, calc(100vw - 28px));
  background: var(--panel);
  border: 1px solid var(--gold-line);
  border-radius: var(--r);
  box-shadow: var(--shadow-lg);
  padding: 15px 16px 16px;
  /* Starts below the fold and slides up. The class is added by app.js so a
     browser with JS off never sees a card it cannot dismiss. */
  transform: translateY(14px);
  opacity: 0;
  transition: transform .38s var(--ease), opacity .38s var(--ease);
}
.trial-toast.is-in { transform: none; opacity: 1; }
.trial-toast[hidden] { display: none; }
.trial-toast__x {
  position: absolute; top: 6px; right: 8px;
  background: none; border: 0; cursor: pointer;
  color: var(--faint); font-size: 1.25rem; line-height: 1;
  padding: 4px 7px; border-radius: var(--r-sm);
}
.trial-toast__x:hover { color: var(--text); background: var(--panel-3); }
.trial-toast strong { display: block; margin: .5rem 0 .3rem; font-size: .97rem; }
.trial-toast p {
  font-size: .82rem; color: var(--muted); line-height: 1.45;
  margin: 0 0 .75rem;
}
@media (max-width: 560px) {
  .trial-toast { right: 10px; left: 10px; bottom: 10px; width: auto; }
}
/* Somebody who asked not to see animation gets the card, not the slide. */
@media (prefers-reduced-motion: reduce) {
  .trial-toast { transition: none; transform: none; opacity: 1; }
}

.trial-pill {
  display: inline-block;
  font: 600 .68rem/1 var(--font-d);
  letter-spacing: .09em; text-transform: uppercase;
  color: var(--gold-soft);
  background: var(--gold-dim);
  border: 1px solid var(--gold-line);
  border-radius: 999px;
  padding: 5px 10px;
}

/* ---- /trial ------------------------------------------------------------- */
.trial-hero { text-align: center; padding: clamp(18px, 4vw, 34px) 0 0; }
.trial-hero h1 { font-size: clamp(1.6rem, 4.2vw, 2.5rem); }

.trial-rules { list-style: none; margin: 0; padding: 0; }
.trial-rules li {
  display: flex; gap: 11px; align-items: flex-start;
  padding: 10px 0;
  border-bottom: 1px solid var(--line-soft);
}
.trial-rules li:last-child { border-bottom: 0; padding-bottom: 0; }
.trial-rules .tick {
  flex: none;
  width: 24px; height: 24px; border-radius: 50%;
  display: grid; place-items: center;
  font: 600 .76rem/1 var(--font-d);
  color: var(--gold-soft);
  background: var(--gold-dim);
  border: 1px solid var(--gold-line);
}
.trial-rules li.is-bad .tick {
  color: var(--danger);
  background: var(--danger-dim);
  border-color: rgba(255,122,107,.34);
}
.trial-rules strong { display: block; font-size: .92rem; }

.trial-cols { display: grid; grid-template-columns: 1fr 1fr; gap: 18px; }
@media (max-width: 620px) { .trial-cols { grid-template-columns: 1fr; } }
.trial-colhead {
  font: 600 .7rem/1 var(--font-d);
  letter-spacing: .09em; text-transform: uppercase;
  padding-bottom: 8px; margin-bottom: 9px;
  border-bottom: 1px solid var(--line);
}
.trial-colhead.ok { color: var(--ok); }
.trial-colhead.lock { color: var(--faint); }

.trial-list { list-style: none; margin: 0; padding: 0; }
.trial-list li {
  position: relative;
  padding: 4px 0 4px 19px;
  font-size: .87rem; color: var(--muted); line-height: 1.45;
}
.trial-list li::before {
  content: "✓";
  position: absolute; left: 0; top: 4px;
  color: var(--ok); font-size: .8rem;
}
.trial-list.is-locked li { color: var(--faint); }
.trial-list.is-locked li::before { content: "🔒"; font-size: .68rem; top: 5px; }
.trial-list strong { color: var(--text); }

.trial-steps { margin: 0; padding-left: 1.1rem; }
.trial-steps li {
  font-size: .87rem; color: var(--muted); line-height: 1.5;
  margin: .3rem 0;
}

.trial-pickwrap { display: flex; flex-direction: column; }
.trial-pickbtn { margin-top: 9px; }

/* ---- a locked field on the renter dashboard ----------------------------- */
/* The control is already `readonly`/`disabled` server-side -- this is only
   how it READS. Greyed with a padlock, so it is plainly a thing that exists
   and is switched off rather than a thing that is broken. */
.is-locked-field { position: relative; opacity: .62; }
.is-locked-field input,
.is-locked-field select,
.is-locked-field textarea { cursor: not-allowed; background: var(--panel-2); }
.trial-lockrow {
  display: flex; gap: 9px; align-items: center;
  margin: 0 0 .85rem;
  padding: 9px 12px;
  border: 1px dashed var(--gold-line);
  background: var(--gold-dim);
  border-radius: var(--r-sm);
  font-size: .82rem; color: var(--gold-soft);
}
.trial-lockrow a { color: var(--gold-soft); font-weight: 600; }

/* ---- the countdown banner ---------------------------------------------- */
.trial-banner {
  display: flex; flex-wrap: wrap; gap: 14px; align-items: center;
  justify-content: space-between;
  margin-bottom: 1.2rem; padding: 14px 16px;
  border: 1px solid var(--gold-line);
  background: linear-gradient(180deg, var(--gold-dim), transparent);
  border-radius: var(--r);
}
.trial-banner__left { min-width: 210px; flex: 1 1 260px; }
.trial-banner__clock {
  font: 700 1.32rem/1 var(--font-d);
  color: var(--gold-soft);
  font-variant-numeric: tabular-nums;
}
.trial-banner__acts { display: flex; gap: 8px; flex-wrap: wrap; }

/* ---- picture focus (owner Listing tab) ---------------------------------
   One row per picture. The stage shows the whole picture and takes the
   click; the previews are the crops the app and the site make from it,
   laid out by app.js [data-focus-pick]. */
.focus-item { border-top: 1px solid var(--line); }
.focus-item summary {
  display: flex; align-items: center; gap: 12px; padding: .6rem 0;
  cursor: pointer; list-style: none;
}
.focus-item summary::-webkit-details-marker { display: none; }
.focus-thumb {
  width: 48px; height: 48px; border-radius: 8px; object-fit: cover;
  flex: none; background: var(--panel);
}
.focus-name { display: flex; flex-direction: column; gap: 2px; flex: 1; min-width: 0; }
.focus-name b { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.focus-name .ok { color: var(--gold-soft); }
.focus-item[open] summary .btn { visibility: hidden; }
.focus-pick { padding: .2rem 0 1rem; }
.focus-grid {
  display: grid; grid-template-columns: minmax(0, 1fr) minmax(0, 1fr);
  gap: 18px; align-items: start;
}
@media (max-width: 760px) { .focus-grid { grid-template-columns: minmax(0, 1fr); } }
/* Shrink-wrapped so the stage IS the picture: a click maps straight to a
   fraction of the image with no letterbox to subtract. */
.focus-stage {
  position: relative; display: inline-block; vertical-align: top;
  max-width: 100%; border-radius: var(--r-sm); overflow: hidden;
  background: var(--panel); cursor: crosshair; touch-action: none;
  user-select: none;
}
.focus-stage img {
  display: block; width: auto; height: auto; max-width: 100%; max-height: 480px;
  -webkit-user-drag: none;
}
.focus-ring {
  position: absolute; border: 2px solid var(--gold-soft); border-radius: 50%;
  box-shadow: 0 0 0 1px rgba(0,0,0,.6), 0 0 0 9999px rgba(0,0,0,.28);
  transform: translate(-50%, -50%); pointer-events: none;
}
.focus-size { display: flex; align-items: center; gap: 10px; margin-top: .6rem; }
.focus-size input { flex: 1; }
.focus-size output { min-width: 5.5em; text-align: right; }
.focus-previews { display: flex; flex-direction: column; gap: 12px; }
.focus-previews .tiny { display: block; margin-bottom: 4px; }
.focus-prev-row { display: flex; gap: 14px; align-items: flex-end; }
.fp { position: relative; overflow: hidden; background: var(--panel); border: 1px solid var(--line); }
.fp img { position: absolute; left: 0; top: 0; max-width: none; }
.fp-wide { width: 100%; max-width: 360px; aspect-ratio: 5 / 2; border-radius: var(--r-sm); }
.fp-card { width: 120px; aspect-ratio: 3 / 4; border-radius: var(--r-sm); }
.fp-circle { width: 96px; aspect-ratio: 1; border-radius: 50%; }

/* ═══════════ home: newest companion ═══════════
   One wide card above the Den. The same pick as the app's Home
   (catalog.newest_card). The art frame crops, so the <img> carries her
   focal point as object-position; a blurred copy behind it is the glow. */
.spot-section { padding-bottom: 0; }
.spot {
  position: relative; overflow: hidden; isolation: isolate;
  display: grid; grid-template-columns: minmax(0, 2fr) minmax(0, 3fr);
  border-radius: var(--r); border: 1px solid var(--gold-line);
  background: var(--ink-2);
  box-shadow: 0 24px 60px -34px color-mix(in srgb, var(--accent) 70%, #000);
}
.spot__glow { position: absolute; inset: 0; z-index: -1; pointer-events: none; }
.spot__glow img {
  width: 100%; height: 100%; object-fit: cover;
  filter: blur(42px) saturate(1.2); opacity: .28; transform: scale(1.2);
}
.spot__art { display: block; position: relative; aspect-ratio: 4 / 5; max-height: 460px; width: 100%; overflow: hidden; background: var(--panel); }
.spot__art img {
  display: block; width: 100%; height: 100%; object-fit: cover;
  object-position: 50% 12%; transition: transform .5s var(--ease);
}
.spot__art:hover img { transform: scale(1.03); }
.spot__art .placeholder {
  width: 100%; height: 100%; display: grid; place-items: center;
  font-family: var(--font-d); font-size: 3.4rem; font-weight: 600; color: var(--line-2);
  background: linear-gradient(150deg, #1c2331, #0b0d12 72%);
}
.spot__body {
  display: flex; flex-direction: column; gap: 10px; justify-content: center;
  padding: clamp(18px, 3.4vw, 38px);
}
.spot__badges { display: flex; gap: 6px; flex-wrap: wrap; }
.spot__name { font-size: clamp(1.7rem, 4vw, 2.5rem); font-weight: 600; letter-spacing: -.02em; line-height: 1.05; }
.spot__tagline { color: #d4dae6; font-size: 1.02rem; line-height: 1.4; }
.spot__bio {
  color: var(--muted); font-size: .9rem; line-height: 1.55; max-width: 60ch;
  display: -webkit-box; -webkit-line-clamp: 4; -webkit-box-orient: vertical; overflow: hidden;
}
.spot__facts { display: flex; align-items: center; gap: 8px; flex-wrap: wrap; margin-top: 2px; }
.spot__price { font-family: var(--font-d); font-weight: 600; font-size: 1.25rem; color: var(--text); margin-right: 4px; }
.spot__price small { font-size: .72rem; font-weight: 400; color: var(--faint); }
.spot__when { font-size: .85rem; font-weight: 600; color: var(--gold-soft); }
.spot__actions { margin-top: 6px; gap: 8px; flex-wrap: wrap; }
@media (max-width: 720px) {
  .spot { grid-template-columns: 1fr; }
  .spot__art { aspect-ratio: 4 / 3; max-height: none; }
}
.den-note { margin: 10px 0 0; }

/* Fenris Labs accounts: the sign-in choices (public/account.html) */
.acct-discord { background: #5865f2; border-color: #5865f2; color: #fff; }
.acct-discord:hover { background: #4752c4; border-color: #4752c4; color: #fff; }
.acct-steam { background: #1b2838; border-color: #2a475e; color: #c7d5e0; }
.acct-steam:hover { background: #2a475e; color: #fff; }
.notice.err { background: var(--danger-dim, rgba(240,80,80,.12)); color: var(--danger); border-color: rgba(240,80,80,.3); }
