/* ============================================================
   style-play-sheet.css
   Split from static/css/style.css on 2026-08-01 for token-cost
   reasons (7057-line file forced multi-chunk reads on every touch).
   Sections 25-26: play sheet (desktop), mobile character vault
   Load order matters - see templates/base.html <link> order.
   ============================================================ */


/* ══════════════════════════════════════════════════════════
   25. PLAY SHEET (desktop)
   ══════════════════════════════════════════════════════════ */
body.play-page {
  overflow: hidden;
}

/* The banner for a save the server refused, or data that could not be confirmed. It sits UNDER the
   52px top bar (it used to cover it, with the menu and the way out), above the overlay drawers (45)
   and under the user menu (50). Neutral surface with a red rule: readable in light and dark. */
.locked-save-conflict-banner {
  position: fixed;
  top: 52px;
  left: 0;
  right: 0;
  z-index: 46;
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  justify-content: center;
  gap: 8px 14px;
  box-sizing: border-box;
  padding: 8px 16px;
  border-bottom: 3px solid var(--danger);
  background: var(--surface2);
  color: var(--text);
  box-shadow: 0 8px 20px rgba(0, 0, 0, .35);
  font-size: 13px;
  font-weight: 800;
  line-height: 1.35;
  text-align: center;
}

.locked-save-conflict-reload {
  min-height: 44px;
  padding: 6px 18px;
  border: 1px solid var(--accent);
  border-radius: 999px;
  background: var(--accent);
  color: var(--on-accent);
  font: inherit;
  font-weight: 800;
  cursor: pointer;
}

.locked-save-conflict-reload:focus-visible { outline: 3px solid var(--text); outline-offset: 2px; }

main.play-page-main {
  width: 100vw;
  max-width: none;
  height: calc(100vh - 52px);
  height: calc(100dvh - 52px);
  margin: 0;
  padding: 0;
  overflow: hidden;
  /* A column, so the DM's "Viewing ..." banner (it is inside <main>) takes its own height and the sheet gets
     what is left. Before, the sheet was 100% of <main> on top of the banner and ran off the bottom. */
  display: flex;
  flex-direction: column;
}
main.play-page-main > .dm-view-bar { flex: none; }
main.play-page-main > #play-sheet { flex: 1 1 0; min-height: 0; height: auto; }

.play-loading { text-align: center; padding: 4rem; color: var(--text-muted); }

#play-sheet {
  height: 100%;
}

.play-layout-wrap {
  position: relative;
  width: 100%;
  height: 100%;
  overflow: hidden;
  overflow: clip; /* clip, not hidden: hidden makes this the scroll container and breaks the sticky category bar */
  background: var(--bg);
}

/* --- Locked live play sheet --- */
.locked-play-sheet {
  position: absolute;
  width: 1437px;
  height: 734px;
  transform-origin: top left;
  overflow: hidden;
  color: var(--text);
  font-family: var(--font);
  background:
    radial-gradient(circle at 50% 8%, var(--bg-glow) 0, var(--surface) 42%, var(--bg) 100%);
}

.mobile-play-sheet {
  display: none;
}

.mobile-sheet {
  display: none;
}

/* The sheet scales to fit (resizePlaySheet) from the phone breakpoint (900px) up, so a tablet never scrolls
   sideways: at 900px the canvas is drawn at 0.63 of its size, at 1024px at 0.71. It used to hold the page at
   1100px wide below that, which left a 900-1099px window with a horizontal scrollbar. The height floor stays:
   under 566px tall the page scrolls up and down instead of shrinking further. */
@media (max-height: 565px) {
  main.play-page-main {
    min-height: 566px;
  }
}

/* ══════════════════════════════════════════════════════════
   26. MOBILE CHARACTER VAULT
   ══════════════════════════════════════════════════════════ */
@media (max-width: 720px) {
  body {
    overflow-x: hidden;
  }

  .navbar {
    height: auto;
    min-height: 56px;
    align-items: stretch;
    flex-direction: column;
    gap: 8px;
    padding: 10px max(12px, env(safe-area-inset-left)) 10px max(12px, env(safe-area-inset-right));
  }

  .brand {
    display: block;
    max-width: 100%;
    overflow: hidden;
    font-size: 20px;
    text-overflow: ellipsis;
    white-space: nowrap;
  }

  .nav-right {
    display: flex;
    gap: 8px;
    overflow-x: auto;
    padding-bottom: 2px;
    -webkit-overflow-scrolling: touch;
  }

  .nav-right > * {
    flex: 0 0 auto;
  }

  .nav-link,
  .nav-username {
    display: inline-flex;
    min-height: 38px;
    align-items: center;
    padding: 0 10px;
    border: 1px solid var(--border);
    border-radius: 999px;
    background: var(--surface2);
  }

  main {
    width: 100%;
    padding: 14px max(12px, env(safe-area-inset-left)) 18px max(12px, env(safe-area-inset-right));
  }

  .login-container,
  .admin-container {
    width: 100%;
    max-width: 100%;
  }

  .login-form,
  .admin-section form {
    align-items: stretch;
    flex-direction: column;
  }

  .login-form .btn,
  .admin-section form .btn {
    justify-content: center;
    width: 100%;
  }

  .char-vault-shell {
    gap: 12px;
  }

  .char-vault-header {
    padding: 14px 16px;
    border-radius: 18px;
  }

  .char-vault-header h1 {
    font-size: 34px;
  }

  .char-vault-kicker {
    margin-bottom: 2px;
  }

  .char-vault-count {
    margin-top: 5px;
  }

  .char-vault-header-actions .btn {
    min-height: 44px;
    border-radius: 15px;
  }

  .char-vault-layout {
    display: flex;
    flex-direction: column;
    gap: 12px;
  }

  .char-vault-sidebar {
    display: none;
  }

  .char-vault-box {
    padding: 12px;
  }

  .char-vault-filter-list {
    flex-direction: row;
    overflow-x: auto;
    padding-bottom: 4px;
    -webkit-overflow-scrolling: touch;
  }

  .char-vault-filter-btn {
    flex: 0 0 auto;
    width: auto;
    min-height: 40px;
    white-space: nowrap;
  }

  .char-vault-box-actions {
    display: grid;
    grid-template-columns: repeat(3, 1fr);
  }

  .char-vault-main {
    width: 100%;
    padding: 0;
    border: 0;
    border-radius: 0;
    background: transparent;
  }

  .char-vault-toolbar {
    display: grid;
    grid-template-columns: 1fr 46px;
    gap: 10px;
    position: sticky;
    top: 0;
    z-index: 15;
    margin: 0 0 12px;
    padding: 10px 0 12px;
    background: linear-gradient(180deg, color-mix(in srgb, var(--bg) 98%, transparent), color-mix(in srgb, var(--bg) 88%, transparent) 80%, transparent);
  }

  .char-vault-search {
    grid-column: 1;
  }

  .char-vault-search span,
  .char-vault-sort span {
    display: none;
  }

  .char-vault-search input {
    min-height: 48px;
    border-radius: 16px;
    font-size: 16px;
  }

  .char-vault-sort {
    grid-column: 1 / -1;
  }

  .char-vault-sort select {
    min-height: 40px;
    border-radius: 14px;
  }

  .char-vault-filter-menu-wrap,
  .char-vault-view-toggle {
    display: none;
  }

  .char-vault-mobile-filter-btn {
    display: flex;
    align-items: center;
    justify-content: center;
    gap: 6px;
    width: 100%;
    grid-column: 1 / -1;
    padding: 10px;
    font-size: 0.9rem;
    font-weight: 700;
    min-height: 44px;
    border: 1px solid var(--border-strong);
    border-radius: var(--radius, 12px);
    background: var(--surface2);
    color: var(--accent);
    margin-bottom: 4px;
    cursor: pointer;
  }

  .char-vault-filter-menu {
    position: fixed;
    inset: auto 12px 12px 12px;
    width: auto;
    max-height: min(70vh, 560px);
    overflow-y: auto;
    grid-template-columns: 1fr;
  }

  .char-vault-ledger {
    display: none;
  }

  .char-vault-card-grid {
    width: 100%;
    grid-template-columns: 1fr;
    gap: 12px;
  }

  .char-vault-card {
    width: 100%;
    border-radius: 18px;
    overflow: hidden;
  }

  .char-vault-card .char-vault-title-cell strong {
    font-family: var(--font-display);
    font-size: 20px;
    line-height: 1;
  }

  .char-vault-card-meta {
    font-size: 14px;
  }

  .char-vault-actions {
    display: grid;
    grid-template-columns: repeat(2, minmax(0, 1fr));
  }

  .char-vault-actions .btn,
  .char-vault-row-menu,
  .char-vault-row-menu > .btn {
    width: 100%;
    justify-content: center;
  }

  .char-vault-row-menu-panel {
    position: fixed;
    left: 12px;
    right: 12px;
    top: auto;
    bottom: 12px;
    z-index: 70;
  }

  .char-vault-mobile-drawer[hidden] {
    display: none;
  }

  .char-vault-mobile-drawer {
    position: fixed;
    inset: 0;
    z-index: 80;
    display: block;
    background: rgba(0,0,0,.52);
    cursor: pointer;
  }

  .char-vault-mobile-panel {
    position: absolute;
    top: 0;
    right: 0;
    bottom: 0;
    width: min(88vw, 380px);
    overflow-y: auto;
    padding: 16px 14px 24px;
    border-left: 1px solid var(--border-strong);
    background: linear-gradient(180deg, var(--surface2), var(--bg));
    box-shadow: -20px 0 50px rgba(0,0,0,.45);
  }

  .char-vault-mobile-drawer-head {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 12px;
    margin-bottom: 12px;
  }

  .char-vault-mobile-drawer-head h2 {
    margin: 0;
    color: var(--accent);
    font-family: var(--font-display);
    font-size: 30px;
    line-height: 1;
  }

  .char-vault-mobile-close {
    width: 42px;
    height: 42px;
    border: 1px solid var(--border);
    border-radius: 14px;
    background: var(--surface);
    color: var(--text);
    font-size: 24px;
  }

  .char-vault-mobile-section {
    overflow: hidden;
    margin-bottom: 10px;
    border: 1px solid var(--border);
    border-radius: 18px;
    background: var(--surface);
  }

  .char-vault-mobile-section summary {
    min-height: 52px;
    padding: 13px 14px;
    color: var(--accent);
    cursor: pointer;
    font-size: 12px;
    font-weight: 900;
    letter-spacing: .1em;
    list-style: none;
    text-transform: uppercase;
  }

  .char-vault-mobile-section summary::-webkit-details-marker {
    display: none;
  }

  .char-vault-mobile-section > .char-vault-filter-list,
  .char-vault-mobile-section > .char-vault-box-actions,
  .char-vault-mobile-field-grid {
    margin: 0 14px 14px;
  }

  .char-vault-mobile-drawer .char-vault-filter-list {
    flex-direction: column;
    overflow: visible;
  }

  .char-vault-mobile-drawer .char-vault-filter-btn {
    width: 100%;
    min-height: 40px;
  }

  .char-vault-mobile-drawer .char-vault-box-actions {
    display: grid;
    grid-template-columns: repeat(3, 1fr);
  }

  .char-vault-mobile-field-grid {
    display: grid;
    grid-template-columns: repeat(2, minmax(0, 1fr));
    gap: 7px;
  }

  .char-vault-mobile-field-grid button {
    min-height: 42px;
    border: 1px solid var(--border);
    border-radius: 13px;
    background: var(--surface2);
    color: var(--text);
    font-weight: 800;
  }

  .char-vault-mobile-field-grid button.active {
    border-color: var(--accent);
    color: var(--accent);
    background: color-mix(in srgb, var(--accent) 10%, var(--surface2));
  }

  .char-vault-mobile-clear {
    width: 100%;
    min-height: 46px;
    margin-top: 8px;
    border: 1px solid color-mix(in srgb, var(--danger) 60%, var(--border));
    border-radius: 15px;
    background: color-mix(in srgb, var(--danger) 12%, transparent);
    color: var(--danger);
    font-weight: 900;
  }

  .admin-table,
  .admin-table thead,
  .admin-table tbody,
  .admin-table tr,
  .admin-table th,
  .admin-table td {
    display: block;
  }

  .admin-table thead {
    display: none;
  }

  .admin-table tr {
    margin-bottom: 12px;
    padding: 12px;
    border: 1px solid var(--border);
    border-radius: 14px;
    background: var(--surface);
  }

  .admin-table td {
    display: flex;
    justify-content: space-between;
    gap: 12px;
    padding: 7px 0;
    border-bottom: 1px solid var(--border);
  }

  .admin-table td:last-child {
    border-bottom: 0;
  }

  .admin-table td::before {
    content: attr(data-label);
    color: var(--text-muted);
    font-size: 11px;
    font-weight: 800;
    letter-spacing: .08em;
    text-transform: uppercase;
  }
}

.locked-play-sheet * {
  box-sizing: border-box;
}

.locked-play-box {
  position: absolute;
  overflow: hidden;
  border: 1px solid var(--border);
  border-radius: 14px;
  background: linear-gradient(180deg, var(--surface), var(--surface2));
  box-shadow: inset 0 1px 0 rgba(255,255,255,.06), 0 16px 34px rgba(0,0,0,.18);
}

.locked-play-box h2 {
  margin: 0;
  padding: 7px 10px;
  border-bottom: 1px solid var(--border);
  color: var(--accent);
  font-size: 10px;
  font-weight: 900;
  letter-spacing: .12em;
  line-height: 1.1;
  text-transform: uppercase;
}

.locked-play-body {
  position: absolute;
  top: 26px;        /* sits below the h2 title bar */
  left: 0;
  right: 0;
  bottom: 0;
  padding: 8px;
  overflow: hidden; /* individual bodies can override to auto for scrolling */
  box-sizing: border-box;
}

.locked-play-header { left: 0; top: 0; width: 720px; height: 60px; border-color: var(--accent); }
.locked-play-hp { left: 0; top: 70px; width: 290px; height: 175px; }
.locked-play-abilities { left: 300px; top: 70px; width: 420px; height: 100px; }
.locked-play-core { left: 730px; top: 0; width: 360px; height: 170px; }
.locked-play-defenses { left: 1100px; top: 0; width: 330px; height: 172px; }
.locked-play-saves { left: 120px; top: 250px; width: 170px; height: 240px; }
.locked-play-passives { left: 0; top: 250px; width: 110px; height: 240px; }
.locked-play-resources { left: 0; top: 500px; width: 290px; height: 230px; }
.locked-play-skills { left: 300px; top: 180px; width: 260px; height: 550px; }
.locked-play-switchable { left: 570px; top: 180px; width: 520px; height: 550px; }
.locked-play-conditions { left: 1100px; top: 182px; width: 330px; height: 168px; }
.locked-play-profs { left: 1100px; top: 360px; width: 327px; height: 274px; overflow: hidden; }
.locked-play-notes-gap { left: 1100px; top: 644px; width: 330px; height: 90px; }

