/* Shadowrun 6 trade dress: near-black violet paper, magenta as the primary
   accent, cyan for data, and hard-cut corners instead of rounded ones. */

:root {
  --bg: #0a0711;
  --panel: #140e20;
  --panel-2: #1b1329;
  --line: #2c1f45;
  --line-bright: #462d6b;
  --text: #ece7f4;
  --muted: #9d90b5;
  --magenta: #e0197c;
  --magenta-soft: #ff4fa3;
  --cyan: #2fe3d5;
  --amber: #ffb020;
  --red: #ff4d6d;
  --green: #3fd18b;

  --sans: system-ui, -apple-system, "Segoe UI", Roboto, sans-serif;
  --mono: ui-monospace, "JetBrains Mono", "DejaVu Sans Mono", Consolas, monospace;

  /* One shared corner cut keeps every surface on the same grid. */
  --cut: 14px;
}

* { box-sizing: border-box; }

body {
  margin: 0;
  background:
    radial-gradient(900px 500px at 15% -10%, #24123a 0%, transparent 60%),
    radial-gradient(700px 400px at 100% 0%, #3a0f36 0%, transparent 55%),
    var(--bg);
  background-attachment: fixed;
  color: var(--text);
  font-family: var(--sans);
  font-size: 15px;
  line-height: 1.55;
  min-height: 100vh;
  display: flex;
  flex-direction: column;
}

/* Wide enough for the catalogue to keep three columns of cards between the
   category sidebar and the character sheet. */
.shell { width: min(1440px, 100% - 3rem); margin-inline: auto; }

h1, h2, h3 {
  font-weight: 700;
  letter-spacing: 0.06em;
  text-transform: uppercase;
  line-height: 1.15;
  margin: 0;
}
h1 { font-size: 1.6rem; }
h2 { font-size: 1.1rem; }
h3 { font-size: 1rem; letter-spacing: 0.04em; }

a { color: inherit; text-decoration: none; }
.muted { color: var(--muted); }
.small { font-size: 0.83rem; }

/* ---------- top bar ---------- */

.topbar {
  border-bottom: 1px solid var(--line);
  background: rgba(10, 7, 17, 0.92);
  backdrop-filter: blur(8px);
  position: sticky;
  top: 0;
  z-index: 10;
}
.topbar-inner {
  display: flex;
  align-items: center;
  gap: 2rem;
  padding: 0.85rem 0;
}
.brand { display: flex; align-items: center; gap: 0.7rem; }
.brand-mark {
  background: var(--magenta);
  color: #fff;
  font-weight: 800;
  font-size: 0.8rem;
  letter-spacing: 0.1em;
  padding: 0.32rem 0.55rem;
  clip-path: polygon(0 0, 100% 0, 100% 65%, calc(100% - 8px) 100%, 0 100%);
}
.brand-text {
  text-transform: uppercase;
  letter-spacing: 0.22em;
  font-weight: 700;
  font-size: 0.92rem;
}

.nav { display: flex; gap: 1.4rem; margin-right: auto; }
.nav a {
  text-transform: uppercase;
  letter-spacing: 0.1em;
  font-size: 0.8rem;
  color: var(--muted);
  padding-bottom: 2px;
  border-bottom: 2px solid transparent;
}
.nav a:hover { color: var(--text); border-bottom-color: var(--magenta); }

.account { display: flex; align-items: center; gap: 1.4rem; }
.wallet {
  text-align: right;
  border-left: 2px solid var(--cyan);
  padding-left: 0.8rem;
}
.wallet-label {
  display: block;
  font-size: 0.65rem;
  text-transform: uppercase;
  letter-spacing: 0.16em;
  color: var(--muted);
}
.wallet-value { font-family: var(--mono); font-size: 1.05rem; color: var(--cyan); }
.wallet-over { border-left-color: var(--red); }
.wallet-over .wallet-value { color: var(--red); }

/* Same shape as the wallet, so the two read as one status block. */
.identity {
  text-align: right;
  border-left: 2px solid var(--line-bright);
  padding-left: 0.8rem;
}
.identity-label {
  display: block;
  font-size: 0.65rem;
  text-transform: uppercase;
  letter-spacing: 0.16em;
  color: var(--muted);
}
/* Named licences make this the widest thing in the bar. It gets a ceiling and
   trails off rather than pushing the wallet around; the title attribute has
   the full list either way. */
.identity-value {
  display: block;
  font-family: var(--mono);
  font-size: 0.9rem;
  max-width: 20rem;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
/* No SIN means licence-bound gear is locked — that is a state, not a detail. */
.identity-none { border-left-color: var(--amber); }
.identity-none .identity-value { color: var(--amber); }

.whoami { text-align: right; line-height: 1.3; }
.whoami-name { display: block; font-size: 0.85rem; }

/* ---------- folds ---------- */

/* Everything in the app that folds says so the same way, with one drawn
   chevron: the browser's own triangle cannot be styled and is a different
   shape in every engine. It sits in front of a disclosure and turns from
   right to down, and behind the menu in the top bar, where it turns from
   down to up — each the way its own kind of control is read elsewhere. */
summary { cursor: pointer; list-style: none; }
summary::-webkit-details-marker { display: none; }

.buchregel-kopf::before,
.option-regel-kopf::before,
.klappe-kopf::before,
.sheet-upload summary::before,
.switcher summary::after {
  content: "";
  width: 0.42rem;
  height: 0.42rem;
  flex-shrink: 0;
  border-right: 2px solid currentColor;
  border-bottom: 2px solid currentColor;
  transition: transform 0.15s ease;
}
summary::before { transform: rotate(-45deg); }
details[open] > summary::before { transform: rotate(45deg); }
summary::after { transform: rotate(45deg); }
details[open] > summary::after { transform: rotate(-135deg); }

/* ---------- surfaces ---------- */

.panel {
  background: linear-gradient(180deg, var(--panel-2), var(--panel));
  border: 1px solid var(--line);
  clip-path: polygon(
    0 0, calc(100% - var(--cut)) 0, 100% var(--cut),
    100% 100%, var(--cut) 100%, 0 calc(100% - var(--cut))
  );
  padding: 1.6rem;
}
.panel > h1, .panel > h2 { margin-bottom: 1rem; }
.panel-centered { text-align: center; display: grid; gap: 1rem; justify-items: center; }

/* Every page is the page itself plus the character sheet on the right. */
.layout { padding: 2rem 0 3rem; flex: 1; }
.layout-sheet {
  display: grid;
  grid-template-columns: minmax(0, 1fr) 21rem;
  gap: 1.6rem;
  align-items: start;
}
.content { min-width: 0; }

.footer {
  border-top: 1px solid var(--line);
  color: var(--muted);
  font-size: 0.78rem;
  padding: 1.2rem 0;
  text-align: center;
}

/* ---------- forms ---------- */

.stack { display: grid; gap: 0.9rem; }
label > span {
  display: block;
  font-size: 0.7rem;
  text-transform: uppercase;
  letter-spacing: 0.14em;
  color: var(--muted);
  margin-bottom: 0.35rem;
}

input[type="text"], input[type="password"], input[type="search"],
input[type="number"], input[type="file"], select {
  width: 100%;
  background: #0d0916;
  border: 1px solid var(--line-bright);
  color: var(--text);
  font: inherit;
  padding: 0.6rem 0.7rem;
}
input:focus, select:focus, button:focus-visible {
  outline: 2px solid var(--magenta);
  outline-offset: 1px;
}

.button {
  display: inline-block;
  background: transparent;
  border: 1px solid var(--line-bright);
  color: var(--text);
  font: inherit;
  font-weight: 600;
  text-transform: uppercase;
  letter-spacing: 0.1em;
  font-size: 0.8rem;
  padding: 0.62rem 1.2rem;
  cursor: pointer;
  clip-path: polygon(0 0, calc(100% - 9px) 0, 100% 9px, 100% 100%, 9px 100%, 0 calc(100% - 9px));
  transition: background 0.15s, border-color 0.15s;
}
.button:hover:not(:disabled) { border-color: var(--magenta); background: rgba(224, 25, 124, 0.12); }
.button-primary { background: var(--magenta); border-color: var(--magenta); color: #fff; }
.button-primary:hover:not(:disabled) { background: var(--magenta-soft); border-color: var(--magenta-soft); }
/* A button the rules have closed has to look closed. Without this a disabled
   button kept its full contrast and even lit up on hover, so it invited the
   click it then refused. The pager uses the class form for the same look. */
.button:disabled, .button.is-disabled { opacity: 0.4; cursor: not-allowed; }
.button:disabled:hover, .button.is-disabled:hover {
  border-color: var(--line-bright);
  background: transparent;
}
.button.is-disabled { cursor: default; }
.linkish:disabled { opacity: 0.4; cursor: not-allowed; }
.linkish:disabled:hover { color: var(--muted); }

.linkish {
  background: none;
  border: 0;
  color: var(--muted);
  font: inherit;
  font-size: 0.75rem;
  text-transform: uppercase;
  letter-spacing: 0.1em;
  cursor: pointer;
  padding: 0;
}
.linkish:hover { color: var(--text); }
.danger:hover { color: var(--red); }

/* ---------- notices ---------- */

.notice {
  border-left: 3px solid var(--muted);
  background: rgba(255, 255, 255, 0.04);
  padding: 0.7rem 0.9rem;
  margin-bottom: 1rem;
  font-size: 0.88rem;
}
.notice a { color: var(--cyan); text-decoration: underline; }
.notice-ok { border-left-color: var(--green); }
.notice-warn { border-left-color: var(--amber); }
.notice-error { border-left-color: var(--red); }

/* ---------- login ---------- */

.panel-login {
  max-width: 25rem;
  margin: 4rem auto;
  text-align: center;
}
.panel-login .stack { text-align: left; margin-top: 1.4rem; }
.panel-login .button { width: 100%; }
.login-mark {
  display: inline-block;
  background: var(--magenta);
  color: #fff;
  font-weight: 800;
  letter-spacing: 0.12em;
  padding: 0.4rem 0.7rem;
  clip-path: polygon(0 0, 100% 0, 100% 65%, calc(100% - 9px) 100%, 0 100%);
}
.login-title { margin-top: 0.9rem; letter-spacing: 0.2em; }
.login-sub, .login-hint { font-size: 0.83rem; }
.login-hint { margin-bottom: 0; margin-top: 1.2rem; }

/* ---------- catalogue ---------- */

.catalog { display: grid; grid-template-columns: 17rem 1fr; gap: 1.8rem; align-items: start; }

/* The category list is longer than the window, so it scrolls on its own:
   without a scroll container of its own the wheel falls through to the page
   and moves the results instead. overscroll-behavior keeps the page still
   once the list reaches its end. */
.sidebar {
  position: sticky;
  top: 5.2rem;
  display: grid;
  gap: 1.2rem;
  grid-template-rows: auto auto minmax(0, 1fr);
  max-height: calc(100vh - 7rem);
}
.sidebar .categories {
  overflow-y: auto;
  overscroll-behavior: contain;
  /* Room for the focus ring and the count badge against the scrollbar. */
  padding-right: 0.3rem;
}
.search { display: flex; gap: 0.5rem; }
.sidebar-title {
  font-size: 0.7rem;
  letter-spacing: 0.18em;
  color: var(--muted);
  border-bottom: 1px solid var(--line);
  padding-bottom: 0.5rem;
}
.categories { list-style: none; margin: 0; padding: 0; display: grid; gap: 1px; }
.category {
  display: flex;
  justify-content: space-between;
  gap: 0.6rem;
  padding: 0.45rem 0.6rem;
  font-size: 0.87rem;
  border-left: 2px solid transparent;
}
.category:hover { background: rgba(255, 255, 255, 0.04); }
.category.active {
  border-left-color: var(--magenta);
  background: rgba(224, 25, 124, 0.12);
  color: #fff;
}
.category .count { font-family: var(--mono); font-size: 0.78rem; color: var(--muted); }

.subcategories {
  list-style: none;
  margin: 0.15rem 0 0.45rem 0.6rem;
  padding: 0 0 0 0.5rem;
  border-left: 1px solid var(--line-bright);
  display: grid;
  gap: 1px;
}
.subcategory {
  display: flex;
  justify-content: space-between;
  gap: 0.6rem;
  padding: 0.3rem 0.5rem;
  font-size: 0.8rem;
  color: var(--muted);
}
.subcategory:hover { background: rgba(255, 255, 255, 0.04); color: var(--text); }
.subcategory.active { color: var(--magenta-soft); }
.subcategory .count { font-family: var(--mono); font-size: 0.72rem; }

.head-sub { color: var(--magenta-soft); }

.results-head { margin-bottom: 1.1rem; }
.results-head p { margin: 0.25rem 0 0; font-size: 0.85rem; }

.grid {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(15.5rem, 1fr));
  gap: 1rem;
}

.pager {
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 1rem;
  margin-top: 1.6rem;
}
.pager-state {
  font-size: 0.8rem;
  letter-spacing: 0.1em;
  text-transform: uppercase;
  color: var(--muted);
}

.card {
  display: flex;
  flex-direction: column;
  background: linear-gradient(180deg, var(--panel-2), var(--panel));
  border: 1px solid var(--line);
  overflow: hidden;
  clip-path: polygon(0 0, calc(100% - 11px) 0, 100% 11px, 100% 100%, 11px 100%, 0 calc(100% - 11px));
  transition: border-color 0.15s, transform 0.15s;
}
.card:hover { border-color: var(--magenta); transform: translateY(-2px); }

/* The body grows so every card in a row ends up the same height and the
   price line stays flush with the bottom edge. */
.card-body {
  display: flex;
  flex-direction: column;
  gap: 0.55rem;
  padding: 0.9rem 1rem 1rem;
  flex: 1;
}
.card-head { display: flex; justify-content: space-between; gap: 0.6rem; align-items: flex-start; }
.card-title {
  font-size: 0.95rem;
  /* Long book names must not push the badges out of the card. */
  overflow-wrap: anywhere;
  hyphens: auto;
}
.card-sub {
  margin: 0;
  font-size: 0.7rem;
  text-transform: uppercase;
  letter-spacing: 0.1em;
  color: var(--muted);
}
.card-text {
  margin: 0;
  font-size: 0.84rem;
  color: var(--muted);
  flex: 1;
}
.card-foot {
  display: flex;
  justify-content: space-between;
  align-items: baseline;
  gap: 0.5rem;
  border-top: 1px solid var(--line);
  padding-top: 0.6rem;
  margin-top: auto;
}
.card-cta {
  font-size: 0.7rem;
  text-transform: uppercase;
  letter-spacing: 0.12em;
  color: var(--magenta-soft);
}

/* ---------- item pictures ---------- */

/* The rulebook data has no artwork, so most items show the placeholder.
   Both variants keep the same box so the grid stays even either way. */
.item-image {
  display: block;
  width: 100%;
  aspect-ratio: 4 / 3;
  object-fit: cover;
  background: #0d0916;
  border-bottom: 1px solid var(--line);
}
.item-image-missing {
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: 0.5rem;
  background:
    repeating-linear-gradient(
      135deg,
      rgba(255, 255, 255, 0.022) 0 10px,
      transparent 10px 20px
    ),
    #0d0916;
  color: var(--muted);
}
.category-icon {
  width: 2.6rem;
  height: 2.6rem;
  color: var(--line-bright);
}
.item-image-note {
  font-size: 0.6rem;
  text-transform: uppercase;
  letter-spacing: 0.16em;
  color: #6d6087;
}

/* Inlined once per page purely as a symbol source. */
.icon-sprite { display: none; }

.detail-figure { margin: 0 0 1.4rem; }
.detail-figure .item-image {
  aspect-ratio: 16 / 9;
  border: 1px solid var(--line);
  object-fit: contain;
}

/* ---------- htmx ---------- */

/* Only a brief dim while the swap is in flight - no layout shift, and
   nothing at all on a fast local response. */
#catalog { transition: opacity 0.12s ease; }
#catalog.htmx-request { opacity: 0.55; }

/* ---------- badges and prices ---------- */

.badges { display: flex; gap: 0.3rem; flex-shrink: 0; }
.badge {
  font-family: var(--mono);
  font-size: 0.68rem;
  letter-spacing: 0.05em;
  padding: 0.1rem 0.38rem;
  border: 1px solid var(--line-bright);
  color: var(--muted);
  white-space: nowrap;
}
.badge-licence { border-color: var(--amber); color: var(--amber); }
.badge-illegal { border-color: var(--red); color: var(--red); }
/* The character that is in play. Cyan is what the app already uses for „this
   is your runner“ — the wallet and the balance carry it too. */
.badge-aktiv { border-color: var(--cyan); color: var(--cyan); }

.price { font-family: var(--mono); font-size: 1rem; color: var(--cyan); }
.price-unit { font-size: 0.72rem; color: var(--muted); }
.price-request { color: var(--muted); font-size: 0.88rem; }

/* ---------- item detail ---------- */

.detail { display: grid; grid-template-columns: 1fr 20rem; gap: 1.5rem; align-items: start; }
.back {
  display: inline-block;
  font-size: 0.72rem;
  text-transform: uppercase;
  letter-spacing: 0.12em;
  color: var(--muted);
  margin-bottom: 1rem;
}
.back:hover { color: var(--magenta-soft); }
.detail-head {
  display: flex;
  justify-content: space-between;
  gap: 1rem;
  align-items: flex-start;
  flex-wrap: wrap;
}
.detail-head h1 { overflow-wrap: anywhere; }
.detail-sub { font-size: 0.75rem; text-transform: uppercase; letter-spacing: 0.1em; margin: 0.3rem 0 1.2rem; }
.detail-text { margin: 0 0 1.4rem; }

.stats { width: 100%; border-collapse: collapse; font-size: 0.88rem; }
.stats th, .stats td { text-align: left; padding: 0.5rem 0.7rem; border-bottom: 1px solid var(--line); }
.stats th {
  width: 40%;
  font-weight: 600;
  font-size: 0.7rem;
  text-transform: uppercase;
  letter-spacing: 0.1em;
  color: var(--muted);
}
.stats td { font-family: var(--mono); color: var(--text); }

.detail-buy { position: sticky; top: 5.2rem; display: grid; gap: 1rem; }
.buy-price { display: flex; justify-content: space-between; align-items: baseline; }
.buy-price .muted { font-size: 0.7rem; text-transform: uppercase; letter-spacing: 0.14em; }
.detail-buy .button-primary { width: 100%; }
.buy-balance { margin: 0; font-size: 0.8rem; border-top: 1px solid var(--line); padding-top: 0.8rem; }

/* ---------- character sheet rail ---------- */

/* The sheet scrolls on its own so a long purchase list cannot push the rail
   past the bottom of the window. */
.sheet-rail {
  position: sticky;
  top: 5.2rem;
  max-height: calc(100vh - 7rem);
  overflow-y: auto;
  padding: 1.2rem 1.3rem 1.4rem;
}

.sheet-title, .sheet-subtitle {
  font-size: 0.7rem;
  letter-spacing: 0.18em;
  color: var(--muted);
  border-bottom: 1px solid var(--line);
  padding-bottom: 0.5rem;
}
.sheet-title { margin-bottom: 1rem; }
.sheet-subtitle { margin: 1.5rem 0 0.3rem; }

.sheet-id h3 { font-size: 1.15rem; overflow-wrap: anywhere; }
.sheet-id p { margin: 0.2rem 0 1.1rem; }

.ledger { display: grid; gap: 0.4rem; margin: 0; }
.ledger div { display: flex; justify-content: space-between; align-items: baseline; gap: 1rem; }
.ledger dt {
  font-size: 0.65rem;
  text-transform: uppercase;
  letter-spacing: 0.14em;
  color: var(--muted);
}
.ledger dd { margin: 0; font-family: var(--mono); font-size: 0.95rem; }
.ledger-total { border-top: 1px solid var(--line); padding-top: 0.5rem; margin-top: 0.2rem; }
.ledger-total dd { color: var(--cyan); font-size: 1.2rem; }
.ledger-total dd.over { color: var(--red); }
.sheet-rail .notice { margin: 1rem 0 0; }

.sheet-identities { list-style: none; margin: 0; padding: 0; }
.sheet-identities > li {
  padding: 0.55rem 0;
  border-bottom: 1px solid var(--line);
  font-size: 0.85rem;
}
.sheet-identities p { margin: 0.15rem 0 0; }
.identity-name { color: var(--cyan); }
.identity-licences { list-style: none; margin: 0.15rem 0 0; padding: 0 0 0 0.9rem; }
.identity-licences li {
  color: var(--muted);
  font-size: 0.8rem;
  overflow-wrap: anywhere;
}

.sheet-purchases { list-style: none; margin: 0; padding: 0; }
.sheet-purchases li {
  display: grid;
  grid-template-columns: minmax(0, 1fr) auto;
  gap: 0.1rem 0.6rem;
  padding: 0.55rem 0;
  border-bottom: 1px solid var(--line);
  font-size: 0.85rem;
}
.purchase-name { overflow-wrap: anywhere; }
.purchase-name:hover { color: var(--magenta-soft); }
.purchase-sum { font-family: var(--mono); font-size: 0.82rem; color: var(--cyan); }
.purchase-warn {
  grid-column: 1 / -1;
  margin: 0.2rem 0 0;
  font-size: 0.72rem;
  color: var(--amber);
}
.sheet-purchases form { grid-column: 2; justify-self: end; }
.sheet-purchases .linkish { font-size: 0.65rem; }

.sheet-finish { margin-top: 1rem; }
.sheet-finish .button { width: 100%; text-align: center; }
.sheet-finish p { margin: 0.5rem 0 0; }
.sheet-hint { margin-top: 0.6rem; }

.ansicht-aktionen { display: flex; gap: 0.6rem; margin-top: 1.2rem; }

.sheet-upload { border-top: 1px solid var(--line); margin-top: 1.4rem; padding-top: 1rem; }
.sheet-upload summary {
  display: flex;
  align-items: center;
  gap: 0.5rem;
  font-size: 0.7rem;
  text-transform: uppercase;
  letter-spacing: 0.14em;
  color: var(--muted);
}
.sheet-upload summary:hover { color: var(--text); }
.sheet-upload .stack { margin-top: 0.9rem; }
.sheet-upload .button { width: 100%; }
.sheet-upload p { margin: 0; }

/* ---------- narrow screens ---------- */

/* Below this width the sheet cannot keep its own column, so it moves under
   the page; the balance in the top bar keeps it in reach while browsing. */
@media (max-width: 75rem) {
  .layout-sheet { grid-template-columns: 1fr; }
  .sheet-rail { position: static; max-height: none; overflow-y: visible; }
}

@media (max-width: 60rem) {
  .shell { width: min(1440px, 100% - 1.6rem); }
  .catalog, .detail { grid-template-columns: 1fr; }
  .sidebar, .detail-buy { position: static; }
  /* Stacked above the results, the sidebar has the whole page to grow into,
     so it must not trap the wheel in a short box of its own. */
  .sidebar { max-height: none; }
  .sidebar .categories { overflow-y: visible; }
  .topbar-inner { flex-wrap: wrap; gap: 0.8rem 1.2rem; }
  .nav { order: 3; width: 100%; margin-right: 0; }
}

.detail-figure .category-icon { width: 4rem; height: 4rem; }

/* ---------- character generator ---------- */

/* The generator owns the full width: wizard left, live sheet right. */
.layout-generator { grid-template-columns: minmax(0, 1fr); }
.generator {
  display: grid;
  grid-template-columns: minmax(0, 1fr) minmax(340px, 460px);
  gap: 1.5rem;
  align-items: start;
}
@media (max-width: 1180px) {
  .generator { grid-template-columns: minmax(0, 1fr); }
}
.generator-arbeit { display: flex; flex-direction: column; gap: 1.25rem; min-width: 0; }

.schritte {
  display: flex;
  flex-wrap: wrap;
  gap: 0.4rem;
  border-bottom: 1px solid var(--line);
  padding-bottom: 0.75rem;
}
.schritt {
  display: flex;
  align-items: center;
  gap: 0.5rem;
  padding: 0.45rem 0.8rem;
  border: 1px solid var(--line);
  background: var(--panel);
  clip-path: polygon(var(--cut) 0, 100% 0, 100% calc(100% - var(--cut)), calc(100% - var(--cut)) 100%, 0 100%, 0 var(--cut));
  font-size: 0.85rem;
  letter-spacing: 0.04em;
  text-transform: uppercase;
}
.schritt:hover { border-color: var(--line-bright); }
.schritt.active { background: var(--magenta); border-color: var(--magenta); color: #fff; }
.schritt-nummer { font-family: var(--mono); opacity: 0.65; }

.schritt-kopf { margin-bottom: 1rem; }
.schritt-inhalt h2 {
  margin: 1.4rem 0 0.6rem;
  padding-top: 0.9rem;
  border-top: 1px solid var(--line);
  color: var(--cyan);
}
.schritt-inhalt h3 { margin: 0.9rem 0 0.5rem; }
.zaehler { color: var(--muted); font-weight: 400; letter-spacing: 0.02em; }

/* -- options ------------------------------------------------------------- */

/* Choices are peers, so they are the same size: grid stretches every card in
   a row to the height of the tallest one. Sized by their own text they were a
   comb of different lengths with holes between them, which read as broken
   rather than as a set to pick from.

   The magic list, whose cards carry a fixed amount of text, equalises across
   the whole grid (1fr rows). Everywhere a description folds open — the wizard's
   choice groups and the module catalogue — the grid equalises per row only:
   one opened card would otherwise stretch *every* row to its height, leaving a
   screen of holes below the one card that was read. */
.optionen, .modulkatalog, .magieliste {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(210px, 1fr));
  gap: 0.5rem;
}
.magieliste { grid-auto-rows: 1fr; }

/* The two groups whose choices carry a whole book description — metatype and
   magic type. Wide columns keep the quote a paragraph instead of a narrow
   tower; at the default width one option ran to fourteen lines next to a
   two-line one. */
.optionen-breit { grid-template-columns: repeat(auto-fill, minmax(21rem, 1fr)); }

/* Wider columns than the other catalogues: a module card carries the book's
   whole effect list, and at 260px every line of it wrapped three times, which
   turned six bullets into a tower of eighteen. */
.modulkatalog {
  grid-template-columns: repeat(auto-fill, minmax(21rem, 1fr));
  max-height: 44rem;
  overflow-y: auto;
}
/* Every card the same height, across the whole catalogue and not just per row:
   the descriptions differ by several lines, and sized by their own text the
   cards were a comb. The description box takes up the slack (see below), so
   the name and both bars land on the same line on every card. */
.modulkatalog { grid-auto-rows: 1fr; }
/* The button keeps its natural height — stretching it instead would open a gap
   between the name and the bars underneath. */
.modulkatalog .option-karte .option { flex: none; }
.modulkatalog .option-karte > .option-regel:last-child {
  flex: 1;
  display: flex;
  flex-direction: column;
}
.modulkatalog .option-karte > .option-regel:last-child > .option-text { flex: 1; }
/* The card is the button plus its folded description. The button stretches to
   the card's full height so that a row of cards still ends on one line. */
/* Descendant and not child: the picking form sits between card and button as
   a display:contents box, which flattens the *layout* but not the selector. */
.option-karte { display: flex; flex-direction: column; min-width: 0; }
.option-karte .option { flex: 1; }
/* Button and fold are two boxes but one card, so the cut corner belongs to
   the card: the button keeps the top one and hands the bottom one down. */
.option-karte:has(.option-regel) .option {
  clip-path: polygon(var(--cut) 0, 100% 0, 100% 100%, 0 100%, 0 var(--cut));
}
.option-form { margin: 0; display: contents; }
.option {
  display: flex;
  flex-direction: column;
  gap: 0.2rem;
  width: 100%;
  text-align: left;
  padding: 0.6rem 0.7rem;
  border: 1px solid var(--line);
  background: var(--panel-2);
  color: var(--text);
  cursor: pointer;
  font: inherit;
  clip-path: polygon(var(--cut) 0, 100% 0, 100% calc(100% - var(--cut)), calc(100% - var(--cut)) 100%, 0 100%, 0 var(--cut));
}
.option:hover:not(:disabled) { border-color: var(--magenta-soft); background: #221635; }
.option:focus-visible { outline: 2px solid var(--cyan); outline-offset: 2px; }
.option.aktiv { border-color: var(--magenta); background: rgba(224, 25, 124, 0.18); }
.option.gesperrt, .option:disabled { opacity: 0.45; cursor: not-allowed; }
.option-name { font-weight: 700; letter-spacing: 0.03em; }
.option-zusatz { font-family: var(--mono); font-size: 0.78rem; color: var(--cyan); }
.option-text { font-size: 0.8rem; color: var(--muted); line-height: 1.45; }
.option-sperre { font-size: 0.75rem; color: var(--red); }
/* How to undo a pick, on the picked card — the only place it can be looked
   for once picking and unpicking are the same click. */
.option-notiz { font-size: 0.72rem; color: var(--muted); }

/* What a life module grants, in the book's own words (see _schritt_module),
   behind the bar that names the kind of module. It is read while comparing
   cards, so it is set small and tight, and the label the book repeats on every
   line ("Wähle eine Option:") is greyed out rather than dropped: it belongs to
   the sentence, it just is not what tells two modules apart. */
.wirkung {
  list-style: none;
  display: flex;
  flex-direction: column;
  gap: 0.22rem;
  margin: 0;
  padding: 0.1rem 0.7rem 0.6rem;
  font-size: 0.76rem;
  line-height: 1.4;
}
.wirkung-zeile {
  position: relative;
  padding-left: 0.7rem;
  color: var(--text);
}
.wirkung-label { color: var(--muted); }
.wirkung-zeile::before {
  content: "";
  position: absolute;
  left: 0;
  top: 0.45em;
  width: 0.22rem;
  height: 0.22rem;
  background: var(--cyan);
}
/* The sentence some entries carry after their list — a rule of the module, but
   not one of its bullets, so it is not dressed up as one. */
.wirkung-zusatz {
  margin: 0;
  padding: 0 0.7rem 0.6rem 1.4rem;
  font-size: 0.76rem;
  line-height: 1.4;
  color: var(--muted);
  font-style: italic;
}

/* The description of a single choice, folded like every other rule in the
   generator (see .buchregel). Quieter than a rule bar: it hangs under its
   card and there is one of them per choice, so it is a thin strip rather
   than a second panel. */
.option-regel {
  border: 1px solid var(--line);
  border-top: 0;
  background: var(--panel);
  font-size: 0.8rem;
  clip-path: polygon(
    0 0, 100% 0, 100% calc(100% - var(--cut)), calc(100% - var(--cut)) 100%, 0 100%
  );
}
/* A picked choice keeps its outline all the way round the card. */
.option-karte:has(.option.aktiv) .option-regel { border-color: var(--magenta); }
/* A module card carries two folds under its button — what the module does and
   what it reads like. Only the lower one ends the card, so only it gets the
   cut corner; the one above hands its bottom edge on as a plain line. */
.option-karte > .option-regel:not(:last-child) {
  clip-path: none;
  border-bottom: 0;
}
.option-regel-kopf {
  display: flex;
  align-items: center;
  gap: 0.4rem;
  padding: 0.3rem 0.7rem;
  user-select: none;
}
.option-regel-kopf::before { color: var(--cyan); }
.option-regel-kopf:hover { background: rgba(47, 227, 213, 0.08); }
.option-regel-kopf:hover .option-regel-schalter { color: var(--text); }
.option-regel-kopf:focus-visible { outline: 2px solid var(--cyan); outline-offset: -2px; }
.option-regel-marke {
  font-size: 0.68rem;
  font-weight: 700;
  text-transform: uppercase;
  letter-spacing: 0.12em;
  color: var(--cyan);
  margin-right: auto;
}
/* Which kind of module it is, in the bar that opens its effects — the only
   place that still says it, so the two kinds are told apart by colour and not
   by reading the word. Must stand after .option-regel-marke: same specificity,
   so order alone decides. */
.marke-ereignis { color: var(--magenta-soft); }
.option-regel-schalter {
  font-size: 0.68rem;
  text-transform: uppercase;
  letter-spacing: 0.1em;
  color: var(--muted);
  white-space: nowrap;
  transition: color 0.15s;
}
.option-regel:not([open]) .nur-auf, .option-regel[open] .nur-zu { display: none; }
.option-regel .option-text { display: block; margin: 0; padding: 0 0.7rem 0.6rem; }
/* Pushed to the bottom edge: with cards of one height the page numbers of a
   row then sit on one line instead of wherever the text happened to end. */
.option-quelle {
  margin-top: auto;
  padding-top: 0.2rem;
  font-style: normal;
  font-size: 0.7rem;
  letter-spacing: 0.04em;
  color: var(--muted);
}
.option-tipp { font-size: 0.75rem; color: var(--amber); }

/* -- forms --------------------------------------------------------------- */

.feldreihe, .qualitaetsreihe {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(240px, 1fr));
  gap: 0.9rem;
}
.textfeld, .qualitaetsslot { display: flex; flex-direction: column; gap: 0.3rem; }
.textfeld label { display: flex; flex-direction: column; gap: 0.3rem; }
.textfeld label > span, .qualitaetsslot label > span {
  font-size: 0.78rem;
  text-transform: uppercase;
  letter-spacing: 0.06em;
  color: var(--muted);
}
.qualitaet-form, .connection-form, .wissen-form, .wildcard {
  display: flex;
  flex-wrap: wrap;
  gap: 0.6rem;
  align-items: flex-end;
  margin: 0;
}
.qualitaet-form label, .connection-form label, .wissen-form label, .wildcard label {
  display: flex; flex-direction: column; gap: 0.25rem;
}
.qualitaet-form .klein input { width: 5.5rem; }
.wachsend { flex: 1 1 12rem; }

/* -- rules from the book --------------------------------------------------

   The rule as the book words it. Quoted, so it reads as the book talking and
   not as the tool inventing a rule of its own — and therefore long: several
   quotes above one row of choices pushed the choices off the screen.

   So it is folded away, and the bar that is left has to do two things at
   once: say that there is a rule here and that clicking opens it. Hence the
   label, the page it stands on, the chevron and the word "anzeigen" — the
   whole bar is the control, and it looks like one. */
.buchregel {
  margin: -0.2rem 0 1rem;
  border: 1px solid var(--line);
  border-left: 3px solid var(--cyan);
  background: var(--panel-2);
  font-size: 0.86rem;
  line-height: 1.55;
  color: var(--text);
}

.buchregel-kopf {
  display: flex;
  align-items: center;
  /* On a phone the bar is wider than the screen. It wraps rather than
     squeezing "Regel aus dem Buch" into a one-word column, so the label and
     the word that says it opens stay legible. */
  flex-wrap: wrap;
  gap: 0.15rem 0.55rem;
  padding: 0.5rem 0.8rem;
  user-select: none;
}
/* Cyan like the accent line and the label, so the bar reads as one control. */
.buchregel-kopf::before { color: var(--cyan); }
.buchregel-kopf:hover { background: rgba(47, 227, 213, 0.08); }
.buchregel-kopf:hover .buchregel-schalter { color: var(--text); }
.buchregel-kopf:focus-visible { outline: 2px solid var(--cyan); outline-offset: -2px; }

.buchregel-marke {
  font-size: 0.7rem;
  font-weight: 700;
  text-transform: uppercase;
  letter-spacing: 0.12em;
  color: var(--cyan);
  white-space: nowrap;
}
.buchregel-titel { font-size: 0.82rem; }
/* The page stays readable while the box is shut: which rule this is answers
   most questions without opening anything. */
.buchregel-quelle {
  font-style: normal;
  font-size: 0.75rem;
  letter-spacing: 0.04em;
  color: var(--muted);
  margin-right: auto;
}
.buchregel-schalter {
  font-size: 0.7rem;
  text-transform: uppercase;
  letter-spacing: 0.1em;
  color: var(--muted);
  white-space: nowrap;
  transition: color 0.15s;
}
.buchregel:not([open]) .nur-auf, .buchregel[open] .nur-zu { display: none; }

.buchregel-text { padding: 0.1rem 0.9rem 0.8rem; }
.buchregel-text p { margin: 0 0 0.5rem; }
.buchregel-text cite {
  display: block;
  margin-top: 0.15rem;
  font-style: normal;
  font-size: 0.75rem;
  letter-spacing: 0.04em;
  color: var(--muted);
}
/* The second quote in the box: the limits that hold across the whole life
   path, not just this step — a line and a shade of grey apart from it. */
.buchregel-grenzen {
  color: var(--muted);
  border-top: 1px solid var(--line);
  margin-top: 0.7rem;
  padding-top: 0.7rem;
}

/* How much room is left is not something to look up, so it stays outside the
   fold — but visibly part of the same box. */
.regelblock { margin-bottom: 1rem; }
.regelblock .buchregel { margin-bottom: 0; border-bottom: 0; }
.regel-zahlen {
  display: flex;
  flex-wrap: wrap;
  gap: 0.4rem 1.2rem;
  margin: 0;
  padding: 0.45rem 0.8rem;
  border: 1px solid var(--line);
  border-left: 3px solid var(--cyan);
  background: var(--panel-2);
  font-family: var(--mono);
  font-size: 0.78rem;
  color: var(--cyan);
}
.regel-zahlen .ueber { color: var(--red); }
/* What this step still owes. Amber while it is open — this is not an error,
   it is the next thing to do; the problem list turns it red once the step is
   left behind. */
.regel-zahlen .pflicht {
  padding: 0 0.45rem;
  border: 1px solid currentColor;
  color: var(--muted);
}
.regel-zahlen .pflicht.offen { color: var(--amber); }
.regel-zahlen .pflicht.erfuellt { color: var(--green); }

/* -- quality slots -------------------------------------------------------- */

.slot-kopf { display: flex; align-items: baseline; justify-content: space-between; gap: 1rem; }
.slot-kopf h3 { margin: 0; }
.slot-kopf form { margin: 0; }
/* What is picked, next to the name of the slot: the catalogue scrolls, and
   a slot whose pick had scrolled out of sight otherwise looked empty. */
.slot-wahl { font-size: 0.85rem; text-align: right; color: var(--muted); }

/* The fine-tuning of a picked quality, inside its own card. */
.option-karte > .qualitaet-form {
  border: 1px solid var(--magenta);
  border-top: 0;
  background: rgba(224, 25, 124, 0.1);
  padding: 0.5rem 0.7rem 0.6rem;
}
.option-karte > .qualitaet-form .fehlt { border-color: var(--red); }

/* The catalogue can be long; it scrolls inside the slot instead of pushing
   the rest of the step off the screen.

   22rem is its own height, but it grows into whatever the slot beside it is
   taller by: a picked benefit adds a panel above the left catalogue, and
   without the growth the flaw column simply ended halfway up, leaving a hole
   the size of that panel next to a scrollbar that had more to show. */
.qualitaetskatalog {
  flex: 1 1 22rem;
  min-height: 22rem;
  overflow-y: auto;
  padding-right: 0.2rem;
}
/* Sticky, because the two groups are long and the heading is the only thing
   that says whether these are benefits or flaws. */
.katalog-gruppe {
  position: sticky;
  top: 0;
  z-index: 1;
  margin: 0.6rem 0 0.4rem;
  padding: 0.25rem 0;
  background: var(--panel);
  font-size: 0.78rem;
  text-transform: uppercase;
  letter-spacing: 0.08em;
  color: var(--cyan);
}
.katalog-gruppe:first-child { margin-top: 0; }
/* Sixty qualities, one under the other: a single column, because the name and
   the karma price are what you scan here. */
.qualitaetskatalog .optionen { grid-template-columns: 1fr; }

/* A "?" that explains a field the book's cost rule made necessary. */
.hilfe {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 1.05em;
  height: 1.05em;
  margin-left: 0.25em;
  border: 1px solid var(--line-bright);
  border-radius: 50%;
  font-size: 0.9em;
  text-decoration: none;
  cursor: help;
  color: var(--cyan);
}

input[type="text"], input[type="number"], select {
  background: var(--bg);
  border: 1px solid var(--line);
  color: var(--text);
  padding: 0.45rem 0.6rem;
  font: inherit;
  width: 100%;
}
input:focus, select:focus { outline: none; border-color: var(--cyan); }

/* -- folded section ------------------------------------------------------ */

/* A whole section of a step, folded by its own heading — for the sections that
   outgrow a screen and bury what comes after them. The bar stays the heading
   it replaces (same rule as .schritt-inhalt h2), with the chevron and the word
   that says it opens added, so it reads as a heading you can click and not as
   a new kind of control. */
.klappe { margin: 1.4rem 0 0.6rem; }
.klappe-kopf {
  display: flex;
  align-items: center;
  gap: 0.5rem;
  padding-top: 0.9rem;
  border-top: 1px solid var(--line);
  color: var(--cyan);
  user-select: none;
}
/* The heading's own frame moves to the bar around it. */
.klappe-kopf > h2 { margin: 0; padding: 0; border-top: 0; }
.klappe-kopf:hover { color: var(--magenta-soft); }
.klappe-kopf:focus-visible { outline: 2px solid var(--cyan); outline-offset: 2px; }
/* Folded, the count is all that is left of the list — so it has to keep
   answering how many modules there are, and show that adding one did. */
.klappe-zahl { font-family: var(--mono); font-size: 0.8rem; color: var(--muted); }
.klappe-schalter {
  margin-left: auto;
  font-size: 0.7rem;
  text-transform: uppercase;
  letter-spacing: 0.1em;
  color: var(--muted);
  white-space: nowrap;
}
.klappe-kopf:hover .klappe-schalter { color: var(--text); }
.klappe:not([open]) .nur-auf, .klappe[open] .nur-zu { display: none; }
.klappe-inhalt { margin-top: 0.8rem; }

/* -- modules ------------------------------------------------------------- */

.modulliste { list-style: none; margin: 0; padding: 0; display: flex; flex-direction: column; gap: 0.5rem; }
.modul { border: 1px solid var(--line); background: var(--panel-2); }
.modul.offen { border-color: var(--magenta); }
.modul-kopf { display: flex; justify-content: space-between; align-items: center; gap: 1rem; padding: 0.6rem 0.8rem; }
/* The row is a fold like every other one, so it says so the same way — but it
   is a link and not a <summary> (the detail is rendered by the server, one at
   a time), so the shared chevron rule cannot reach it and it gets its own. */
.modul-name { display: flex; align-items: center; gap: 0.55rem; flex: 1; }
.modul-titel { display: flex; flex-direction: column; gap: 0.15rem; flex: 1; }
.modul-name::before {
  content: "";
  width: 0.42rem;
  height: 0.42rem;
  flex-shrink: 0;
  border-right: 2px solid currentColor;
  border-bottom: 2px solid currentColor;
  transform: rotate(-45deg);
  transition: transform 0.15s ease;
  color: var(--magenta-soft);
}
.modul.offen .modul-name::before { transform: rotate(45deg); }
/* Magenta while the module still wants a decision, cyan once every one of them
   is made — the same cyan as the heading above the list, so a glance down the
   folded list says what is left to do. */
.modul.vollstaendig .modul-name::before { color: var(--cyan); }
.modul-schalter {
  font-size: 0.7rem;
  text-transform: uppercase;
  letter-spacing: 0.1em;
  color: var(--muted);
  white-space: nowrap;
  transition: color 0.15s;
}
.modul-name:hover .modul-schalter { color: var(--text); }
.modul-detail { padding: 0 0.8rem 0.9rem; border-top: 1px solid var(--line); }
.modul-text { font-size: 0.86rem; line-height: 1.5; color: var(--muted); }
.auswahlgruppe { margin-top: 1rem; }
.aktionsliste { list-style: none; margin: 0.4rem 0; padding: 0; display: flex; flex-direction: column; gap: 0.25rem; font-size: 0.85rem; }
.aktionsliste li { display: flex; gap: 0.6rem; align-items: center; }
.buchungsliste { list-style: none; margin: 0.4rem 0 0.8rem; padding: 0; display: flex; flex-direction: column; gap: 0.3rem; font-size: 0.85rem; }
.buchungsliste li {
  display: flex; gap: 0.8rem; align-items: baseline;
  padding: 0.4rem 0.6rem; border: 1px solid var(--line);
  background: var(--panel-2);
}
.buchung-name { flex: 1; }
.buchung-karma { font-family: var(--mono); font-size: 0.78rem; color: var(--cyan); }
.steigerungen { display: flex; flex-direction: column; gap: 0.4rem; margin-top: 0.6rem; }
.steigerung { display: flex; gap: 0.6rem; align-items: center; flex-wrap: wrap; }
.steigerung-name { flex: 1 1 14rem; display: flex; flex-direction: column; }

/* -- messages ------------------------------------------------------------ */

.meldungen-kopf { display: flex; justify-content: space-between; align-items: baseline; gap: 1rem; }
.karma-stand { font-family: var(--mono); color: var(--cyan); }
.meldung { margin: 0.35rem 0; padding-left: 0.8rem; border-left: 3px solid; font-size: 0.87rem; }
.meldung.problem { border-color: var(--red); color: #ffd4dc; }
.meldung.hinweis { border-color: var(--amber); color: #ffe6bd; }
.meldung.ok { border-color: var(--green); color: #c8f5e0; }

/* -- live sheet preview -------------------------------------------------- */

.vorschau {
  position: sticky;
  top: 4.5rem;
  border: 1px solid var(--line);
  background: var(--panel);
  padding: 0.9rem;
  max-height: calc(100vh - 6rem);
  overflow-y: auto;
}
.vorschau-kopf h2 { color: var(--cyan); }
.vorschau-legende { display: flex; gap: 0.8rem; margin: 0.5rem 0 0; font-size: 0.75rem; }
.legende { display: flex; align-items: center; gap: 0.3rem; color: var(--muted); }
.legende::before { content: ""; width: 0.7rem; height: 0.7rem; border: 1px solid; }
.legende.besser::before { background: rgba(63, 209, 139, 0.35); border-color: var(--green); }
.legende.schlechter::before { background: rgba(255, 77, 109, 0.35); border-color: var(--red); }
.legende.ohne::before { background: rgba(255, 176, 32, 0.35); border-color: var(--amber); }

.vorschau-seiten { display: flex; flex-direction: column; gap: 1rem; margin-top: 0.8rem; }
.bogenseite { position: relative; margin: 0; line-height: 0; }
.bogenseite img { width: 100%; height: auto; display: block; }

/* One box per filled field, positioned in page percentages so the overlay
   follows the image at whatever width the rail happens to be. */
.bogenfeld {
  position: absolute;
  display: flex;
  align-items: center;
  overflow: hidden;
  line-height: 1;
  transition: background-color 120ms ease, box-shadow 120ms ease;
}
.bogenfeld-wert {
  font-size: clamp(4px, 0.62vw, 9px);
  font-family: var(--sans);
  color: #10131a;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
  padding: 0 1px;
}
.bogenfeld.diff-besser { background: rgba(63, 209, 139, 0.45); box-shadow: 0 0 0 1px var(--green); }
.bogenfeld.diff-schlechter { background: rgba(255, 77, 109, 0.45); box-shadow: 0 0 0 1px var(--red); }
.bogenfeld.diff-ohne { background: rgba(255, 176, 32, 0.45); box-shadow: 0 0 0 1px var(--amber); }

/* -- overview and switcher ------------------------------------------------ */

.pfadliste { list-style: none; margin: 0; padding: 0; display: flex; flex-direction: column; gap: 0.5rem; }
.pfad { display: flex; align-items: center; gap: 1rem; flex-wrap: wrap; padding: 0.7rem 0; border-bottom: 1px solid var(--line); }
.pfad-name { font-weight: 700; letter-spacing: 0.04em; flex: 1 1 12rem; }
.pfad-aktionen { display: flex; gap: 0.5rem; align-items: center; }

.switcher { position: relative; }
/* Without the chevron the character's name in the top bar looks like a label,
   and nobody finds the other characters behind it. */
.switcher summary { display: flex; align-items: center; justify-content: flex-end; gap: 0.4rem; }
.switcher summary:hover { color: var(--magenta-soft); }
.switcher-list {
  position: absolute;
  right: 0;
  z-index: 20;
  list-style: none;
  margin: 0.4rem 0 0;
  padding: 0.3rem;
  min-width: 14rem;
  background: var(--panel-2);
  border: 1px solid var(--line-bright);
}
.switcher-list button { width: 100%; text-align: left; padding: 0.35rem 0.5rem; display: flex; justify-content: space-between; gap: 0.6rem; }
.switcher-list button.active { color: var(--magenta-soft); }

.abschluss-uebersicht { margin-bottom: 1rem; }
.abschluss-aktionen { display: flex; gap: 0.7rem; align-items: center; flex-wrap: wrap; margin-top: 1rem; }