.locked-play-switchable > .locked-play-body {
  top: 0;  /* no h2 in switchable box — tabs are inside the body */
}

.locked-play-header-body {
  display: grid;
  grid-template-columns: auto 1fr auto;
  align-items: center;
  gap: 10px;
  height: 100%;
  padding: 7px 10px;
}

.locked-play-name-block {
  display: grid;
  grid-template-columns: auto minmax(0, 1fr);
  align-items: center;
  gap: 18px;
  min-width: 0;
}

.locked-play-name-block h1 {
  margin: 0;
  color: var(--accent);
  font-family: var(--font-display);
  font-size: 24px;
  line-height: .95;
  white-space: nowrap;
  /* A long name is cut with an ellipsis rather than pushing the identity pills and the Rest button around;
     the full name is in the title attribute for hover. */
  max-width: 300px;
  min-width: 0;
  overflow: hidden;
  text-overflow: ellipsis;
}

.locked-play-identity-grid {
  display: grid;
  grid-template-columns: repeat(3, minmax(0, 1fr));
  gap: 10px;
  min-width: 0;
}

.locked-id-pill {
  min-width: 0;
  padding: 4px 7px;
  border: 1px solid var(--border-strong);
  border-radius: 10px;
  background: var(--input-bg);
}

.locked-id-pill span,
.locked-hp-main span,
.locked-hp-mini span,
.locked-def-row > span,
.locked-exhaustion > span {
  display: block;
  color: var(--text-muted);
  font-size: 8px;
  font-weight: 900;
  letter-spacing: .09em;
  text-transform: uppercase;
}

.locked-id-pill b {
  display: block;
  overflow: hidden;
  margin-top: 3px;
  color: var(--text);
  font-size: 12px;
  line-height: 1.1;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.locked-header-actions {
  display: flex;
  gap: 6px;
}

.locked-icon-btn,
.locked-hp-tools button,
.locked-save-card,
.locked-cond,
.locked-ex,
.locked-bubble,
.locked-tab,
.locked-action-chip,
.locked-add-item button,
.locked-inv-add-section button,
.locked-coin-remove button,
.locked-inv-actions button {
  border: 1px solid var(--border-strong);
  color: var(--text);
  font-family: var(--font);
  font-weight: 900;
  background: var(--input-bg);
  cursor: pointer;
}

.locked-icon-btn {
  display: grid;
  place-items: center;
  width: 38px;
  height: 34px;
  padding: 0;
  border-radius: 10px;
  font-size: 11px;
}

.locked-rest-menu {
  position: absolute;
  left: 730px;
  top: 0;
  width: 360px;
  height: 170px;
  z-index: 20;
  padding: 14px;
  border: 1px solid var(--border-strong);
  border-radius: 12px;
  background: var(--input-bg);
  box-shadow: 0 18px 38px rgba(0,0,0,.35);
  display: flex;
  flex-direction: column;
  gap: 6px;
}

.locked-rest-menu p {
  margin: 0;
  color: var(--text-muted);
  font-size: 12px;
  line-height: 1.35;
}

.locked-rest-menu>.locked-rest-btns {
  display: flex;
  gap: 5px;
  flex-wrap: wrap;
}

.locked-rest-menu .btn-undo-rest {
  display: none;
}

.locked-rest-menu .btn-undo-rest.visible {
  display: inline-block;
}

.locked-rest-hd {
  margin-top: auto;
}

.locked-hd-row {
  display: flex;
  align-items: center;
  gap: 6px;
  flex-wrap: wrap;
}

.locked-hd-row span {
  color: var(--text-muted);
  font-size: 12px;
  white-space: nowrap;
}

.locked-hd-baubles {
  display: flex;
  gap: 3px;
}

.locked-hd-bauble {
  width: 22px;
  height: 22px;
  box-sizing: border-box;
  border: 2px solid var(--border-strong);
  border-radius: 7px;
  background: var(--input-bg);
  cursor: pointer;
  transition: border-color .12s ease, background-color .12s ease;
}

.locked-hd-bauble:hover { border-color: var(--accent); }

.locked-hd-bauble.used {
  border-color: var(--accent);
  background: var(--accent);
}

.locked-hp-body {
  display: grid;
  grid-template-columns: 1fr 78px;
  grid-template-rows: 1fr 1fr 22px;
  gap: 5px;
}

.locked-hp-main {
  grid-row: 1 / 3;
  padding: 10px;
  border: 1px solid var(--border-strong);
  border-radius: 12px;
  background: var(--input-bg);
  min-height: 0;
  position: relative;
}

.locked-hp-main span {
  margin: 0;
}

.locked-hp-main b {
  display: block;
  margin: 1px 0 0;
  color: var(--text);
  font-size: 32px;
  line-height: 1;
}

input.locked-hp-current-input {
  display: block;
  width: 100%;
  margin: 1px 0 0;
  padding: 0;
  border: 0;
  outline: none;
  background: transparent;
  color: var(--text);
  font-family: var(--font);
  font-size: 32px;
  font-weight: 900;
  line-height: 1;
  -moz-appearance: textfield;
}

input.locked-hp-current-input::-webkit-outer-spin-button,
input.locked-hp-current-input::-webkit-inner-spin-button {
  -webkit-appearance: none;
  margin: 0;
}

.locked-hp-meter {
  position: absolute;
  bottom: 10px;
  left: 10px;
  right: 10px;
  height: 12px;
  overflow: hidden;
  border: 1px solid var(--border-strong);
  border-radius: 999px;
  background: var(--bg);
}

.locked-hp-meter i {
  display: block;
  height: 100%;
  border-radius: inherit;
  /* Coloured by how hurt the character is, never by the theme's accent: the
     accent is oxblood in the light theme, which made a full bar read as danger. */
  background: linear-gradient(90deg, color-mix(in srgb, var(--success) 80%, var(--bg)), var(--success));
  transition: width .25s ease;
}

/* Bloodied (half or less) and low (a quarter or less). */
.locked-hp-meter.bloodied i { background: linear-gradient(90deg, color-mix(in srgb, var(--warn) 80%, var(--bg)), var(--warn)); }
.locked-hp-meter.low i { background: linear-gradient(90deg, color-mix(in srgb, var(--danger) 80%, var(--bg)), var(--danger)); }

.locked-hp-main.down .locked-hp-meter i {
  background: var(--danger);
}

.locked-hp-main.down .locked-death-saves {
  position: absolute;
  top: 10px;
  right: 10px;
  display: flex;
  flex-direction: column;
  gap: 12px;
}

.locked-death-row {
  display: flex;
  align-items: center;
  gap: 4px;
}

.locked-death-row span {
  display: block;
  color: var(--text-muted);
  font-size: 9px;
  width: 38px;
  text-align: right;
}

/* What this character's sources say about the death-save ROLL -- a bonus on the
   d20, a floor under it, or both. Rendered only when there is something to say,
   so the ordinary down-character panel is unchanged. Right-aligned to sit under
   the bubbles rather than under their labels, and wrapped rather than clipped:
   a character carrying both an Ioun Stone of Vitality and a Periapt says two
   things at once. */
.locked-death-note {
  color: var(--text-muted);
  font-size: 9px;
  text-align: right;
  max-width: 130px;
  margin-top: -6px;
}

.locked-death-bubble {
  width: 17px;
  height: 17px;
  border-radius: 50%;
  border: 2px solid var(--border-strong);
  background: transparent;
  cursor: pointer;
}

.locked-death-bubble.success.filled {
  background: var(--success);
  border-color: var(--success);
}

.locked-death-bubble.failure.filled {
  background: var(--danger);
  border-color: var(--danger);
}

.locked-hp-mini {
  padding: 7px;
  border: 1px solid var(--border-strong);
  border-radius: 12px;
  background: var(--input-bg);
}

.locked-hp-mini b,
.locked-hp-mini input {
  display: block;
  width: 100%;
  margin-top: 2px;
  border: 0;
  outline: none;
  color: var(--text);
  font-size: 14px;
  font-weight: 900;
  line-height: 1;
  background: transparent;
  min-height: 0;   /* prevent browser-default input height */
  padding: 0;       /* remove any browser-default input padding */
  -moz-appearance: textfield;
}

.locked-hp-mini input::-webkit-outer-spin-button,
.locked-hp-mini input::-webkit-inner-spin-button {
  -webkit-appearance: none;
  margin: 0;
}

.locked-hp-tools {
  grid-column: 1 / 3;
  display: grid;
  grid-template-columns: 1fr 54px 1fr;
  gap: 7px;
}

.locked-hp-tools button,
.locked-hp-tools input {
  min-height: 0;
  height: 22px;
  border-radius: 999px;
  font-size: 11px;
}

.locked-hp-tools input {
  border: 1px solid var(--border-strong);
  color: var(--text);
  text-align: center;
  background: var(--input-bg);
  -moz-appearance: textfield;
}

.locked-hp-tools input::-webkit-outer-spin-button,
.locked-hp-tools input::-webkit-inner-spin-button {
  -webkit-appearance: none;
  margin: 0;
}

.locked-abilities-body {
  top: 0;           /* no h2 in abilities box */
  display: grid;
  grid-template-columns: repeat(6, 1fr);
  gap: 7px;
}

.locked-ability {
  display: grid;
  align-content: center;
  justify-items: center;
  min-width: 0;
  border: 1px solid var(--border-strong);
  border-radius: 12px;
  background: var(--input-bg);
}

.locked-ability span {
  color: var(--text-muted);
  font-size: 8px;
  font-weight: 900;
  letter-spacing: .08em;
  text-transform: uppercase;
}

.locked-ability b {
  color: var(--text);
  font-size: 24px;
  line-height: 1.05;
}

.locked-ability em {
  color: var(--accent);
  font-size: 14px;
  font-style: normal;
  font-weight: 900;
}

.locked-core-body {
  position: absolute;  /* absolute within box, not relative */
  top: 26px; left: 0; right: 0; bottom: 0;
  display: grid;
  grid-template-columns: 1fr 1fr;
  grid-template-rows: 1fr 1fr;
  gap: 7px;
  padding: 8px;
  box-sizing: border-box;
}

.locked-core-stat {
  display: grid;
  place-items: center;
  border: 1px solid var(--border-strong);
  border-radius: 14px;
  background: var(--input-bg);
}

.locked-core-stat b {
  color: var(--text);
  font-size: 28px;
  line-height: .95;
}

.locked-core-stat span {
  color: var(--text-muted);
  font-size: 9px;
  font-weight: 900;
  letter-spacing: .1em;
  text-transform: uppercase;
}

.locked-inspiration-star {
  position: absolute;
  left: 50%;
  top: 50%;
  width: 68px;
  height: 68px;
  padding: 0;
  transform: translate(-50%, -50%);
  border: 0;
  color: transparent;
  background: transparent;
  filter: drop-shadow(0 5px 10px rgba(0,0,0,.45));
}

.locked-inspiration-star::before {
  content: "";
  position: absolute;
  inset: 0;
  background: var(--border-strong);
  clip-path: polygon(50% 0, 61% 35%, 98% 35%, 68% 56%, 79% 92%, 50% 70%, 21% 92%, 32% 56%, 2% 35%, 39% 35%);
}

.locked-inspiration-star::after {
  content: "";
  position: absolute;
  inset: 10px;
  background: var(--input-bg);
  clip-path: polygon(50% 0, 61% 35%, 98% 35%, 68% 56%, 79% 92%, 50% 70%, 21% 92%, 32% 56%, 2% 35%, 39% 35%);
}

.locked-inspiration-star.active::before {
  background: var(--accent);
}

.locked-def-body,
.locked-profs-body {
  display: flex;
  flex-direction: column;
  gap: 6px;
  font-size: 11px;
  padding: 8px 10px;
  overflow-y: auto;  /* content scrolls within fixed box */
  box-sizing: border-box;
}

.locked-def-row {
  display: grid;
  gap: 5px;
}

.locked-def-row > div {
  display: flex;
  flex-wrap: wrap;
  gap: 5px;
}

.locked-tag {
  display: inline-flex;
  align-items: center;
  max-width: 100%;
  padding: 3px 7px;
  border: 1px solid var(--border-strong);
  border-radius: 999px;
  color: var(--text);
  font-size: 11px;
  font-weight: 800;
  line-height: 1.1;
  background: var(--input-bg);
}

.locked-tag.r { border-color: var(--tag-resistance); }
.locked-tag.i { border-color: var(--tag-immunity); }
.locked-tag.v { border-color: var(--tag-vulnerability); }
.locked-tag.adv { border-color: var(--tag-advantage); }

/* "Poison · Resist": the kind as a small label in the chip's own colour. */
.locked-tag em {
  margin-left: 6px;
  padding-left: 6px;
  border-left: 1px solid color-mix(in srgb, currentColor 25%, transparent);
  color: var(--text-muted);
  font-size: 9px;
  font-style: normal;
  font-weight: 900;
  letter-spacing: .06em;
  text-transform: uppercase;
}

.locked-tag.r em { color: var(--tag-resistance); }
.locked-tag.i em { color: var(--tag-immunity); }
.locked-tag.v em { color: var(--tag-vulnerability); }
.locked-tag.adv em { color: var(--tag-advantage); }

.locked-saves-body {
  display: grid;
  gap: 7px;
}

.locked-passive-body {
  display: grid;
  align-content: center;
  gap: 7px;
}

.locked-save-card,
.locked-passive {
  display: grid;
  align-items: center;
  min-height: 28px;
  border-radius: 10px;
  background: var(--input-bg);
}

.locked-save-card {
  grid-template-columns: 1fr auto;
  padding: 0 8px;
  text-align: left;
}

.locked-save-card.has-advantage { border: 1px solid color-mix(in srgb, var(--tag-advantage) 45%, transparent); }

.locked-save-card b,
.locked-skill b small {
  color: var(--text-muted);
  font-size: 10px;
  letter-spacing: .08em;
}

.locked-save-card span,
.locked-skill em {
  color: var(--accent);
  font-size: 15px;
  font-style: normal;
  font-weight: 900;
}

.locked-passive {
  justify-items: center;
  padding: 6px 4px;
  border: 1px solid var(--border-strong);
}

.locked-passive b {
  color: var(--text);
  font-size: 24px;
  line-height: 1;
}

.locked-passive span {
  color: var(--text-muted);
  font-size: 8px;
  font-weight: 900;
  line-height: 1.1;
  text-align: center;
  text-transform: uppercase;
}

.locked-res-body,
.locked-skills-body {
  display: flex;
  flex-direction: column;
  gap: 5px;
  overflow-y: auto;
}

.locked-skills-body {
  padding: 0 8px;
}

.locked-resource {
  display: flex;
  align-items: center;
  gap: 8px;
  padding: 5px 7px;
  border: 1px solid var(--border);
  border-radius: 10px;
  background: var(--input-bg);
}

.locked-resource b {
  flex: 1 1 auto;
  min-width: 0;
  color: var(--text);
  font-size: 12px;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.locked-resource span {
  display: flex;
  flex: 0 0 auto;
  gap: 4px;
  align-items: center;
}

.locked-resource em {
  flex: 0 0 auto;
  color: var(--text-muted);
  font-size: 10px;
  font-style: normal;
  font-weight: 800;
  text-transform: uppercase;
}

.locked-resource-count {
  display: flex;
  align-items: center;
  gap: 5px;
  color: var(--text-muted);
  font-size: 11px;
  font-weight: 800;
  white-space: nowrap;
}

.locked-resource-count b {
  color: var(--text-muted);
  font-size: 11px;
  font-weight: 800;
  white-space: nowrap;
}

.locked-resource-adjust {
  display: inline-grid;
  place-items: center;
  width: 18px;
  height: 18px;
  min-height: 0;
  padding: 0;
  border: 1px solid var(--border-strong);
  border-radius: 50%;
  background: var(--input-bg);
  color: var(--text);
  font-family: var(--font);
  font-size: 12px;
  font-weight: 900;
  line-height: 1;
  cursor: pointer;
}

.locked-resource-adjust:disabled {
  opacity: .4;
  cursor: default;
}

.locked-bubble {
  width: 18px;
  height: 18px;
  min-height: 0;
  padding: 0;
  border-radius: 50%;
  background: transparent;
}

.locked-bubble.on {
  background: var(--accent);
  box-shadow: inset 0 0 0 3px var(--input-bg);
}

.locked-skill {
  display: grid;
  grid-template-columns: 36px 1fr 34px;
  align-items: center;
  gap: 6px;
  min-height: 24px;
  padding: 2px 5px;
  border: 1px solid color-mix(in srgb, var(--border-strong) 55%, transparent);
  border-radius: 9px;
  background: color-mix(in srgb, var(--input-bg) 82%, transparent);
}

.locked-marker {
  display: inline-grid;
  place-items: center;
  min-width: 28px;
  min-height: 16px;
  border: 1px solid var(--border-strong);
  border-radius: 999px;
  color: var(--text-muted);
  font-size: 8px;
  font-weight: 900;
}

/* The raw ability-check chip (_abilityCheckTag in play.js) is a separate class
   from the skill/save marker above, because it sits in a differently shaped
   container and wants different rules: a small block carrying a tier word over
   a resolved number ("Half" above "Check +5") rather than a fixed-width 28px
   abbreviation on one line.
   Borrowing `.locked-marker` for it did not work -- both of the ability card's
   containers are more specific than a single class (`.locked-ability span`
   here, `.mobile-sheet-stat-card span` in style-mobile.css), so the chip took
   its casing, size and display from them and read "HALF +2" on the desktop
   sheet and a full-width rectangle on mobile.
   Everything the containers do NOT set can live at one-class specificity here;
   the properties they do set (display, margin, font, letter-spacing,
   text-transform) are declared per surface, each at its container's own
   specificity -- see `.locked-ability .locked-check-chip` below and the mobile
   rule in style-mobile.css. `currentColor` on the border keeps that per-surface
   block out of the colour question entirely. */
.locked-check-chip {
  max-width: 100%;
  padding: 1px 5px;
  border: 1px solid currentColor;
  /* A rounded rectangle, not the marker's 999px pill: this one is two stacked
     lines when a tier is present, and a full capsule around two lines needs
     horizontal padding the 61px card has not got. */
  border-radius: 7px;
  line-height: 1.25;
  text-align: center;
  white-space: nowrap;
}

/* The tier word and the resolved check, one above the other. Two-class selector
   because both containers' own `span` rules -- including the uppercasing,
   blocking one in style-mobile.css, which loads after this file -- would
   otherwise reach these inner lines and re-impose exactly what the chip's class
   exists to escape. */
.locked-check-chip .cc-line {
  display: block;
  margin: 0;
  color: inherit;
  font-size: inherit;
  font-weight: inherit;
  letter-spacing: inherit;
  text-transform: none;
}

.locked-ability .locked-check-chip {
  display: inline-block;
  margin-top: 3px;
  color: var(--text-muted);
  font-size: 8px;
  font-weight: 900;
  letter-spacing: 0;
  text-transform: none;
}

/* The save card's own `span` rule (`.locked-save-card span`, 15px accent) is
   likewise more specific than `.locked-marker`, which inflated the save tier
   badge to the size of the save total beside it. */
.locked-save-card .locked-marker {
  font-size: 8px;
  font-weight: 900;
}

/* Tier colours, single-sourced across both badge shapes so a tier reads the
   same on a skill row, a save card and an ability card. Two-class selectors:
   they must out-specify the containers' own `span` colour rules, and they must
   land after the per-surface layout blocks above. */
.locked-marker.none,
.locked-check-chip.none { color: var(--text-muted); border-color: var(--border-strong); }

/* Not proficient: a quiet dot instead of an empty outlined capsule, which read
   as a missing value. Only when empty, so a "none" marker with a word keeps its box. */
.locked-marker.none:empty { border-color: transparent; }
.locked-marker.none:empty::before {
  content: "";
  width: 5px;
  height: 5px;
  border-radius: 50%;
  background: var(--border-strong);
}
.locked-marker.proficient,
.locked-check-chip.proficient { color: var(--accent); border-color: var(--accent); }
.locked-marker.expertise,
.locked-check-chip.expertise { color: var(--marker-expertise); border-color: var(--marker-expertise); }
.locked-marker.half,
.locked-check-chip.half { color: var(--marker-half); border-color: var(--marker-half); }

.locked-adv-badge {
  display: inline-grid;
  place-items: center;
  width: 16px;
  height: 16px;
  border: 1px solid var(--success);
  border-radius: 999px;
  color: var(--success);
  font-size: 9px;
  font-weight: 900;
}

.locked-skill b {
  overflow: hidden;
  color: var(--text);
  font-size: 12px;
  font-weight: 800;
  text-overflow: ellipsis;
  white-space: nowrap;
  line-height: 1;
}

.locked-skill em {
  /* Normal line-height leaves asymmetric space below the glyph box (font descent metrics),
     which inflates this row's auto height unevenly and pushes every child -- including the
     Prof/Exp/Half pill -- a couple px above true center. Pin it to 1 so the row sizes to the
     actual glyph box and align-items: center lands exactly centered. */
  line-height: 1;
}

.locked-switch-body {
  display: flex;
  flex-direction: column;
  gap: 8px;
  min-height: 0;
}

.locked-tabs {
  display: flex;
  flex-wrap: wrap;
  gap: 5px;
}

.locked-tab,
.locked-action-chip,
.locked-add-item button,
.locked-inv-add-section button,
.locked-coin-remove button,
.locked-inv-actions button {
  min-height: 0;
  padding: 5px 8px;
  border-radius: 999px;
  font-size: 11px;
}

.locked-tab.on,
.locked-action-chip:hover,
.locked-coin-remove button:hover,
.locked-inv-actions button:hover {
  border-color: var(--accent);
  color: var(--accent);
}

.locked-play-switch-scroll {
  flex: 1;
  min-height: 0;
  overflow-y: auto;
  padding-right: 5px;
}

.locked-play-switch-scroll h3 {
  margin: 8px 0 5px;
  color: var(--accent);
  font-size: 11px;
  font-weight: 900;
  letter-spacing: .08em;
  text-transform: uppercase;
}

.locked-attack-table,
.locked-spell-table {
  width: 100%;
  border-collapse: collapse;
  color: var(--text);
  font-size: 11px;
}

.locked-attack-table th,
.locked-attack-table td {
  padding: 4px 5px;
  border-bottom: 1px solid var(--border);
  text-align: left;
}

.locked-attack-table th:nth-child(2),
.locked-attack-table td:nth-child(2) {
  width: 40px;
  text-align: center;
}

.locked-attack-table th:nth-child(3),
.locked-attack-table td:nth-child(3) {
  text-align: right;
}

.locked-attack-table th:nth-child(4),
.locked-attack-table td:nth-child(4) {
  text-align: left;
}

.locked-attack-count {
  font-weight: 400;
  font-size: 9px;
  color: var(--text-faint);
  margin-left: 6px;
}

.locked-weapon-prop {
  display: inline-block;
  padding: 1px 5px;
  margin: 1px 2px 1px 0;
  border: 1px solid var(--border-strong);
  border-radius: 4px;
  font-size: 9px;
  line-height: 1.3;
  color: var(--text-muted);
  text-transform: lowercase;
}

/* A weapon's extra damage dice, beside its own damage and visibly a SECOND
   term: the leading "+ " and the lighter colour are there so a player reads
   "1d8+3 slashing" and "1d6 fire" as two rolls rather than as one long number
   they are meant to combine in their head. See
   `app/core/stats/damage_dice.py`. */
.locked-weapon-extra-damage {
  display: inline-block;
  margin-left: 6px;
  color: var(--text-muted);
  white-space: nowrap;
}

.locked-action-chips,
.locked-coins {
  display: flex;
  flex-wrap: wrap;
  gap: 5px;
}

.locked-coins-total {
  color: var(--text-muted);
  font-size: 10px;
  font-weight: 700;
  text-transform: none;
}

.locked-coins-row {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 10px;
}

.locked-coin-remove {
  display: flex;
  align-items: center;
  gap: 6px;
}

.locked-coin-remove h4 {
  margin: 0;
  font-size: 10px;
  color: var(--text-muted);
}

.locked-coin-remove input {
  width: 54px;
  padding: 4px;
  text-align: center;
  border: 1px solid var(--border-strong);
  border-radius: 6px;
  background: var(--input-bg);
  color: var(--text);
}

.locked-coin-remove select {
  padding: 4px;
  border: 1px solid var(--border-strong);
  border-radius: 6px;
  background: var(--input-bg);
  color: var(--text);
}

.locked-coin-remove-error {
  color: var(--danger, #d9534f);
  font-size: 10px;
  font-weight: 700;
}

/* ── Carried weight (inventory tab, under Coins) ────────────────────────────
   Markup owned by playCarryBar() in static/js/play.js. Built from the locked-*
   panel recipe used by .locked-inv-add-section / .locked-action-feature — 10px
   radius, a 55%-strength border-strong hairline, an 82% input-bg wash — so it
   belongs to the live sheet rather than borrowing the wizard's carry summary.
   Band colours are the shared --carry-* ramp from style-base.css; the four
   modifiers land on both the fill and the note, so each rule is scoped. */
/* Room between Coins (the Remove row), Carried Weight and the Inventory list. */
.locked-coins-section { margin-bottom: 12px; }

.locked-carry {
  margin-bottom: 14px;
  padding: 7px 9px 8px;
  border: 1px solid color-mix(in srgb, var(--border-strong) 55%, transparent);
  border-radius: 10px;
  background: color-mix(in srgb, var(--input-bg) 82%, transparent);
}
.locked-carry > h3 {
  margin: 0 0 5px;
  color: var(--accent);
  font-size: 10px;
  font-weight: 900;
  letter-spacing: 0.08em;
  text-transform: uppercase;
}
.locked-carry-figure {
  color: var(--text-muted);
  font-size: 11px;
  line-height: 1.3;
}
.locked-carry-figure b {
  color: var(--num);
  font-size: 14px;
  font-weight: 900;
}
.locked-carry-pdl {
  color: var(--text-faint);
  font-size: 9px;
  white-space: nowrap;
}
.locked-carry-bar {
  height: 6px;
  margin-top: 5px;
  border: 1px solid color-mix(in srgb, var(--border-strong) 55%, transparent);
  border-radius: 999px;
  background: color-mix(in srgb, var(--bg) 55%, transparent);
  overflow: hidden;
}
/* Width comes inline from playCarryBar(); the track, colour and transition are here. */
.locked-carry-fill {
  display: block;
  height: 100%;
  border-radius: inherit;
  background: var(--carry-clear);
  transition: width .25s ease, background-color .25s ease;
}
.locked-carry-fill.locked-carry-clear { background: var(--carry-clear); }
.locked-carry-fill.locked-carry-encumbered { background: var(--carry-encumbered); }
/* Sparse hatch then dense, on top of a ramp that also steps down in lightness: the two
   loudest bands stay apart with the colour removed, and the note names the band anyway. */
.locked-carry-fill.locked-carry-heavily_encumbered {
  background-color: var(--carry-heavily_encumbered);
  background-image: repeating-linear-gradient(135deg,
    color-mix(in srgb, var(--bg) 60%, transparent) 0 1px,
    transparent 1px 8px);
}
.locked-carry-fill.locked-carry-over_capacity {
  background-color: var(--carry-over_capacity);
  background-image: repeating-linear-gradient(135deg,
    color-mix(in srgb, var(--bg) 65%, transparent) 0 2px,
    transparent 2px 5px);
}
/* Weight is advisory on this sheet too — nothing is enforced — so the note is a
   coloured rule beside ordinary body text, not an error box. The band is named in the
   copy, and the rule thickens per band as a signal that survives greyscale. */
.locked-carry-note {
  margin-top: 6px;
  padding: 5px 8px;
  border-left: 3px solid var(--carry-clear);
  border-radius: 6px;
  background: color-mix(in srgb, var(--carry-clear) 12%, transparent);
  color: var(--text-muted);
  font-size: 10px;
  line-height: 1.4;
}
.locked-carry-note.locked-carry-encumbered {
  border-left-color: var(--carry-encumbered);
  background: color-mix(in srgb, var(--carry-encumbered) 14%, transparent);
}
.locked-carry-note.locked-carry-heavily_encumbered {
  border-left-width: 4px;
  border-left-color: var(--carry-heavily_encumbered);
  background: color-mix(in srgb, var(--carry-heavily_encumbered) 16%, transparent);
}
.locked-carry-note.locked-carry-over_capacity {
  border-left-width: 5px;
  border-left-color: var(--carry-over_capacity);
  background: color-mix(in srgb, var(--carry-over_capacity) 18%, transparent);
  color: var(--text);
  font-weight: 700;
}

.locked-combat-tab {
  font-size: 11px;
}

.locked-action-feature {
  margin-bottom: 7px;
  padding: 7px;
  border: 1px solid color-mix(in srgb, var(--border-strong) 55%, transparent);
  border-radius: 10px;
  background: color-mix(in srgb, var(--input-bg) 82%, transparent);
}

.locked-action-feature b {
  color: var(--text);
  font-size: 11px;
}

.locked-action-feature p {
  color: var(--text-muted);
  font-size: 11px;
  line-height: 1.35;
  word-break: break-word;
  overflow-wrap: break-word;
}

.locked-action-note,
.locked-empty,
.locked-feature p,
.locked-prof-section div,
.locked-inv-row {
  color: var(--text-muted);
  font-size: 11px;
  line-height: 1.35;
  word-break: break-word;
  overflow-wrap: break-word;
  cursor: pointer;
}

/* .desc-list itself now lives in style-base.css (loaded on every page, not just
   the play sheet) since formatDescriptionHTML() is shared app-wide. */

.locked-inv-row:hover {
  background: color-mix(in srgb, var(--text) 6%, transparent);
}

.locked-inv-name {
  font-weight: 700;
  color: var(--text);
}

/* The edition badge sits directly after the item name with no space in the markup
   (name -> qty tag -> edition badge, all adjacent inline elements) -- give it room so
   it doesn't read as smudged against the name. Scoped to the inventory row rather than
   .edition-badge itself, which is shared by other pages that already space it correctly. */
.locked-inv-row .edition-badge {
  margin-left: 5px;
}

/* .rarity-*-text (style-components.css) must win over the color above for magic items --
   style-play-sheet.css loads AFTER style-components.css, so equal-specificity single-class
   selectors would otherwise let .locked-inv-name's plain --text color clobber the rarity
   tint. Bump specificity by qualifying with the element's own class instead of !important. */
.locked-inv-name.rarity-common-text    { color: var(--rarity-common-fg); }
.locked-inv-name.rarity-uncommon-text  { color: var(--rarity-uncommon-fg); }
.locked-inv-name.rarity-rare-text      { color: var(--rarity-rare-fg); }
.locked-inv-name.rarity-very_rare-text { color: var(--rarity-very_rare-fg); }
.locked-inv-name.rarity-legendary-text { color: var(--rarity-legendary-fg); }
.locked-inv-name.rarity-artifact-text  { color: var(--rarity-artifact-fg); }

.locked-inv-row.expanded {
  border-color: var(--accent, #d4a843);
}

.locked-item-detail {
  padding: 0 0 6px;
  font-size: 10px;
  line-height: 1.45;
  color: var(--text-muted);
}

/* Magic-item row badge + mechanical/vibe chips (playChipsHTML). The chips reuse the
   iv-prop/iv-vibe classes but sit directly in the row (no .iv-meta wrapper), so they get
   their own selectors here; colors match the wizard chip look. */
.locked-rarity {
  display: inline-block; font-size: 8.5px; font-weight: 700; letter-spacing: .05em;
  text-transform: uppercase; padding: 1px 7px; border-radius: 99px;
  margin: 0 4px 0 6px; vertical-align: 1px;
}
.locked-inv-row .iv-prop {
  color: var(--text-muted); cursor: help;
  text-decoration: underline dotted var(--text-faint); text-underline-offset: 2px;
}
.locked-inv-row .iv-prop:hover { color: var(--text); text-decoration-color: var(--accent, #d4a843); }
.locked-inv-row .iv-vibe {
  color: var(--text-muted); cursor: help; text-decoration: none;
  padding: 1px 7px; border-radius: 99px; margin: 0 2px;
  background: color-mix(in srgb, var(--surface) 85%, transparent); border: 1px solid color-mix(in srgb, var(--border-strong) 55%, transparent);
}
.locked-inv-row .iv-meta-sep { color: var(--text-faint); margin: 0 3px; }
.locked-inv-row em { color: var(--success); font-style: normal; font-size: 9px; }
/* Title line and chip line of an inventory row. The chips wrap as whole pills
   in their own row, so an item with many effects grows downwards instead of
   breaking "+2 spell atk" across lines. */
.locked-inv-main { display: flex; flex-direction: column; gap: 4px; min-width: 0; flex: 1 1 240px; }
.locked-inv-title { display: flex; flex-wrap: wrap; align-items: center; gap: 4px; }
.locked-inv-title .locked-rarity { margin: 0; }
.locked-inv-chips { display: flex; flex-wrap: wrap; align-items: center; gap: 4px 6px; }
.locked-inv-chips .iv-prop { white-space: nowrap; }
.locked-inv-chips .iv-vibe { margin: 0; }
.locked-inv-chips .iv-meta-sep { display: none; }
/* Transient 3-cap attunement notice (top of the inventory tab). */
.locked-attune-limit {
  font-size: 11px; font-weight: 600; color: var(--badge-warn-fg, #e08080);
  background: var(--badge-warn-bg, #2a1a1a); border: 1px solid var(--badge-warn-fg, #e08080);
  border-radius: 8px; padding: 7px 11px; margin: 0 0 10px;
}
.attune-section h3 { margin-top: 18px; }

.locked-feature,
.locked-feature-group,
.locked-slot-group,
.locked-inv-wrap,
.locked-prof-section {
  margin-bottom: 7px;
  padding: 7px;
  border: 1px solid color-mix(in srgb, var(--border-strong) 55%, transparent);
  border-radius: 10px;
  background: color-mix(in srgb, var(--input-bg) 82%, transparent);
}

.locked-prof-section:last-child {
  margin-bottom: 0;
}

.locked-feature b,
.locked-prof-section b {
  color: var(--text);
  font-size: 11px;
}

.locked-spell-meta,
.locked-slot-group span {
  color: var(--accent);
  font-size: 11px;
  font-weight: 900;
  text-transform: uppercase;
}

.locked-spell-head,
.locked-spell-row {
  display: grid;
  /* Name | From (source chip) | Time | Range | Save/Atk | Dmg | Effect.
     Time is wide enough for "1 bonus action"; Save/Dmg are short codes. */
  /* ... | Effect | cast: the Use button, out of the name cell (the rarer 1/LR
     toggle stays beside the name). */
  /* Floors, not shares (the sheet's canvas is a fixed 1437px, so this table is the same 487px at every window width):
     From holds its widest chip ("Invocation"), Time holds "Reaction", Save/Atk its own header, Dmg "10d6+10" (46px),
     Effect "incapacitated", the last column "No slots". Every column but the name is a fixed width; the name takes the rest. */
  grid-template-columns: minmax(58px, 1fr) 56px 50px 66px 42px 46px 76px 44px;
  gap: 3px;
  padding: 4px;
  border-bottom: 1px solid var(--border);
  font-size: 10px;
}

.locked-spell-head {
  color: var(--accent);
  font-weight: 900;
  text-transform: uppercase;
}

.locked-spell-row.spell-row {
  display: grid;
  align-items: center;
}

/* Value cells wrap at sensible points (a space, a slash) and at two lines at most; a dice expression is never split
   (.dice-tok). What still does not fit is read in full in the styled tip and in the expanded spell, never silently lost. */
.locked-spell-row.spell-row > span:not(.spell-row-name) {
  min-width: 0;
  display: -webkit-box;
  -webkit-box-orient: vertical;
  -webkit-line-clamp: 2;
  line-clamp: 2;
  overflow: hidden;
  white-space: normal;
  overflow-wrap: break-word;
  line-height: 1.25;
}
.dice-tok { white-space: nowrap; }

/* The name wraps to two lines at most (the styled tip carries it whole when cut); the C/R tags and the Use button stay whole. */
.locked-spell-row.spell-row > .spell-row-name {
  display: flex;
  flex-wrap: wrap;   /* the C / R tags drop under a name that has no room for them, so no word of the name is broken for a tag */
  align-items: center;
  gap: 0 4px;
  min-width: 0;
}

.spell-row-name .spell-name-text {
  font-size: 11px;
  flex: 0 1 auto;
  min-width: 0;
  display: -webkit-box;
  -webkit-box-orient: vertical;
  -webkit-line-clamp: 2;
  line-clamp: 2;
  overflow: hidden;
  white-space: normal;
  overflow-wrap: break-word;
  line-height: 1.2;
}

.spell-row-name > :not(.spell-name-text) { flex: none; }

/* A casting time with its trigger clause cut off ("1 reaction"). */
.locked-spell-cast.has-trigger {
  text-decoration: underline dotted;
  text-underline-offset: 2px;
  cursor: help;
}

.mobile-spell-cast { margin: 0 0 6px; }

.locked-spell-detail {
  color: var(--text-muted);
  background: color-mix(in srgb, var(--bg) 35%, transparent);
}

.locked-spell-metamagic {
  margin-bottom: 10px;
}
.locked-spell-metamagic h3 {
  font-size: 11px; font-weight: 700; text-transform: uppercase;
  letter-spacing: 0.08em; color: var(--accent, #d4a843);
  padding: 4px 0 3px;
  border-bottom: 1px solid var(--border-strong);
  margin: 0 0 6px;
}
.locked-metamagic-row {
  margin-bottom: 7px;
  padding: 7px;
  border: 1px solid color-mix(in srgb, var(--border-strong) 55%, transparent);
  border-radius: 10px;
  background: color-mix(in srgb, var(--input-bg) 82%, transparent);
  font-size: 11px;
}
.locked-metamagic-row b { color: var(--text); }
.locked-metamagic-cost {
  color: var(--accent);
  font-weight: 900;
  text-transform: uppercase;
  margin-left: 6px;
}

.locked-spell-groups { display: flex; flex-direction: column; gap: 12px; }
.locked-spell-group-hdr {
  font-size: 11px; font-weight: 700; text-transform: uppercase;
  letter-spacing: 0.08em; color: var(--accent, #d4a843);
  padding: 4px 0 3px;
  border-bottom: 1px solid var(--border-strong);
  margin-bottom: 4px;
  display: flex;
  align-items: center;
  justify-content: space-between;
}

.locked-inline-slots {
  display: inline-flex;
  gap: 3px;
  align-items: center;
}

.locked-inline-bubble {
  width: 15px; height: 15px;
}

.locked-lang-list { display: flex; flex-wrap: wrap; gap: 4px; margin-top: 3px; }
.locked-lang-chip {
  font-size: 10px; padding: 1px 5px;
  background: var(--surface2, #141930);
  border: 1px solid var(--border-strong); border-radius: 4px;
  color: var(--text-muted);
}

.locked-coin {
  display: grid;
  gap: 3px;
  color: var(--text-muted);
  font-size: 10px;
  font-weight: 900;
}

.locked-coin input,
.locked-inv-add-section {
  display: flex;
  flex-direction: column;
  gap: 5px;
  margin-bottom: 7px;
  padding: 7px;
  border: 1px solid color-mix(in srgb, var(--border-strong) 55%, transparent);
  border-radius: 10px;
  background: color-mix(in srgb, var(--input-bg) 82%, transparent);
}

.locked-inv-add-section h4 {
  font-size: 10px;
  font-weight: 900;
  text-transform: uppercase;
  letter-spacing: 0.08em;
  color: var(--accent, #d4a843);
  margin: 0;
}

.locked-inv-add-section label {
  font-size: 10px;
  color: var(--text-faint);
  margin-top: 2px;
}

.locked-inv-add-section input,
.locked-inv-add-section textarea {
  border: 1px solid var(--border-strong);
  border-radius: 8px;
  color: var(--text);
  background: var(--input-bg);
  padding: 5px 7px;
  font-size: 11px;
  font-family: inherit;
  resize: vertical;
}

.locked-inv-add-section button {
  align-self: flex-start;
  margin-top: 2px;
}

/* 2026-07-31: live-play "Search Equipment" catalog picker (Add from Equipment List) */
.locked-catalog-results {
  display: flex;
  flex-direction: column;
  gap: 3px;
  max-height: 180px;
  overflow-y: auto;
}

.locked-catalog-row {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 6px;
  padding: 4px 6px;
  border: 1px solid color-mix(in srgb, var(--border-strong) 40%, transparent);
  border-radius: 6px;
  background: color-mix(in srgb, var(--input-bg) 60%, transparent);
  font-size: 11px;
  color: var(--text);
}

.locked-catalog-tag {
  font-size: 9px;
  text-transform: uppercase;
  letter-spacing: 0.06em;
  color: var(--text-faint);
  flex-shrink: 0;
}

.locked-catalog-row button {
  border: 1px solid var(--border-strong);
  border-radius: 999px;
  background: var(--input-bg);
  color: var(--text);
  font-size: 10px;
  font-weight: 900;
  padding: 3px 8px;
  cursor: pointer;
  flex-shrink: 0;
}

.locked-catalog-row button:hover {
  border-color: var(--accent);
  color: var(--accent);
}

.locked-inv-row {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 7px;
  /* Item #3 (round 4): flex-wrap so .locked-inv-desc-inline (flex-basis:100%) can drop
     onto its own line below the name/actions row instead of squeezing into it. */
  flex-wrap: wrap;
}
/* Always-visible flavor description for potion/scroll consumables (see
   _isConsumableInventoryItem in play.js) -- previously only shown behind a click-to-expand
   .locked-item-detail panel like every other magic item, which read as "no description" for
   the item type players most want to read at a glance. */
.locked-inv-desc-inline {
  flex-basis: 100%; order: 10;
  font-size: 10px; color: var(--text-faint); line-height: 1.4; margin-top: 2px;
}

.locked-coin input {
  width: 54px;
  padding: 4px;
  text-align: center;
}



.locked-inv-row em {
  color: var(--accent);
  font-size: 9px;
  font-style: normal;
  font-weight: 900;
  text-transform: uppercase;
}

.locked-inv-actions {
  display: flex;
  flex-wrap: wrap;
  gap: 4px;
}

/* Item #11 (round 2): stackable quantity tag + +/- stepper for inventory items. */
.locked-inv-qty {
  font-size: 10px; font-weight: 900; color: var(--gold, #d4a843); margin-left: 5px;
}
.locked-inv-qty-stepper { display: inline-flex; gap: 2px; }
.locked-inv-qty-stepper button {
  min-width: 20px; padding: 2px 6px !important; line-height: 1;
}

/* In-place weight for a player's own item -- catalogue entries carry the book's figure and
   get no box at all (customWeightEditor in play.js). Blank reads as "no weight stated",
   which is why the placeholder is an em dash rather than a 0. */
.locked-inv-weight {
  display: inline-flex;
  align-items: center;
  gap: 3px;
  color: var(--text-faint);
  font-size: 10px;
  font-weight: 900;
}
.locked-inv-weight input {
  width: 42px;
  padding: 3px 4px;
  border: 1px solid var(--border-strong);
  border-radius: 999px;
  background: color-mix(in srgb, var(--input-bg) 82%, transparent);
  color: var(--text);
  text-align: center;
  font-size: 11px;
}
.locked-inv-weight input:focus {
  border-color: var(--accent);
  outline: none;
}

.locked-cond-body {
  display: grid;
  grid-template-rows: 86px 35px;
  gap: 6px;
  padding: 7px;
}

.locked-cond-grid {
  display: grid;
  grid-template-columns: repeat(7, 1fr);
  grid-template-rows: repeat(2, 40px);
  gap: 5px;
}

.locked-cond {
  display: grid;
  place-items: center;
  min-height: 0;
  height: 40px;
  padding: 2px;
  border-radius: 9px;
  background: var(--input-bg);
}

.locked-cond span {
  color: var(--accent);
  font-size: 22px;
  line-height: 1;
  font-family: var(--font-emoji);
}

.locked-cond span img {
  width: 32px;
  height: 32px;
  display: block;
}

.locked-cond.on {
  border-color: var(--accent);
  background: color-mix(in srgb, var(--accent) 14%, transparent);
}

/* Hover/focus tip for a condition chip. A body-level fixed element (the chip's
   box clips overflow), placed by showConditionTip() in play.js; dressed like a
   .locked-play-box: same gradient surface, gold small-caps heading, soft shadow. */
.cond-tip {
  position: fixed;
  z-index: 1000;
  left: 0;
  top: 0;
  width: max-content;
  max-width: 260px;
  padding: 9px 12px 10px;
  border: 1px solid var(--border-strong);
  border-radius: 10px;
  background: linear-gradient(180deg, var(--surface), var(--surface2));
  box-shadow: inset 0 1px 0 rgba(255,255,255,.06), 0 12px 28px rgba(0,0,0,.32);
  color: var(--text);
  font-family: var(--font);
  pointer-events: none;
  opacity: 0;
  visibility: hidden;
  transform: translateY(-6px) scale(.96);
  transform-origin: var(--cond-tip-arrow, 50%) top;
  transition: opacity .16s ease, transform .18s cubic-bezier(.2,.8,.3,1.2), visibility 0s linear .18s;
}

.cond-tip.above {
  transform: translateY(6px) scale(.96);
  transform-origin: var(--cond-tip-arrow, 50%) bottom;
}

.cond-tip.show {
  opacity: 1;
  visibility: visible;
  transform: none;
  transition: opacity .16s ease, transform .18s cubic-bezier(.2,.8,.3,1.2), visibility 0s;
}

/* Moving chip to chip while the tip is open slides it across instead of blinking. */
.cond-tip.show.glide {
  transition: opacity .16s ease, transform .18s ease, visibility 0s, left .16s ease, top .16s ease;
}

/* The arrow: a rotated square sharing the tip's border, pointing at the chip. */
.cond-tip::before {
  content: "";
  position: absolute;
  top: -6px;
  left: var(--cond-tip-arrow, 50%);
  width: 10px;
  height: 10px;
  border-top: 1px solid var(--border-strong);
  border-left: 1px solid var(--border-strong);
  background: var(--surface);
  transform: translateX(-50%) rotate(45deg);
}

.cond-tip.above::before {
  top: auto;
  bottom: -6px;
  border: 0;
  border-right: 1px solid var(--border-strong);
  border-bottom: 1px solid var(--border-strong);
  background: var(--surface2);
}

.cond-tip-title {
  display: block;
  margin-bottom: 4px;
  padding-bottom: 5px;
  border-bottom: 1px solid var(--border);
  color: var(--accent);
  font-size: 11px;
  font-weight: 900;
  letter-spacing: .12em;
  line-height: 1.1;
  text-transform: uppercase;
}

/* An active condition says so, so the tip doubles as a state read-out. */
.cond-tip.active .cond-tip-title::after {
  content: "Active";
  margin-left: 8px;
  padding: 1px 6px;
  border-radius: 999px;
  background: var(--accent);
  color: var(--on-accent);
  font-size: 9px;
  letter-spacing: .08em;
  vertical-align: 1px;
}

.cond-tip-body {
  display: block;
  font-size: 13px;
  font-weight: 500;
  line-height: 1.5;
  white-space: pre-line;  /* one effect or source per line */
}

/* A chip the player did not tick but another condition imposed (Unconscious
   makes you Prone): dashed, so it reads as "on, because of something else". */
.locked-cond.imposed {
  border-style: dashed;
  border-color: color-mix(in srgb, var(--accent) 70%, transparent);
  background: color-mix(in srgb, var(--accent) 7%, transparent);
}

.cond-tip.imposed .cond-tip-title::after {
  content: "Imposed";
  margin-left: 8px;
  padding: 0 6px;
  border: 1px dashed var(--accent);
  border-radius: 999px;
  color: var(--accent);
  font-size: 9px;
  letter-spacing: .08em;
  vertical-align: 1px;
}

/* What a condition did to a roll: DIS / ADV / FAIL beside the number it
   affects, naming its source in the tip. Danger for what hurts, success for
   what helps, so the eye finds the bad news first. */
.cond-tag {
  display: inline-block;
  margin-left: 3px;
  padding: 1px 5px;
  border: 1px solid currentColor;
  border-radius: 999px;
  font-family: var(--font);
  font-size: 9px;
  font-weight: 900;
  font-style: normal;
  letter-spacing: .06em;
  line-height: 1.25;
  vertical-align: 1px;
  cursor: help;
}

.cond-tag.dis,
.cond-tag.fail {
  color: var(--danger);
  background: color-mix(in srgb, var(--danger) 14%, transparent);
}

.cond-tag.fail {
  color: var(--on-accent);
  background: var(--danger);
  border-color: var(--danger);
}

.cond-tag.adv {
  color: var(--success);
  background: color-mix(in srgb, var(--success) 14%, transparent);
}

.cond-tag:focus-visible { outline: 2px solid var(--accent); outline-offset: 1px; }

/* The save card styles every `span` as its 15px number; a tag inside the
   card's label must stay a tag (same fix as `.locked-save-card .locked-marker`). */
.locked-save-card .cond-tag {
  font-size: 9px;
  font-weight: 900;
  letter-spacing: .06em;
}

.locked-save-card .cond-tag.dis { color: var(--danger); }
.locked-save-card .cond-tag.fail { color: var(--on-accent); }

.locked-core-stat.cond-affected {
  border-color: var(--danger);
  cursor: help;
}

.locked-core-stat.cond-affected b,
.locked-core-stat.cond-affected > span { color: var(--danger); }

/* "Disadvantage on attack rolls · Poisoned" above the attack list. */
.cond-attack-note {
  display: flex;
  flex-wrap: wrap;
  align-items: baseline;
  gap: 4px 8px;
  margin: 2px 0 6px;
  padding: 5px 9px;
  border: 1px solid var(--danger);
  border-left-width: 3px;
  border-radius: 8px;
  background: color-mix(in srgb, var(--danger) 10%, transparent);
  font-size: 11px;
  line-height: 1.35;
}

.cond-attack-note b { color: var(--danger); font-weight: 900; }
.cond-attack-note span { color: var(--text-muted); }

.cond-attack-note.adv { border-color: var(--success); background: color-mix(in srgb, var(--success) 10%, transparent); }
.cond-attack-note.adv b { color: var(--success); }

.cond-attack-note.none { border-color: var(--border-strong); background: var(--input-bg); color: var(--text-muted); }

/* Other creatures' rolls against you, under Defenses. */
.cond-against {
  display: block;
  width: 100%;
  color: var(--text-muted);
  line-height: 1.35;
}

.cond-against b { color: var(--danger); font-weight: 900; }

.mobile-cond-effect {
  display: grid;
  gap: 2px;
  padding: 6px 0;
  border-bottom: 1px solid var(--border);
  font-size: 13px;
  line-height: 1.4;
}

.mobile-cond-effect:last-child { border-bottom: 0; }
.mobile-cond-effect b { color: var(--accent); }
.mobile-cond-effect span { color: var(--text-muted); }

.locked-exhaustion {
  display: flex;
  align-items: center;
  gap: 8px;
  height: 35px;
  padding: 5px 8px;
  border: 1px solid var(--border);
  border-radius: 10px;
  background: var(--input-bg);
}

.locked-exhaustion > span {
  color: var(--accent);
}

.locked-ex {
  width: 23px;
  height: 23px;
  min-height: 0;
  padding: 0;
  border-radius: 50%;
  font-size: 10px;
}

.locked-ex.on {
  border-color: var(--accent);
  color: var(--input-bg);
  background: var(--accent);
}

/* What the live play state is doing to the numbers, printed under the condition
   chips. One column of short lines rather than a table: each is a sentence the
   engine wrote (`derived.play_state`), and the point is that it is READABLE at a
   glance beside the controls that caused it, not that it lines up. */
.locked-play-note {
  display: flex;
  flex-direction: column;
  gap: 2px;
  margin-top: 6px;
  font-size: 11px;
  line-height: 1.35;
  color: var(--text-muted);
}

.locked-play-note em {
  color: var(--text-muted);
  font-style: italic;
  opacity: 0.85;
}

.locked-exhaustion-effects span {
  color: var(--accent);
}

.locked-play-notes-gap .locked-play-body {
  color: var(--text-muted);
  font-size: 12px;
  padding: 6px;
}

.locked-notes-textarea {
  width: 100%;
  height: 100%;
  border: 1px solid var(--border);
  border-radius: 8px;
  background: var(--surface);
  color: var(--text);
  padding: 6px 8px;
  font-size: 11px;
  line-height: 1.45;
  font-family: var(--font);
  resize: none;
  box-sizing: border-box;
}

.locked-notes-textarea:focus {
  outline: none;
  border-color: var(--accent);
}

/* Sticky header */
.play-header {
  position: sticky;
  top: 0;
  z-index: 100;
  background: var(--surface);
  border-bottom: 1px solid var(--border);
  padding: 0.6rem 1rem;
  display: flex;
  align-items: center;
  gap: 1rem;
  flex-wrap: wrap;
}
.play-header-title { font-size: 1.1rem; font-weight: 700; color: var(--accent-gold); flex: 1; }
.play-header-sub { font-size: 0.85rem; color: var(--text-muted); }
.play-rest-btns { display: flex; gap: 0.4rem; align-items: center; }
.btn-rest { padding: 0.3rem 0.8rem; font-size: 0.85rem; }
.btn-undo-rest { display: none; background: var(--surface2); border: 1px solid var(--accent-gold); color: var(--accent-gold); }
.btn-undo-rest.visible { display: inline-flex; }

/* 3-column grid */
.play-columns {
  display: grid;
  grid-template-columns: 1fr 1fr 1fr;
  gap: 1rem;
  padding: 1rem;
  align-items: start;
}
@media (max-width: 1023px) {
  .play-columns { grid-template-columns: 1fr 1fr; }
}
@media (max-width: 639px) {
  .play-columns { grid-template-columns: 1fr; }
}

/* Panels */
.play-panel {
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: 6px;
  padding: 0.75rem;
  display: flex;
  flex-direction: column;
  gap: 0.75rem;
}
.play-panel-title {
  font-size: 0.7rem;
  font-weight: 700;
  text-transform: uppercase;
  letter-spacing: 0.08em;
  color: var(--text-muted);
  border-bottom: 1px solid var(--border);
  padding-bottom: 0.4rem;
  margin-bottom: 0.1rem;
}
.prof-line {
  font-size: 0.78rem;
  line-height: 1.5;
  color: var(--text);
}
.prof-label {
  font-size: 0.68rem;
  font-weight: 700;
  text-transform: uppercase;
  letter-spacing: 0.06em;
  color: var(--text-muted);
  margin-right: 4px;
}

/* HP block */
.hp-block { display: flex; align-items: center; gap: 0.75rem; }
.hp-current-wrap { display: flex; flex-direction: column; align-items: center; }
.hp-current {
  width: 72px; height: 72px;
  font-size: 2rem; font-weight: 800;
  text-align: center;
  background: var(--bg);
  border: 2px solid var(--accent);
  border-radius: 50%;
  color: var(--text);
  line-height: 72px;
  padding: 0;
}
.hp-max-label { font-size: 0.75rem; color: var(--text-muted); margin-top: 0.2rem; }
.temp-hp-wrap { display: flex; flex-direction: column; align-items: center; gap: 0.2rem; }
.temp-hp-wrap label { font-size: 0.7rem; color: var(--text-muted); }
.temp-hp-input {
  width: 48px; height: 48px; text-align: center;
  font-size: 1.2rem; font-weight: 700;
  background: var(--bg); border: 1px solid var(--border); border-radius: 6px;
  color: var(--text);
}

/* Stat row boxes */
.stat-row { display: flex; gap: 0.5rem; flex-wrap: wrap; }
.stat-box {
  display: flex; flex-direction: column; align-items: center;
  background: var(--bg); border: 1px solid var(--border);
  border-radius: 6px; padding: 0.3rem 0.6rem; min-width: 52px;
}
.stat-box-value { font-size: 1.2rem; font-weight: 800; color: var(--text); }
.stat-box-label { font-size: 0.65rem; color: var(--text-muted); text-transform: uppercase; }

/* Bubble tracker */
.bubble-tracker { display: flex; flex-direction: column; gap: 0.25rem; }
.bubble-tracker-header {
  display: flex; align-items: center; justify-content: space-between;
  font-size: 0.8rem; color: var(--text-muted);
}
.bubble-tracker-name { font-weight: 600; color: var(--text); }
.bubble-row { display: flex; gap: 4px; flex-wrap: wrap; }
.bubble {
  width: 18px; height: 18px;
  border-radius: 50%;
  border: 2px solid var(--accent-gold);
  background: transparent;
  cursor: pointer;
  transition: background 0.1s;
  flex-shrink: 0;
}
.bubble.filled { background: var(--accent-gold); }
.bubble.success-bubble { border-color: var(--success); }
.bubble.success-bubble.filled { background: var(--success); }
.bubble.failure-bubble { border-color: var(--accent); }
.bubble.failure-bubble.filled { background: var(--accent); }

/* Death saves */
.death-saves { display: flex; gap: 1.5rem; }
.save-group { display: flex; flex-direction: column; gap: 0.3rem; align-items: center; }
.save-group-label { font-size: 0.7rem; color: var(--text-muted); }

/* Inspiration toggle */
.inspiration-btn {
  width: 100%; padding: 0.4rem;
  border: 1px solid var(--border); border-radius: 4px;
  background: var(--bg); color: var(--text-muted);
  cursor: pointer; font-size: 0.85rem; transition: all 0.15s;
}
.inspiration-btn.active {
  border-color: var(--accent-gold); color: var(--accent-gold);
  background: color-mix(in srgb, var(--accent) 10%, transparent);
}

/* Exhaustion stepper */
.exhaustion-row { display: flex; align-items: center; gap: 0.5rem; }
.stepper-btn {
  width: 28px; height: 28px; border-radius: 4px;
  border: 1px solid var(--border); background: var(--bg);
  color: var(--text); cursor: pointer; font-size: 1rem; line-height: 1;
}
.exhaustion-val { font-size: 1.1rem; font-weight: 700; min-width: 1.5rem; text-align: center; }

/* Conditions grid */
.conditions-grid {
  display: grid; grid-template-columns: repeat(2, 1fr); gap: 0.25rem;
}
.condition-item { display: flex; align-items: center; gap: 0.4rem; font-size: 0.8rem; cursor: pointer; }
.condition-item input { accent-color: var(--accent); cursor: pointer; }

/* Saving throws table */
.save-table { display: flex; flex-direction: column; gap: 0.15rem; }
.save-row {
  display: flex; align-items: center; gap: 0.5rem;
  font-size: 0.8rem; padding: 0.1rem 0;
}
.save-row-val { font-weight: 700; min-width: 2.5rem; color: var(--text); }
.save-row-name { color: var(--text-muted); flex: 1; }
.save-prof-dot {
  width: 8px; height: 8px; border-radius: 50%;
  border: 1px solid var(--accent-gold); background: transparent; flex-shrink: 0;
}
.save-prof-dot.filled { background: var(--accent-gold); }

/* Weapon table */
.weapon-table { width: 100%; border-collapse: collapse; font-size: 0.78rem; }
.weapon-table th { color: var(--text-muted); font-weight: 600; padding: 0.2rem 0.3rem; border-bottom: 1px solid var(--border); }
.weapon-table td { padding: 0.2rem 0.3rem; border-bottom: 1px solid var(--border); }

/* Spell list */
.spell-row {
  display: flex; align-items: center; justify-content: space-between;
  padding: 0.25rem 0.4rem; border-radius: 4px; cursor: pointer;
  font-size: 0.82rem; border: 1px solid transparent;
  transition: background 0.1s;
}
.spell-row:hover { background: var(--surface2); }
.spell-row.expanded { border-color: var(--border); background: var(--surface2); }
.spell-row-name { font-weight: 600; }
.locked-spell-tag {
  margin-left: 4px; font-size: 0.6rem; font-weight: 800;
  color: var(--accent); border: 1px solid var(--accent);
  border-radius: 3px; padding: 0 3px; vertical-align: 1px; white-space: nowrap;
}
.locked-spell-uses {
  margin-left: 4px; font-size: 0.62rem; font-weight: 600;
  color: var(--text-muted); border: 1px solid var(--border);
  border-radius: 3px; padding: 0 3px; vertical-align: 1px; white-space: nowrap;
}
.locked-spell-use {
  margin-left: 5px; font-size: 0.6rem; font-weight: 600; cursor: pointer;
  color: var(--accent); border: 1px solid var(--accent);
  background: transparent; border-radius: 3px; padding: 0 4px;
  vertical-align: 1px; white-space: nowrap;
}
.locked-spell-use:hover { background: var(--accent); color: var(--on-accent); }
/* A larger hit area than the button draws: a slightly-off click used to land on
   the row and open the spell instead of casting it. */
.locked-spell-use { position: relative; }
.locked-spell-use::after { content: ""; position: absolute; inset: -7px -6px; }
/* "Free": the next click spends the free cast, not a slot -- a different look,
   so the first click on a 1/LR spell visibly does something. */
.locked-spell-use.free,
.mobile-sheet-chip.free {
  color: var(--success); border-color: var(--success);
}
.locked-spell-use.free:hover { background: var(--success); color: var(--bg); }

/* The slot (or free-cast tag) a cast just spent pulses once, so a cast that went
   to a higher-level slot is seen where it landed. */
@keyframes slot-flash {
  0% { box-shadow: 0 0 0 0 var(--accent-glow); transform: scale(1); }
  40% { box-shadow: 0 0 0 6px transparent; transform: scale(1.25); }
  100% { box-shadow: 0 0 0 0 transparent; transform: scale(1); }
}
.slot-flash { animation: slot-flash .6s ease-out; }
.locked-spell-use.used {
  color: var(--text-muted); border-color: var(--text-muted);
  opacity: .55; cursor: default;
}
.locked-spell-use.used:hover,
.locked-spell-use:disabled:hover { background: transparent; color: var(--text-muted); }

/* The free-cast tag ("1/LR") is also its toggle: available reads as a tag,
   spent is struck through and dimmed; either way a click flips it. */
button.locked-spell-uses,
button.mobile-spell-uses {
  font-family: var(--font);
  background: transparent;
  cursor: pointer;
}
button.locked-spell-uses:hover { border-color: var(--accent); color: var(--text); }
.locked-spell-uses.spent,
.mobile-spell-uses.spent {
  text-decoration: line-through;
  opacity: .55;
}
.spell-detail {
  display: none; padding: 0.5rem 0.4rem 0.4rem;
  font-size: 0.78rem; color: var(--text-muted); line-height: 1.5;
  border-left: 2px solid var(--accent-gold); margin: 0.1rem 0 0.4rem 0.4rem;
  padding-left: 0.6rem;
}
.spell-detail.visible { display: block; }
.spell-detail-meta { display: flex; gap: 1rem; flex-wrap: wrap; margin-bottom: 0.4rem; }
.spell-detail-meta span { font-size: 0.72rem; }
.spell-detail-meta strong { color: var(--text); }
.spell-detail-mech { display: flex; flex-wrap: wrap; gap: 4px 16px; margin: 4px 0 6px 0; font-size: 0.85em; }

/* Hit die helper */
.hit-die-helper {
  display: none; background: var(--surface2); border: 1px solid var(--border);
  border-radius: 6px; padding: 0.6rem; font-size: 0.82rem;
}
.hit-die-helper.visible { display: block; }
.hit-die-helper p { margin: 0 0 0.4rem; color: var(--text-muted); }
.hit-die-helper .hd-row { display: flex; gap: 0.5rem; align-items: center; }
.hit-die-helper input[type=number] {
  width: 52px; text-align: center; padding: 0.25rem;
  background: var(--bg); border: 1px solid var(--border); border-radius: 4px; color: var(--text);
}

/* Spell slot group label */
.slot-group { display: flex; align-items: center; gap: 0.5rem; margin-bottom: 0.2rem; }
.slot-group-label { font-size: 0.75rem; color: var(--text-muted); min-width: 3.5rem; }

/* ── Diagnostics: what the engine read and did not apply ──────────────────────
   The visual rule this block exists to enforce is stated at length in play.js,
   above diagnosticGroups(): a diagnostic is an ABSTENTION, not an error. So
   every colour here comes from --badge-info-*, the token pair already spoken
   for informational asides, and never from --danger or the amber-to-terracotta
   carry-band ramp. Borrowing either would tell a player their sheet is broken
   at the one moment it is provably right. */
.locked-diag-chip {
  display: inline-block;
  margin-left: 6px;
  padding: 0 5px;
  border: 1px solid color-mix(in srgb, var(--badge-info-fg) 45%, transparent);
  border-radius: 999px;
  background: var(--badge-info-bg);
  color: var(--badge-info-fg);
  font-size: 9px;
  font-weight: 900;
  letter-spacing: .06em;
  text-transform: uppercase;
  vertical-align: middle;
  white-space: nowrap;
}


/* Homebrew item choices (static/js/item-choices.js) and display-only notes. */
.ic-host { flex-basis: 100%; padding: 6px 10px 8px; }
.ic-group { border: 1px solid var(--border, rgba(128,128,128,.35)); border-radius: 6px; margin: 4px 0; padding: 4px 10px 6px; }
.ic-group legend { font-size: 12px; font-weight: 600; padding: 0 4px; }
.ic-count { font-weight: 400; color: var(--text-muted); }
.ic-mode { font-weight: 600; color: var(--accent); }
.ic-opt { display: inline-flex; align-items: center; gap: 4px; margin: 2px 12px 2px 0; font-size: 13px; cursor: pointer; }
.ic-pill { margin-left: 4px; }
.situational-note { display: flex; flex-direction: column; gap: 2px; font-size: 12px; font-style: italic; color: var(--text-muted); }
.locked-sense b { font-size: 14px; }
.ic-opt input { width: auto; height: auto; min-width: 0; margin: 0; padding: 0; flex: 0 0 auto; }
.ic-group { min-width: 0; }
.ic-group legend { float: none; width: auto; margin: 0; }

/* Phone and tablet sheet: a very long name stops at two lines (full name on long-press/hover via title). */
@media (max-width: 899px) {
  .mobile-sheet-identity h1 { display: -webkit-box; -webkit-line-clamp: 2; -webkit-box-orient: vertical; overflow: hidden; overflow-wrap: anywhere; }
  .mobile-sheet-identity > div:first-child { min-width: 0; }
}

/* ── Overlay shell (static/js/play-overlay.js) ──────────────────────────────
   The live sheet's side panels. One handle per panel on the right
   edge, stacked from mid-height down (--ov-slot, set by the shell); a 330px panel (the Active Conditions box's width) that
   slides over the sheet and closes only by its × or the handle. Dressed like a
   .locked-play-box. z-index 45: above the sheet, under the user menu (50), the
   mobile drawers (70/80) and the condition tip (1000). */
.ov-handle {
  position: fixed;
  /* The shell measures every handle and sets --ov-y (the centre, in px) so none overlaps another; the slot
     offset is only the fallback before the first measure. */
  top: var(--ov-y, calc(50% + var(--ov-slot, 0) * 132px));
  right: 0;
  z-index: 45;
  display: grid;
  justify-items: center;
  gap: 6px;
  padding: 12px 6px 10px 7px;
  border: 1px solid var(--border-strong);
  border-right: 0;
  border-radius: 12px 0 0 12px;
  background: linear-gradient(180deg, var(--surface), var(--surface2));
  box-shadow: -6px 8px 22px rgba(0,0,0,.28);
  color: var(--accent);
  font-family: var(--font);
  cursor: pointer;
  /* Closed, it is a 12px strip at the screen edge so it covers nothing on the
     sheet; hovering or focusing it slides the whole tab out. The strip keeps
     the live / your-turn colour (::before), so it still signals while tucked. */
  transform: translateY(-50%) translateX(calc(100% - 12px));
  transition: right .22s cubic-bezier(.2,.8,.3,1), transform .18s cubic-bezier(.2,.8,.3,1),
              background-color .15s ease;
}

.ov-handle:hover,
.ov-handle:focus-visible,
.ov-handle.open { transform: translateY(-50%); }

.ov-handle:hover { background: var(--surface2); }
.ov-handle:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }
.ov-handle[hidden] { display: none; }
.ov-handle.open { right: min(var(--ov-w, 330px), 92vw); }

.ov-handle::before {
  content: "";
  position: absolute;
  top: 12px;
  bottom: 12px;
  left: 4px;
  width: 3px;
  border-radius: 999px;
  background: var(--border-strong);
  transition: opacity .15s ease;
}

.ov-handle.live::before { background: var(--success); }
.ov-handle.attention::before { background: var(--accent); }

/* Out of the way once the tab itself is showing. */
.ov-handle:hover::before,
.ov-handle:focus-visible::before,
.ov-handle.open::before { opacity: 0; }

/* Tucked, the tab's contents stay hidden so the 12px strip shows only its
   coloured bar -- no sliver of the icon or the label. They fade in as the tab
   slides out (hover, keyboard focus) and stay while the drawer is open. */
.ov-handle-icon,
.ov-handle-label,
.ov-handle-count,
.ov-handle-dot {
  opacity: 0;
  transition: opacity .15s ease;
}

.ov-handle:hover :is(.ov-handle-icon, .ov-handle-label, .ov-handle-count, .ov-handle-dot),
.ov-handle:focus-visible :is(.ov-handle-icon, .ov-handle-label, .ov-handle-count, .ov-handle-dot),
.ov-handle.open :is(.ov-handle-icon, .ov-handle-label, .ov-handle-count, .ov-handle-dot) {
  opacity: 1;
  transition-delay: .05s;
}

.ov-handle-icon { font-family: var(--font-emoji); font-size: 15px; line-height: 1; }

/* A count (the summons that are out) as a small badge under the label. */
.ov-handle-count {
  min-width: 18px;
  padding: 1px 5px;
  border-radius: 999px;
  background: var(--accent);
  color: var(--on-accent);
  font-size: 10px;
  font-weight: 900;
  line-height: 14px;
  text-align: center;
}
.ov-handle-count[hidden] { display: none; }

/* Four handles on a short screen: smaller handles, so they still stack without touching. */
.ov-compact .ov-handle { gap: 4px; padding: 8px 5px 7px 6px; }
.ov-compact .ov-handle-label { font-size: 9px; letter-spacing: .06em; }
.ov-compact .ov-handle-icon { font-size: 13px; }

/* Still too tall (a phone on its side): icon, count and dot only, no vertical label. The hover card and the
   aria-label still carry the name. */
.ov-mini .ov-handle { gap: 3px; padding: 6px 5px 6px 6px; }
.ov-mini .ov-handle-label { display: none; }
.ov-mini .ov-handle-icon { font-size: 15px; }
.ov-mini .ov-handle.attention .ov-handle-dot { box-shadow: 0 0 0 3px color-mix(in srgb, var(--accent) 30%, transparent); }

.ov-handle-label {
  writing-mode: vertical-rl;
  transform: rotate(180deg);
  font-size: 10px;
  font-weight: 900;
  letter-spacing: .14em;
  text-transform: uppercase;
}

/* Live: an encounter is running. My turn: it is this character's. */
.ov-handle-dot {
  width: 7px;
  height: 7px;
  border-radius: 50%;
  background: var(--border-strong);
}

.ov-handle.live .ov-handle-dot { background: var(--success); box-shadow: 0 0 0 3px color-mix(in srgb, var(--success) 25%, transparent); }

.ov-handle.attention {
  border-color: var(--accent);
  animation: ov-pulse 1.6s ease-in-out infinite;
}

.ov-handle.attention .ov-handle-dot { background: var(--accent); }
.ov-handle.attention .ov-handle-label::after { content: " !"; }

@keyframes ov-pulse {
  0%, 100% { box-shadow: -6px 8px 22px rgba(0,0,0,.28), 0 0 0 0 var(--accent-glow); }
  50% { box-shadow: -6px 8px 22px rgba(0,0,0,.28), 0 0 0 7px transparent; }
}

/* Hover and keyboard focus grow the handle: its name appears large on a label to the left of the handle. The label is
   absolutely positioned, so it takes no room in the stack the shell measures (play-overlay.js places the handles by
   their resting size) and never pushes or covers a neighbour: it is shorter than the handle it belongs to and
   centred on it. Pointers only, desktop width: the phone's 26px tab is unchanged, and a touch screen has no hover. */
.ov-handle-tip { display: none; }
@media (hover: hover) and (pointer: fine) and (min-width: 900px) {
  .ov-handle-tip {
    position: absolute;
    top: 50%;
    right: calc(100% - 1px);
    display: flex;
    align-items: center;
    gap: 10px;
    padding: 11px 18px 11px 14px;
    border: 1px solid var(--accent);
    border-right: 0;
    border-radius: 14px 0 0 14px;
    background: var(--surface2);
    color: var(--accent);
    box-shadow: -8px 8px 24px rgba(0,0,0,.3);
    font-family: var(--font);
    font-size: 16px;
    font-weight: 800;
    line-height: 1;
    letter-spacing: .02em;
    white-space: nowrap;
    opacity: 0;
    pointer-events: none;
    transform: translate(12px, -50%) scale(.94);
    transform-origin: right center;
    transition: opacity .14s ease, transform .18s cubic-bezier(.2,.8,.3,1);
  }
  .ov-handle:not(.open):hover .ov-handle-tip,
  .ov-handle:not(.open):focus-visible .ov-handle-tip {
    opacity: 1;
    pointer-events: auto;
    transform: translate(0, -50%);
  }
  .ov-tip-icon { font-family: var(--font-emoji); font-size: 22px; line-height: 1; }
  .ov-tip-text { display: grid; gap: 5px; }
  /* The state in one line under the name: the form and the time left, how many are out, whose turn. Body text size and
     the normal text colour, so it reads on the card as well as the name does. */
  .ov-tip-status {
    width: max-content;   /* an absolutely positioned card is only as wide as the 35px handle offers: ask for the text's own width */
    max-width: 260px;
    color: var(--text);
    font-size: 13px;
    font-weight: 600;
    letter-spacing: 0;
    line-height: 1.3;
    white-space: normal;
  }
  .ov-tip-status:empty { display: none; }
  .ov-handle.attention .ov-tip-label::after { content: " !"; }
}
@media (prefers-reduced-motion: reduce) {
  .ov-handle-tip { transition: none; }
  .ov-handle { transition: none; }
  .ov-handle.attention { animation: none; }
}

/* A drawer is open (desktop): the other handles stand out beside it, at its left edge, instead of lying under it or on
   top of its scrollbar. They stay one tap away. Phones hide them and offer the drawer's switcher instead. */
@media (min-width: 900px) {
  #play-overlays.ov-has-open .ov-handle:not(.open) {
    right: min(var(--ov-open-w, 330px), 92vw);
    transform: translateY(-50%);
  }
  #play-overlays.ov-has-open .ov-handle:not(.open) :is(.ov-handle-icon, .ov-handle-label, .ov-handle-count, .ov-handle-dot) { opacity: 1; }
  #play-overlays.ov-has-open .ov-handle:not(.open)::before { opacity: 0; }
}

.ov-drawer {
  position: fixed;
  top: 60px;
  right: 0;
  bottom: 12px;
  z-index: 45;
  display: flex;
  flex-direction: column;
  width: min(var(--ov-w, 330px), 92vw);
  box-sizing: border-box;
  border: 1px solid var(--border-strong);
  border-right: 0;
  border-radius: 14px 0 0 14px;
  background: linear-gradient(180deg, var(--surface), var(--surface2));
  box-shadow: -14px 0 34px rgba(0,0,0,.3);
  color: var(--text);
  font-family: var(--font);
  transform: translateX(calc(100% + 24px));
  visibility: hidden;
  transition: transform .22s cubic-bezier(.2,.8,.3,1), visibility 0s linear .22s;
}

.ov-drawer.open {
  transform: none;
  visibility: visible;
  transition: transform .22s cubic-bezier(.2,.8,.3,1), visibility 0s;
}

@media (prefers-reduced-motion: reduce) {
  .ov-drawer, .ov-drawer.open { transition: none; }
}

.ov-head {
  display: flex;
  align-items: center;
  gap: 6px;
  padding: 9px 10px 8px 12px;
  border-bottom: 1px solid var(--border);
}

.ov-titles { display: grid; flex: 1; min-width: 0; }

.ov-titles h2 {
  margin: 0;
  color: var(--accent);
  font-size: 11px;
  font-weight: 900;
  letter-spacing: .12em;
  text-transform: uppercase;
}

.ov-subtitle {
  overflow: hidden;
  color: var(--text-muted);
  font-size: 11px;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.ov-btn {
  min-height: 28px;
  padding: 3px 10px;
  border: 1px solid var(--border-strong);
  border-radius: 999px;
  background: var(--input-bg);
  color: var(--text);
  font-family: var(--font);
  font-size: 11px;
  font-weight: 800;
  cursor: pointer;
}

.ov-btn:hover { border-color: var(--accent); }
.ov-btn:disabled { opacity: .6; cursor: progress; }
.ov-close { width: 28px; padding: 0; font-size: 16px; line-height: 1; }

.ov-body {
  flex: 1;
  min-height: 0;
  overflow-y: auto;
  padding: 10px 12px 14px;
}

.penc-empty {
  display: grid;
  gap: 6px;
  padding: 22px 14px;
  border: 1px dashed var(--border-strong);
  border-radius: 10px;
  color: var(--text-muted);
  font-size: 12px;
  line-height: 1.45;
  text-align: center;
}

.penc-empty b { color: var(--text); font-size: 13px; }

.penc-view { display: grid; gap: 8px; }

.penc-name {
  overflow-wrap: anywhere;
  font-family: var(--font-display);
  font-size: 18px;
  font-weight: 700;
  line-height: 1.15;
}

.penc-turn {
  display: flex;
  flex-wrap: wrap;
  align-items: baseline;
  gap: 4px 12px;
  padding: 7px 10px;
  border: 1px solid var(--accent);
  border-radius: 10px;
  background: var(--accent-soft);
  font-size: 12px;
}

.penc-turn b { color: var(--accent); }
.penc-turn.mine { border-width: 2px; }

.penc-yourturn {
  padding: 1px 8px;
  border-radius: 999px;
  background: var(--accent);
  color: var(--on-accent) !important;
  font-size: 11px;
  letter-spacing: .06em;
  text-transform: uppercase;
}

/* The full page's rows, compacted for a 330px column. */
.penc-list { gap: 7px; padding-top: 2px; }

.penc-cmb.cev-cmb {
  gap: 5px;
  padding: 6px 8px;
  border-left-width: 4px;
  border-radius: 9px;
}

.penc-cmb.cev-cmb.current {
  border-width: 1px;
  border-left-width: 6px;
  box-shadow: 0 0 0 2px var(--accent-soft);
}

.penc-cmb.mine:not(.current) { border-left-color: var(--success); }

.penc-cmb .cev-top { grid-template-columns: 26px minmax(0, 1fr) auto; gap: 7px; }
.penc-cmb .cev-init { font-size: 15px; }
.penc-cmb .cev-name,
.penc-cmb.current .cev-who .cev-name { font-size: 13px; }
.penc-cmb .cev-who { gap: 5px; }
.penc-cmb .cmp-badge { padding: 0 6px; font-size: 10px; }
.penc-cmb .cmp-bar { height: 5px; }
.penc-cmb .cev-chips { gap: 4px; }

.penc-hp { color: var(--text-muted); font-size: 11px; font-weight: 800; white-space: nowrap; }
.penc-you { color: var(--on-accent); background: var(--success); border-color: var(--success); }

.penc-full {
  justify-self: start;
  margin-top: 4px;
  color: var(--accent);
  font-size: 12px;
  font-weight: 700;
}

/* ── Number steppers (coins) ─────────────────────────────────────────────────
   numberStepperHTML in play.js. The box carries the field's border and fill;
   the input inside is bare, its native spinners hidden; two small arrow buttons
   stack on the right in the sheet's own colours. */
.num-stepper {
  display: inline-flex;
  align-items: stretch;
  width: 72px;
  max-width: 100%;
  box-sizing: border-box;
  overflow: hidden;
  border: 1px solid var(--border-strong);
  border-radius: 8px;
  background: var(--input-bg);
  transition: border-color .15s ease;
}

.num-stepper:hover { border-color: color-mix(in srgb, var(--accent) 55%, var(--border-strong)); }
.num-stepper:focus-within { border-color: var(--accent); }

.num-stepper input[type="number"],
.locked-coin .num-stepper input[type="number"],
.locked-coin-remove .num-stepper input[type="number"] {
  flex: 1;
  width: auto;
  min-width: 0;
  margin: 0;
  padding: 4px 2px 4px 6px;
  border: 0;
  border-radius: 0;
  background: transparent;
  color: var(--text);
  font: inherit;
  font-size: 12px;
  font-weight: 800;
  text-align: center;
  outline: none;
  -moz-appearance: textfield;
  appearance: textfield;
}

.num-stepper input[type="number"]::-webkit-inner-spin-button,
.num-stepper input[type="number"]::-webkit-outer-spin-button {
  -webkit-appearance: none;
  margin: 0;
}

.num-stepper-btns {
  display: grid;
  grid-template-rows: 1fr 1fr;
  width: 18px;
  flex: none;
  border-left: 1px solid var(--border-strong);
}

.num-stepper-btns button {
  display: grid;
  place-items: center;
  min-height: 0;
  padding: 0;
  border: 0;
  border-radius: 0;
  background: color-mix(in srgb, var(--surface2) 70%, transparent);
  color: var(--accent);
  font-size: 7px;
  line-height: 1;
  cursor: pointer;
  transition: background-color .12s ease, color .12s ease;
}

.num-stepper-btns button + button { border-top: 1px solid var(--border-strong); }
.num-stepper-btns button:hover { background: var(--accent-soft); }
.num-stepper-btns button:active { background: var(--accent); color: var(--on-accent); }

/* In the Remove row the stepper is a flex item: keep it from shrinking away. */
.locked-coin-remove .num-stepper { flex: none; width: 64px; }

/* Phones: wider field, taller tap targets. */
.mobile-coin-field .num-stepper,
.mobile-coin-remove .num-stepper { width: 100%; min-height: 40px; border-radius: 10px; }
.mobile-coin-remove .num-stepper { width: 96px; }
.mobile-coin-field .num-stepper input[type="number"],
.mobile-coin-remove .num-stepper input[type="number"] { font-size: 15px; min-height: 0; padding: 6px 4px 6px 10px; }
.mobile-coin-field .num-stepper-btns,
.mobile-coin-remove .num-stepper-btns { width: 30px; }
.mobile-coin-field .num-stepper-btns button,
.mobile-coin-remove .num-stepper-btns button { font-size: 9px; }

/* ── Rarity line in an expanded item ─────────────────────────────────────────
   "Uncommon 5.5E — Requires Attunement". It wears the generic .rarity-* badge
   classes (style-components.css: a filled, square box). On the sheet it is a
   rounded pill like the chips beside it; fill and border are derived from the
   rarity's own text colour, so every rarity and both themes follow. */
.locked-detail-rarity[class*="rarity-"] {
  display: inline-flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 2px 6px;
  width: fit-content;
  max-width: 100%;
  box-sizing: border-box;
  padding: 2px 10px;
  border: 1px solid color-mix(in srgb, currentColor 45%, transparent);
  border-radius: 999px;
  background: color-mix(in srgb, currentColor 12%, transparent);
  font-size: 11px;
  font-weight: 700;
  line-height: 1.35;
}

.locked-detail-rarity strong { font-weight: 900; letter-spacing: .02em; }

.locked-detail-rarity .edition-badge {
  margin: 0;
  padding: 0 6px;
  border-radius: 999px;
  font-size: 9px;
  line-height: 1.5;
}

/* ── Spell source chips ──────────────────────────────────────────────────────
   spellSourceChipHTML in play.js: who gave a spell (Chosen, Class, Subclass,
   Species, Feat, Item, Invocation, Homebrew, Option), the exact source in the
   styled tip. One colour per kind from the existing palette tokens, so both
   themes follow; "Chosen" stays quiet because it is the common case. */
.spell-src {
  --src: var(--text-muted);
  display: inline-block;
  flex: none;
  margin-left: 0;
  padding: 0 6px;
  border: 1px solid color-mix(in srgb, var(--src) 55%, transparent);
  border-radius: 999px;
  background: color-mix(in srgb, var(--src) 12%, transparent);
  color: var(--src);
  font-size: 8px;
  font-weight: 900;
  letter-spacing: .06em;
  line-height: 1.6;
  text-transform: uppercase;
  white-space: nowrap;
  cursor: help;
  vertical-align: 1px;
}

.spell-src:focus-visible { outline: 2px solid var(--accent); outline-offset: 1px; }

.spell-src-chosen { --src: var(--text-faint); background: transparent; }
.spell-src-class { --src: var(--accent); }
.spell-src-subclass { --src: var(--marker-expertise); }
.spell-src-species { --src: var(--success); }
.spell-src-feat { --src: var(--tag-immunity); }
.spell-src-item { --src: var(--rarity-rare-fg); }
.spell-src-invocation { --src: var(--badge-arcane-fg); }
.spell-src-homebrew { --src: var(--warn); }
.spell-src-granted { --src: var(--accent); }
.spell-src-list { --src: var(--text-faint); border-style: dashed; background: transparent; }

.spell-src-line { margin: 0 0 6px; color: var(--text-muted); font-size: 11px; }
.spell-src-line b { color: var(--text); }

/* The From cell holds the chip whole: it is a label, not text to cut. */
.locked-spell-row .spell-src { padding: 0 5px; letter-spacing: .03em; }
.locked-spell-row.spell-row > span.spell-src-cell { overflow: visible; text-overflow: clip; }

/* The cast cell: the Use button, right-aligned, never cut. */
.locked-spell-row.spell-row > span.spell-cast-cell {
  display: flex;
  justify-content: flex-end;
  align-items: center;
  gap: 3px;
  overflow: visible;
  text-overflow: clip;
}
.spell-cast-cell .locked-spell-uses,
.spell-cast-cell .locked-spell-use { margin-left: 0; }
.mobile-sheet-spell .spell-src { margin-left: 4px; }

.locked-upcast-tag { color: var(--accent, #d4a843); font-size: 11px; font-weight: 600; white-space: nowrap; }
/* The name column is narrow: on an upcast row the spell name keeps its own line and the "(upcast N)" marker
   (with the C / R tags) sits under it, so neither is cut to make room for the other. */
.locked-spell-row.spell-row.locked-upcast-row > .spell-row-name { flex-wrap: wrap; row-gap: 0; }
.locked-upcast-row .spell-row-name .spell-name-text { flex: 1 1 100%; }

/* An upcast row's Damage and Effect cells hold the figure ("9d6 fire") and wrap like every other cell: two lines at most,
   the whole figure in the tip and in the expanded row ("Effect at this level"). */
.locked-spell-row.spell-row.locked-upcast-row > span.upcast-dmg-cell,
.locked-spell-row.spell-row.locked-upcast-row > span.upcast-effect-cell {
  white-space: normal;
  overflow: hidden;
  overflow-wrap: break-word;
  line-height: 1.25;
}
/* The rest of what the row says (summon AC / HP chips, creature counts, extra targets, the sentence of the rule)
   sits on a full-width line under the cells: wrapped, two lines at most, clear of the name and the Use button.
   The whole sentence is the first thing in the expanded row. */
.locked-spell-row.spell-row.locked-upcast-row > .locked-upcast-more {
  grid-column: 1 / -1;
  display: flex;
  flex-wrap: wrap;
  align-items: baseline;
  gap: 3px 10px;
  min-width: 0;
  padding: 0 0 2px;
  white-space: normal;
  color: var(--text-muted);
  line-height: 1.35;
}
.locked-upcast-chips { display: inline-flex; flex-wrap: wrap; align-items: center; gap: 3px 4px; }
.locked-upcast-lead { color: var(--text-muted); font-weight: 600; }
.locked-upcast-chip {
  padding: 0 6px;
  border: 1px solid var(--border-strong, var(--border));
  border-radius: 999px;
  background: color-mix(in srgb, var(--accent) 9%, transparent);
  color: var(--text);
  font-weight: 600;
  white-space: nowrap;
}
.locked-upcast-note {
  flex: 1 1 16em;
  min-width: 0;
  display: -webkit-box;
  -webkit-box-orient: vertical;
  -webkit-line-clamp: 2;
  line-clamp: 2;
  overflow: hidden;
  overflow-wrap: anywhere;
}
.locked-upcast-note b { color: var(--accent, #d4a843); font-weight: 600; }
.spell-upcast-detail {
  margin: 0 0 8px;
  padding: 6px 9px;
  border-left: 2px solid var(--accent, #d4a843);
  background: color-mix(in srgb, var(--accent) 7%, transparent);
  color: var(--text);
  line-height: 1.45;
}
.spell-upcast-detail > b { color: var(--accent, #d4a843); }
.spell-upcast-detail p { margin: 4px 0 0; }

/* The creature drawers (play-overlay-wildshape/summons/companions.js): rows inside the shell's drawer body. */
.pcr-row {
  display: grid;
  gap: 6px;
  padding: 8px 10px;
  border: 1px solid var(--border);
  border-radius: 10px;
  background: var(--surface);
}

/* A downed creature keeps full-strength controls (Heal is the point); only its name and bar recede. */
.pcr-row.down { border-style: dashed; }
.pcr-row.down .pcr-name, .pcr-line.down .pcr-name { color: var(--text-muted); }
.pcr-down {
  align-self: center;
  padding: 0 7px;
  border: 1px solid var(--warn);
  border-radius: 999px;
  color: var(--warn);
  font-size: 10px;
  font-weight: 900;
  letter-spacing: .08em;
  text-transform: uppercase;
}
.pcr-top .pcr-down { margin-right: auto; }
.pcr-notice {
  padding: 8px 10px;
  border: 1px solid var(--warn);
  border-radius: 10px;
  background: var(--warn-soft);
  color: var(--text);
  font-size: 12px;
  line-height: 1.4;
}
.pcr-top { display: flex; align-items: baseline; justify-content: space-between; gap: 8px; }
.pcr-name { overflow-wrap: anywhere; font-family: var(--font-display); font-size: 15px; font-weight: 700; }
.pcr-hp { font-size: 12px; font-variant-numeric: tabular-nums; white-space: nowrap; }
.pcr-bar { height: 6px; }
.pcr-chips { display: flex; flex-wrap: wrap; gap: 4px; }
.pcr-controls, .pcr-foot { display: flex; flex-wrap: wrap; gap: 6px; align-items: center; }

.pcr-controls input.pcr-amount {
  width: 64px;
  padding: 3px 6px;
  border: 1px solid var(--border-strong);
  border-radius: 6px;
  background: var(--bg);
  color: var(--text);
  font-size: 12px;
}

.pcr-btn { padding: 3px 8px; font-size: 12px; }
.pcr-btn:focus-visible, .pcr-amount:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }

/* Phones: a 12px strip is not a thumb target. Show 26px of the tab, icon included, and let the
   other tabs step aside while a drawer is open (they would sit on its scrollbar). */
@media (max-width: 899px) {
  .ov-handle { transform: translateY(-50%) translateX(calc(100% - 26px)); }
  .ov-handle:hover, .ov-handle:focus-visible, .ov-handle.open { transform: translateY(-50%); }
  .ov-handle-icon, .ov-handle-count { opacity: 1; }
  .ov-handle::before { left: 11px; }
  #play-overlays.ov-has-open .ov-handle:not(.open) { visibility: hidden; }
}

/* Phones and touch screens: every control in the side panels reaches the 44px touch target. */
@media (max-width: 899px), (pointer: coarse) {
  .ov-drawer .ov-btn { min-height: 44px; padding: 6px 14px; font-size: 13px; }
  .ov-drawer .ov-close { width: 44px; }
  .pcr-controls input.pcr-amount { min-height: 44px; width: 84px; font-size: 16px; }
  .pcr-btn { padding: 6px 14px; font-size: 13px; }
}
.pcr-statblock { margin-top: 2px; }

/* The frame the Tamer's dialogs (play-tamer.js) are drawn in: a modal outside #play-sheet, so a sheet render never wipes it. */
.pcr-note { color: var(--text-muted); font-size: 11px; font-weight: 700; }

.pws-backdrop {
  position: fixed;
  inset: 0;
  z-index: 9500;
  display: flex;
  align-items: center;
  justify-content: center;
  padding: 12px;
  background: rgba(0, 0, 0, .55);
}

.pws-dialog {
  display: flex;
  flex-direction: column;
  gap: 8px;
  width: min(980px, 100%);
  max-height: calc(100vh - 24px);
  box-sizing: border-box;
  padding: 12px 14px;
  border: 1px solid var(--border-strong);
  border-radius: 14px;
  background: var(--surface);
  color: var(--text);
  font-family: var(--font);
  box-shadow: 0 24px 60px rgba(0, 0, 0, .45);
}

.pws-head { display: flex; align-items: center; justify-content: space-between; gap: 8px; }
.pws-head h2 { margin: 0; color: var(--accent); font-family: var(--font-display); font-size: 20px; }

.pws-limits {
  display: grid;
  gap: 2px;
  margin: 0;
  padding: 8px 10px 8px 26px;
  border: 1px solid var(--border);
  border-radius: 10px;
  background: var(--surface2);
  font-size: 12px;
}

.pws-tabs { display: flex; gap: 6px; }
.pws-tab {
  padding: 4px 12px;
  border: 1px solid var(--border-strong);
  border-radius: 999px;
  background: var(--input-bg);
  color: var(--text);
  font-family: var(--font);
  font-size: 12px;
  font-weight: 800;
  cursor: pointer;
}
.pws-tab.on { border-color: var(--accent); color: var(--accent); }

.pws-cols { display: grid; grid-template-columns: minmax(260px, 340px) 1fr; gap: 12px; min-height: 0; flex: 1; }
.pws-left, .pws-right { display: flex; flex-direction: column; gap: 6px; min-height: 0; min-width: 0; }
.pws-filters { display: flex; flex-wrap: wrap; gap: 6px; align-items: center; }
.pws-search, .pws-cr {
  padding: 4px 8px;
  border: 1px solid var(--border-strong);
  border-radius: 8px;
  background: var(--bg);
  color: var(--text);
  font-size: 13px;
}
.pws-search { flex: 1 1 120px; min-width: 0; }
/* In the Summon dialog the search sits straight in the left column, where a flex-basis would be a height. */
.pws-left > .pws-search { flex: none; }
.pws-toggle { display: inline-flex; align-items: center; gap: 4px; font-size: 12px; }
.pws-count { color: var(--text-muted); font-size: 11px; }
.pws-listbox { flex: 1; min-height: 120px; max-height: 46vh; overflow-y: auto; }
.pws-list { display: grid; gap: 4px; margin: 0; padding: 0; list-style: none; }
.pws-item { display: flex; align-items: stretch; gap: 6px; }
.pws-check { display: flex; align-items: center; }
.pws-pick {
  display: grid;
  flex: 1;
  gap: 1px;
  padding: 6px 10px;
  border: 1px solid var(--border);
  border-radius: 10px;
  background: var(--input-bg);
  color: var(--text);
  font-family: var(--font);
  text-align: left;
  cursor: pointer;
}
.pws-pick:hover { border-color: var(--accent); }
.pws-pick.on { border-color: var(--accent); background: color-mix(in srgb, var(--accent) 12%, var(--input-bg)); }
.pws-name { font-size: 14px; font-weight: 800; }
.pws-meta, .pws-note { color: var(--text-muted); font-size: 11px; }
.pws-note { color: var(--warn); font-weight: 800; }
.pws-empty { padding: 10px; color: var(--text-muted); font-size: 12px; }
.pws-managefoot { display: flex; align-items: center; justify-content: space-between; gap: 8px; }
.pws-managefoot[hidden], .pws-confirm[hidden] { display: none; }
.pws-known-count { font-size: 12px; font-weight: 800; }

.pws-stat { flex: 1; min-height: 120px; max-height: 52vh; overflow-y: auto; }
.pws-statblock { padding: 12px; border: 1px solid var(--border); border-radius: 10px; background: var(--bg); }
.pws-msg { min-height: 16px; color: var(--text-muted); font-size: 12px; font-weight: 700; }
.pws-msg.err { color: var(--badge-warn-fg); }
.pws-confirm { display: flex; gap: 8px; justify-content: flex-end; }
.pws-placeholder {
  display: grid;
  place-items: center;
  min-height: 120px;
  padding: 14px;
  border: 1px dashed var(--border-strong);
  border-radius: 10px;
  color: var(--text-muted);
  font-size: 12px;
  text-align: center;
}
.pws-group {
  position: sticky;
  top: 0;
  z-index: 1;
  padding: 4px 2px;
  background: var(--surface);
  color: var(--text-muted);
  font-size: 11px;
  font-weight: 900;
  letter-spacing: .08em;
  text-transform: uppercase;
}
.pws-pick:focus-visible, .pws-tab:focus-visible, .pws-search:focus-visible, .pws-cr:focus-visible,
.pws-toggle input:focus-visible, .pws-check input:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }
.pws-go { padding: 6px 16px; border-color: var(--accent); color: var(--accent); font-size: 13px; }

@media (max-width: 899px), (pointer: coarse) {
  .pws-dialog .ov-btn, .pws-tab { min-height: 44px; }
  .pws-pick { min-height: 44px; }
  .pws-search, .pws-cr { min-height: 44px; font-size: 16px; }
  .pws-toggle { min-height: 44px; font-size: 13px; }
  .pws-toggle input, .pws-check input { width: 22px; height: 22px; }
  .pws-check { padding: 0 6px; }
}

@media (max-width: 720px) {
  /* The dialog itself scrolls, so the list keeps a usable height under the limit box. */
  .pws-dialog { overflow-y: auto; }
  .pws-confirm, .pws-managefoot { position: sticky; bottom: 0; z-index: 2; padding: 6px 0 2px; background: var(--surface); }
  .pws-cols { grid-template-columns: 1fr; flex: none; }
  .pws-listbox { flex: none; min-height: 220px; max-height: 44vh; }
  .pws-stat { max-height: none; }
}

/* The Summon button inside an expanded spell description (play.js summonBoxHTML). It opens the Summons panel's set-up. */
.spell-summon-box { display: flex; flex-wrap: wrap; align-items: center; gap: 6px 12px; margin-top: 10px; padding-top: 8px; border-top: 1px dashed var(--border); }
.spell-summon-btn {
  display: inline-flex; align-items: center; gap: 6px;
  min-height: 32px; padding: 4px 14px;
  border: 1px solid var(--accent); border-radius: 999px;
  background: var(--accent-soft); color: var(--accent);
  font-family: var(--font); font-size: 13px; font-weight: 800; cursor: pointer;
}
.spell-summon-btn:hover { background: var(--accent); color: var(--on-accent); }
.spell-summon-btn:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }
@media (max-width: 899px), (pointer: coarse) { .spell-summon-btn { min-height: 44px; padding: 6px 18px; } }
.psm-field { display: flex; flex-direction: column; gap: 3px; }
.psm-label { color: var(--text-muted); font-size: 11px; font-weight: 800; text-transform: uppercase; letter-spacing: .04em; }
