/* ============================================================================
   Custom Game feature UI — the create/tweak wizard (create-modal.js) and the
   library management modal (library-modal.js). Both build on the shared
   .final-round-report-panel/-sheet wrapper (scorecard.css) for the outer
   modal chrome (backdrop, centered scrollable card, sticky header) — the
   rules below are feature-specific layout/spacing/button-hierarchy only.

   Button hierarchy: primary actions (Generate, Activate) use the EXISTING
   .btn.accent class (header.css) rather than the .btn-primary class that
   used to appear in this feature's markup — .btn-primary has no CSS rule
   anywhere in the app, so it rendered identically to a plain .btn with zero
   visual hierarchy. .btn.accent is already proven elsewhere in the app, so
   reusing it keeps this feature visually consistent with the rest of the UI
   instead of inventing a parallel "primary button" look.
   ============================================================================ */

/* ── Create/tweak wizard body ────────────────────────────────────────── */
.custom-game-create-body {
  display: flex;
  flex-direction: column;
  gap: 12px;
}

.custom-game-description-input {
  width: 100%;
  min-height: 90px;
  padding: 10px 12px;
  border: 1px solid var(--line);
  border-radius: var(--radius-md);
  background: var(--panel-alt);
  color: var(--ink);
  font-size: 14px;
  font-family: inherit;
  resize: vertical;
  box-sizing: border-box;
}

/* ── Guided creation form (2026-07-20) — sections asking directly for team
   structure / how it works / handicap / totals, instead of one free-text
   paragraph. Reuses the app's existing .hcp-mode-btn-group radio convention
   (banker.html's Scoring Mode/Par 3 Multiplier controls) so these look like
   every other in-app options radio group, not a new one-off pattern. ── */
.custom-game-form-section {
  display: flex;
  flex-direction: column;
  gap: 8px;
}

.custom-game-form-section .hcp-mode-btn-group + .hcp-mode-btn-group {
  margin-top: 6px;
}

/* [2026-07-20 wizard] One section shown at a time instead of all 6 at once
   -- direct instruction that the guided form was overwhelming as one long
   scroll. stepChrome wraps the step indicator, whichever sectionN is
   currently visible, and the Back/Next nav; hidden in one shot once
   Generate is clicked (see create-modal.js's own comment on why). */
.custom-game-step-chrome {
  display: flex;
  flex-direction: column;
  gap: 12px;
}

.custom-game-step-indicator {
  font-size: var(--text-sm, 13px);
  font-weight: 700;
  color: var(--muted);
}

.custom-game-step-nav {
  display: flex;
  justify-content: space-between;
  gap: 10px;
}

.custom-game-step-back {
  flex: 0 0 auto;
}

.custom-game-step-next {
  flex: 1 1 auto;
}

.custom-game-form-followup {
  display: flex;
}

.custom-game-form-followup:empty { display: none; }

.custom-game-team-size-input,
.custom-game-role-name-input,
.custom-game-inline-number,
.custom-game-text-input {
  width: 100%;
  padding: 8px 10px;
  border: 1px solid var(--line);
  border-radius: var(--radius-md);
  background: var(--panel-alt);
  color: var(--ink);
  font-size: 13px;
  font-family: inherit;
  box-sizing: border-box;
}

/* ── Guided form v2 (2026-07-20) — 6 sections, ~40 conditionally-revealed
   fields, informed by an audit of the app's own hand-coded games. A
   "followup cluster" is a group of controls that only makes sense once an
   earlier answer picked the right branch (e.g. team-formation questions
   only under Fixed Teams) — a light left border visually nests it under
   its parent choice without a heavy box-in-a-box look. ── */
.custom-game-form-followup-cluster {
  display: flex;
  flex-direction: column;
  gap: 8px;
  margin-top: 4px;
  padding-left: 10px;
  border-left: 2px solid color-mix(in srgb, var(--line) 70%, transparent);
}

.custom-game-conditional[hidden] { display: none; }

.custom-game-subfield-label {
  font-size: 12px;
  font-weight: 600;
  color: var(--muted);
  margin-top: 4px;
}

.custom-game-help-note {
  margin: 0;
  font-size: 12px;
  font-style: italic;
  color: var(--muted);
}

/* [2026-07-24, 3-page redesign] One short caption per radio option (e.g.
   the 4 handicap modes), replacing a single dense paragraph that tried to
   explain every option at once — a plain list is easier to scan than a
   run-on sentence, and doesn't require the radio-group primitive itself to
   know about per-option help text. */
.custom-game-option-captions {
  margin: 0;
  padding: 0 0 0 16px;
  display: flex;
  flex-direction: column;
  gap: 2px;
  font-size: 11.5px;
  color: var(--muted);
}

.custom-game-credits-note--empty {
  color: var(--danger, #e05252);
  font-style: normal;
  font-weight: 600;
}

.custom-game-buy-credits-btn {
  align-self: flex-start;
  margin-top: 2px;
  padding: 6px 12px;
  font-size: 0.82rem;
  font-weight: 600;
}

/* Checkbox rows — a single tap target (label wraps the native checkbox +
   its text) matching the app's existing large-tap-target mobile-first
   convention (junk-sheet.js's chip family, custom-game-tally-chip). */
.custom-game-checkbox-row {
  display: flex;
  align-items: flex-start;
  gap: 8px;
  padding: 6px 2px;
  cursor: pointer;
}

.custom-game-checkbox-row input[type="checkbox"] {
  flex: 0 0 auto;
  margin-top: 2px;
  width: 18px;
  height: 18px;
}

.custom-game-checkbox-text {
  display: flex;
  flex-direction: column;
  gap: 2px;
  min-width: 0;
}

.custom-game-checkbox-label {
  font-size: 13px;
  color: var(--ink);
}

.custom-game-checkbox-help {
  font-size: 11px;
  color: var(--muted);
}

.custom-game-checkbox-group {
  display: flex;
  flex-direction: column;
}

/* Repeatable add/remove rows (scoring table, achievements list, segments,
   per-hole stake overrides, role outcome values). */
.custom-game-repeatable-rows {
  display: flex;
  flex-direction: column;
  gap: 6px;
}

.custom-game-repeatable-rows-list {
  display: flex;
  flex-direction: column;
  gap: 6px;
}

.custom-game-repeatable-row {
  display: flex;
  gap: 6px;
  align-items: center;
}

.custom-game-repeatable-input {
  flex: 1 1 0;
  min-width: 0;
  padding: 7px 9px;
  border: 1px solid var(--line);
  border-radius: var(--radius-md);
  background: var(--panel-alt);
  color: var(--ink);
  font-size: 13px;
  font-family: inherit;
  box-sizing: border-box;
}

.custom-game-repeatable-remove {
  flex: 0 0 auto;
  width: 28px;
  height: 28px;
  padding: 0;
  color: var(--danger);
  border-color: color-mix(in srgb, var(--danger) 45%, transparent);
}

.custom-game-repeatable-add {
  align-self: flex-start;
  font-size: 12px;
  padding: 5px 10px;
}

/* Inputs editor (docs/js/games/custom/create-modal.js's appendInputsEditor)
   — label + reorder only, same row shape as the repeatable rows above. */
.custom-game-inputs-editor-wrap {
  display: flex;
  flex-direction: column;
  gap: 6px;
  margin: var(--space-md, 12px) 0;
}
.custom-game-inputs-editor-list {
  display: flex;
  flex-direction: column;
  gap: 6px;
}
.custom-game-inputs-editor-row {
  display: flex;
  gap: 6px;
  align-items: center;
}
.custom-game-inputs-editor-meta {
  flex: 0 0 auto;
  font-size: var(--text-xs);
  color: var(--muted);
  text-transform: uppercase;
  letter-spacing: 0.02em;
  min-width: 80px;
}
.custom-game-inputs-editor-label-input {
  flex: 1 1 0;
  min-width: 0;
  padding: 7px 9px;
  border: 1px solid var(--line);
  border-radius: var(--radius-md);
  background: var(--panel-alt);
  color: var(--ink);
  font-size: 13px;
  font-family: inherit;
  box-sizing: border-box;
}
.custom-game-inputs-editor-move {
  flex: 0 0 auto;
  width: 28px;
  height: 28px;
  padding: 0;
}
.custom-game-inputs-editor-move:disabled {
  opacity: 0.35;
  cursor: default;
}

/* Special Mechanics accordion — collapsed by default, matches the
   app-wide chevron-toggle convention (results-collapse-bar). */
.custom-game-accordion-toggle {
  width: 100%;
  text-align: left;
  font-weight: 700;
}

.custom-game-accordion-body {
  display: flex;
  flex-direction: column;
  gap: 10px;
  padding-top: 6px;
}

.custom-game-accordion-body[hidden] { display: none; }

/* Explicit "start from an included game" entry. This is a compact rules
   index, not a stack of cards: one divider row per shipped game, with the
   deterministic edit path kept distinct from the AI-credit action. */
.custom-game-entry-picker {
  display: flex;
  flex-direction: column;
  gap: 10px;
}

.custom-game-entry-picker h3 {
  margin: 0;
}

.custom-game-entry-note {
  margin: 0;
  color: var(--muted);
  font-size: 13px;
  line-height: 1.45;
}

.custom-game-entry-list {
  border-top: 1px solid var(--line-soft);
}

.custom-game-entry-row {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 16px;
  padding: 10px 0;
  border-bottom: 1px solid var(--line-soft);
}

.custom-game-entry-name {
  color: var(--ink);
  font-size: 14px;
}

.custom-game-entry-actions {
  display: flex;
  flex: 0 0 auto;
  flex-wrap: wrap;
  justify-content: flex-end;
  gap: 8px;
}

@media (max-width: 560px) {
  .custom-game-entry-row {
    align-items: stretch;
    flex-direction: column;
    gap: 8px;
  }

  .custom-game-entry-actions {
    justify-content: stretch;
  }

  .custom-game-entry-actions .btn {
    flex: 1 1 150px;
  }
}

.custom-game-status {
  margin: 0;
  min-height: 1.2em;
  color: var(--muted);
  font-size: 13px;
}

.custom-game-status:empty { display: none; }

/* Generating animation — shown while a Gemini call is in flight (Generate,
   Apply tweak, or the clarification Continue button all hit the same slow
   backend path, ~10-60s observed in prod across repair-loop rounds). A
   spinning ring plus a status line that cycles through a few short messages
   every couple seconds, so a long wait doesn't read as a stalled/frozen UI —
   same visual formula as .scorecard-import-spinner (scorecard-import.css)
   but kept local to this file per its own file-header scope comment, rather
   than reaching into a different feature's stylesheet for one class. */
.custom-game-generating {
  display: flex;
  align-items: center;
  gap: 10px;
}

.custom-game-generating-spinner {
  flex: 0 0 auto;
  width: 18px;
  height: 18px;
  border: 2px solid var(--line);
  border-top-color: var(--accent);
  border-radius: 50%;
  animation: customGameGeneratingSpin 0.8s linear infinite;
}

@keyframes customGameGeneratingSpin {
  to { transform: rotate(360deg); }
}

.custom-game-generating-text {
  flex: 1 1 auto;
  min-width: 0;
}

@media (prefers-reduced-motion: reduce) {
  .custom-game-generating-spinner {
    animation-duration: 2.4s;
  }
}

.custom-game-generate-btn {
  width: 100%;
}

.custom-game-preview {
  display: flex;
  flex-direction: column;
  gap: 4px;
}

.custom-game-preview:empty { display: none; }

.custom-game-preview h4 { margin: 0; }

.custom-game-preview ul {
  margin: 4px 0 0;
  padding-left: 18px;
}

/* "Example settlement" block (create-modal.js's appendExampleSettlement) —
   a synthetic worked example shown under the plain-English preview so a
   generated game's money math is visible before Generate/Save/Activate,
   not just its description. Deliberately set apart with its own divider
   (not just another .custom-game-preview <p>) since it's illustrative
   sample data, not part of the definition summary above it. */
.custom-game-example {
  display: flex;
  flex-direction: column;
  gap: 4px;
  margin-top: 4px;
  padding-top: 8px;
  border-top: 1px solid color-mix(in srgb, var(--line) 60%, transparent);
}

.custom-game-example-label { margin-bottom: 2px; }

.custom-game-example-hole-row {
  display: flex;
  align-items: baseline;
  flex-wrap: wrap;
  gap: 4px 8px;
}

.custom-game-example-hole-detail {
  display: flex;
  flex-wrap: wrap;
  gap: 2px 10px;
}

/* No `color` here, deliberately: a matchup chip (buildMatchupChip) carries
   .banker-total-positive/-negative on this SAME element to color the whole
   "wins/pays" sentence, and that class is defined in banker.css, which this
   stylesheet happens to be concatenated after -- equal specificity means
   whichever rule comes LAST in the cascade wins the `color` property, so a
   `color` set here would silently beat banker.css's sign coloring instead
   of layering under it. Plain delta chips (buildDeltaChip) don't need it
   either: they inherit the ambient ink color and put their OWN colored
   amount in a nested .custom-game-hole-result-amount span instead. */
.custom-game-example-chip {
  font-size: var(--text-sm, 13px);
  white-space: nowrap;
}

/* font-weight only — .custom-game-holes-note (applied alongside this class,
   see appendExampleSettlement) already sets color/size; it's defined later
   in this file, so a `color` override here would lose the cascade. */
.custom-game-example-final {
  margin-top: 2px;
  font-weight: 600;
}

/* Clarification interstitial — appears between Generate and the preview
   (or, mid-tweak, near the tweak section) when Gemini asks a question
   instead of returning a definition. Styled to stand out (accent border)
   since it's blocking further progress until answered. */
.custom-game-clarification-section {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 8px;
  padding: 10px 12px;
  border: 1px solid var(--accent-soft-line);
  border-radius: var(--radius-md);
  background: var(--accent-soft-bg);
}

.custom-game-clarification-section[hidden] { display: none; }

/* [wager-ledger, de-risk] Same "stand out, non-blocking" posture as the
   clarification section above -- appears after a generate/tweak response
   whose AI-authored "wagerSuggestion" prose implies the description wants
   live side-bets. Unlike clarification, this never blocks progress (both
   buttons just dismiss it -- Save/Activate work fine either way). */
.custom-game-wager-suggestion {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 8px;
  padding: 10px 12px;
  border: 1px solid var(--accent-soft-line);
  border-radius: var(--radius-md);
  background: var(--accent-soft-bg);
}

.custom-game-wager-suggestion[hidden] { display: none; }

.custom-game-wager-suggestion-text {
  flex: 1 1 100%;
  margin: 0;
  font-size: 14px;
  color: var(--ink);
}

.custom-game-clarification-question {
  flex: 1 1 100%;
  margin: 0;
  font-size: 14px;
  font-weight: 600;
  color: var(--ink);
}

.custom-game-clarification-input {
  flex: 1 1 auto;
  min-width: 0;
  height: 40px;
  padding: 0 12px;
  border: 1px solid var(--line);
  border-radius: var(--radius-md);
  background: var(--panel-alt);
  color: var(--ink);
  font-size: 14px;
  box-sizing: border-box;
}

/* Tweak row and the final Save/Activate row are visually separated from
   what's above them with a divider — makes the "input phase" vs "refine
   phase" vs "commit phase" grouping clear at a glance instead of everything
   just stacking with no structure. */
/* [2026-07-24, 3-page redesign] Results-display preference (show hole-by-
   hole breakdown / show bet detail) — relocated out of the paginated
   wizard into this persistent post-generation area, shared by both the
   create and edit flows (see create-modal.js's own comment on why one
   instance now covers both). Reuses .custom-game-form-followup-cluster's
   own left-border/padding for the "grouped box" look; .custom-game-tweak-
   section's border-top right below already provides the divider into the
   next phase, so this doesn't add a second one. */
.custom-game-results-display-section[hidden] { display: none; }

/* Edit mode's read-only reference to the original typed description —
   informational only (never an input), so it's styled as quiet body text
   rather than a form control. */
.custom-game-original-description {
  padding-bottom: 12px;
  border-bottom: 1px solid color-mix(in srgb, var(--line) 60%, transparent);
}

.custom-game-original-description[hidden] { display: none; }

.custom-game-original-description-text {
  margin: 4px 0 0;
  padding: 8px 10px;
  border-radius: var(--radius-md);
  background: var(--panel-alt);
  color: var(--muted);
  font-size: 13px;
  white-space: pre-wrap;
}

.custom-game-tweak-section {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 8px;
  padding-top: 12px;
  border-top: 1px solid color-mix(in srgb, var(--line) 60%, transparent);
}

.custom-game-tweak-section[hidden] { display: none; }

.custom-game-tweak-label {
  flex: 0 1 auto;
  width: 100%;
  margin: 0;
  font-size: 13px;
  font-weight: 600;
  color: var(--muted);
}

.custom-game-tweak-input {
  flex: 1 1 auto;
  min-width: 0;
  height: 40px;
  padding: 0 12px;
  border: 1px solid var(--line);
  border-radius: var(--radius-md);
  background: var(--panel-alt);
  color: var(--ink);
  font-size: 14px;
  box-sizing: border-box;
}

.custom-game-tweak-history {
  flex: 0 1 auto;
  width: 100%;
  margin: 4px 0 0;
  padding-left: 18px;
  font-size: 12px;
  color: var(--muted);
}

.custom-game-tweak-history:empty { display: none; }

.custom-game-create-actions {
  display: flex;
  gap: 10px;
  padding-top: 12px;
  border-top: 1px solid color-mix(in srgb, var(--line) 60%, transparent);
}

.custom-game-create-actions .btn {
  flex: 1 1 0;
}

@media (max-width: 480px) {
  .custom-game-create-actions,
  .custom-game-tweak-section {
    flex-direction: column;
    align-items: stretch;
  }
}

/* ── Library modal (library-modal.js) ────────────────────────────────── */
.custom-game-library-body {
  display: flex;
  flex-direction: column;
  gap: 10px;
}

.custom-game-library-status {
  margin: 0;
  min-height: 1.2em;
  color: var(--muted);
  font-size: 13px;
}

.custom-game-library-status:empty { display: none; }

.custom-game-library-empty {
  margin: 0;
  padding: 20px 0;
  text-align: center;
  color: var(--muted);
}

.custom-game-library-list {
  display: flex;
  flex-direction: column;
  gap: 8px;
}

.custom-game-library-row {
  display: flex;
  align-items: center;
  gap: 10px;
  padding: 10px 12px;
  border: 1px solid var(--line-soft);
  border-radius: var(--radius-md);
  background: var(--panel-alt);
}

.custom-game-library-row-body {
  flex: 1 1 auto;
  min-width: 0;
}

.custom-game-library-row-name-row {
  display: flex;
  align-items: baseline;
  gap: 6px;
  min-width: 0;
}

.custom-game-library-row-name {
  font-weight: 700;
  font-size: 15px;
  color: var(--ink);
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
  min-width: 0;
}

/* [uge-builder 2026-07-25] "Shared with you" provenance badge — see
   library-modal.js's renderList for what sets this. */
.custom-game-library-row-shared-badge {
  flex: 0 0 auto;
  font-size: var(--text-xs);
  color: var(--accent);
  border: 1px solid color-mix(in srgb, var(--accent) 45%, transparent);
  border-radius: var(--radius-sm, 6px);
  padding: 1px 6px;
  white-space: nowrap;
}

.custom-game-library-row-desc {
  margin-top: 2px;
  font-size: 13px;
  opacity: 0.75;
  overflow: hidden;
  text-overflow: ellipsis;
  display: -webkit-box;
  -webkit-line-clamp: 2;
  -webkit-box-orient: vertical;
}

.custom-game-library-row-activate,
.custom-game-library-row-rename,
.custom-game-library-row-edit,
.custom-game-library-row-share,
.custom-game-library-row-delete {
  flex: 0 0 auto;
  white-space: nowrap;
}

.custom-game-library-row-delete {
  color: var(--danger);
  border-color: color-mix(in srgb, var(--danger) 45%, transparent);
}

.custom-game-library-row-delete:hover {
  background: color-mix(in srgb, var(--danger) 14%, transparent);
}

.custom-game-library-create {
  display: flex;
  flex-direction: column;
  gap: 8px;
  padding-top: 4px;
}

.custom-game-library-create-title {
  margin: 0;
  color: var(--ink);
  font-size: 15px;
  font-weight: 700;
}

.custom-game-library-create-actions {
  display: flex;
  flex-wrap: wrap;
  align-items: flex-start;
  gap: 12px;
}

.custom-game-library-create-option {
  display: flex;
  flex: 1 1 190px;
  flex-direction: column;
  gap: 4px;
  min-width: 0;
}

.custom-game-library-new {
  width: 100%;
  min-height: 44px;
}

.custom-game-library-create-promise {
  color: var(--muted);
  font-size: 12px;
  line-height: 1.3;
  text-align: center;
}

.custom-game-library-share-form {
  display: flex;
  align-items: center;
  flex-wrap: wrap;
  gap: 8px;
  padding: 10px 12px;
  margin-top: -4px;
  border: 1px solid var(--line-soft);
  border-top: none;
  border-radius: 0 0 var(--radius-md) var(--radius-md);
  background: var(--panel-alt);
}

.custom-game-library-share-input {
  flex: 1 1 160px;
  min-width: 0;
}

.custom-game-library-share-status {
  flex: 1 1 100%;
  margin: 0;
  font-size: 13px;
  color: var(--muted);
}

.custom-game-library-share-status:empty { display: none; }

@media (max-width: 480px) {
  .custom-game-library-row {
    flex-wrap: wrap;
  }

  .custom-game-library-row-activate,
  .custom-game-library-row-rename,
  .custom-game-library-row-edit,
  .custom-game-library-row-share,
  .custom-game-library-row-delete {
    flex: 1 1 auto;
  }

  .custom-game-library-create-actions {
    flex-direction: column;
    align-items: stretch;
  }

  .custom-game-library-create-option {
    flex-basis: auto;
  }
}

/* ============================================================================
   Live game rendering (engine.js's render()) — the actual results table and
   input controls a player sees when a custom game is active in a round.
   Deliberately built on the SAME base classes every other game's live
   results card uses (.live-results-card/.live-results-table/
   .live-results-title-row/.live-results-data-row/.live-results-label,
   game-shell.css) rather than bespoke custom-game-only styling — this is
   what makes a generated game inherit font-scaling (main.css's !important
   rule keys off these exact classes), the app's responsive breakpoints,
   collapsible-card behavior (results-collapse.js), and Totals-tab clone
   styling (game-totals.css) for free, and look/feel consistent with every
   other game instead of bolted-on. Team grouping, money-sign coloring, and
   the tally/hole-badge chip formulas below are all copied from real,
   already-shipped patterns (Junk's team-divider row, the cross-game
   banker-total-positive/negative convention, junk-sheet.js's toggle chips,
   Stroke Play's small stat badge) — see the 2026-07-19 UI-pattern-catalog
   research this was built from.
   ============================================================================ */

.custom-game-description,
.custom-game-holes-note,
/* [identity-state] renderHolderBanner's "who currently holds X" line(s) —
   reuses the exact same small/muted status-text treatment as the holes-
   scored note above rather than inventing new visual language. */
.custom-game-holder-banner-row {
  margin: 0 0 6px;
  font-size: var(--text-sm, 13px);
  color: var(--muted);
}

/* Empty-state cell inside the kept table shell (Hi-Lo's pattern — the most
   polished of the empty-state treatments found in the research, keeps the
   card frame instead of collapsing to a bare unstyled line). */
.custom-game-empty {
  padding: 16px 8px;
  text-align: center;
  font-style: italic;
  opacity: 0.7;
}

/* Team-divider row — copied from .junk-team-divider-row's exact spec
   (junk.css) so a team-mode custom game's results table groups teammates
   using the same, already-established convention instead of inventing a
   new one (no existing game color-codes teams inside a results table, so
   this deliberately doesn't either — see the research's teamOrGrouping
   finding). */
.custom-game-team-divider-row td {
  background: var(--panel-alt);
  font-weight: 700;
  text-align: left;
  border-left: none;
}

/* ── Multi-instance wrappers (render(), 2026-07-20) ──────────────────────
   #customInputsContainer/#customResultsBottom now hold N stacked per-
   instance blocks (one per concurrently-active custom game) instead of a
   single one — these wrappers give each instance its own labeled card so
   several custom games in the same round read as distinct games, not one
   merged blob, while everything INSIDE a block (input rows, results table)
   keeps reusing the classes above/below unchanged. */
.custom-game-instance-block {
  display: flex;
  flex-direction: column;
  padding-bottom: 10px;
  margin-bottom: 10px;
  border-bottom: 1px solid var(--line-soft);
}

.custom-game-instance-block:last-child {
  padding-bottom: 0;
  margin-bottom: 0;
  border-bottom: none;
}

.custom-game-instance-results {
  margin-bottom: 10px;
}

.custom-game-instance-results:last-child {
  margin-bottom: 0;
}

.custom-game-instance-header {
  display: flex;
  align-items: flex-start;
  justify-content: space-between;
  gap: 8px;
  padding: 4px 4px 0;
}

.custom-game-instance-title {
  display: flex;
  align-items: center;
  min-width: 0;
  padding-top: 4px;
}

.custom-game-instance-name {
  font-weight: 700;
  font-size: 15px;
  color: var(--ink);
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.custom-game-instance-actions {
  display: flex;
  flex: 0 0 auto;
  gap: 6px;
}

/* [touch-fix] Scoped under .custom-game-instance-actions (specificity 0,2,0)
   so this reliably beats responsive-layout.css's bare `.btn { min-height:
   var(--touch-comfortable); padding: ... }` (specificity 0,1,0) at mobile
   widths -- without this, that rule's min-height (~48px+) stretched these
   into a tall pill while width stayed pinned at whatever this rule set,
   confirmed live. Sized to --touch-min (44px, this app's own touch-target
   floor -- the earlier 30px was actually BELOW it) so they read as real
   squares matching the floor every other compact icon button in this file
   already uses, not an arbitrary smaller circle. */
.custom-game-instance-actions .custom-game-info-btn,
.custom-game-instance-actions .custom-game-remove-btn,
.custom-game-instance-actions .custom-game-options-btn,
.custom-game-instance-actions .custom-game-layout-btn {
  flex: 0 0 auto;
  width: var(--touch-min);
  height: var(--touch-min);
  min-height: var(--touch-min);
  padding: 0;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  font-size: 15px;
  line-height: 1;
  border-radius: 999px;
}

.custom-game-remove-btn {
  color: var(--danger);
  border-color: color-mix(in srgb, var(--danger) 45%, transparent);
}

.custom-game-remove-btn:hover {
  background: color-mix(in srgb, var(--danger) 14%, transparent);
}

/* Collapsible rules panel — toggled open/closed by .custom-game-info-btn,
   matching the built-in games' ⓘ-button-opens-a-panel convention
   (game-controls.js's bindGameOptionsToggles), just wired locally per
   instance instead of through that shared one-time-DOM-scan system (a
   dynamically-rendered-per-instance header can't be discovered by a scan
   that only runs once at page load — see engine.js's render() comment). */
.custom-game-info-panel {
  margin: 4px 4px 0;
  padding: 8px 10px;
  border: 1px solid var(--line-soft);
  border-radius: var(--radius-md);
  background: var(--panel-alt);
}

.custom-game-info-panel[hidden] { display: none; }

.custom-game-info-description {
  margin: 0 0 6px;
  font-size: var(--text-sm, 13px);
  color: var(--ink);
}

.custom-game-info-facts {
  margin: 0;
  padding-left: 18px;
  font-size: var(--text-sm, 13px);
  color: var(--muted);
}

.custom-game-info-facts li + li {
  margin-top: 4px;
}

/* ── Per-hole ledger (renderResults' hole-by-hole breakdown) ─────────────
   Reuses .live-results-table/.custom-results-table for the totals table's
   own font-scaling/card chrome, plus the cross-game banker-total-positive/
   negative convention (already used above for the totals column) for
   per-hole sign coloring — no new color system introduced. Always visible
   in the main area (matches Banker's #bankerBody, etc.) — the whole-card
   results-collapse-bar is the one user-controlled show/hide, not a second
   nested toggle here. overflow-x matters since several players render as
   columns on a narrow screen. */
.custom-game-ledger-label {
  margin: 10px 0 4px;
  font-size: var(--text-sm, 13px);
  font-weight: 600;
  color: var(--muted);
}

.custom-game-ledger-wrap {
  overflow-x: auto;
}

.custom-game-ledger-empty-cell {
  text-align: center;
  opacity: 0.5;
}

/* [results-display] renderHoleMatchupDetailTable's per-bet rows — an
   alternate to the plain player-column grid above, opt-in via the creation
   wizard's "show matchup detail" checkbox. Each hole gets its own
   .live-results-title-row (the hole-badge th, matching every other table's
   title-row convention), followed by one data row per settled bet: a
   description cell that wraps on narrow screens (two full player names +
   scores is long) and a fixed, right-aligned amount cell using the same
   banker-total-positive/negative sign convention as every other total in
   this file. */
.custom-game-matchup-desc {
  white-space: normal;
  color: var(--ink);
}

.custom-game-matchup-amount {
  text-align: right;
  font-weight: 600;
  white-space: nowrap;
}

/* ── Input controls (renderInputs/renderInputControl) ────────────────────── */
.custom-game-inputs {
  display: flex;
  flex-direction: column;
}

.custom-game-inputs-inner {
  display: flex;
  flex-direction: column;
  gap: 8px;
  padding: 8px 4px;
}

/* Per-instance Options dialog content (team assignment, stakes — the
   round/roundPlayer-scoped inputs pulled out of .custom-game-inputs-inner).
   Reuses .custom-game-input-row/.custom-game-hole-badge unchanged; the
   dialog chrome itself (backdrop, centering, header) comes from
   .game-options-panel.is-options-modal in game-options-ui.css once
   GameOptionsModal.open() reparents this panel into the shared modal. */
.custom-game-options-panel {
  display: flex;
  flex-direction: column;
  gap: 8px;
}

/* [2026-07-21 banker-parity] Row + control sizing now mirrors Banker's own
   formula (docs/stylesheet/banker.css's .banker-inline-wrap/.banker-select/
   .banker-number-input): auto height driven by padding+border+font-size
   (never a fixed `height`, which — see the mobile fix below — used to fight
   flexbox's column-mode basis math and blow up to ~100px tall on phones),
   inline label+control (never stacked), and a font-size that grows with
   --font-size-step like every hand-coded game's own inputs instead of being
   pinned at 14px regardless of accessibility settings. */
.custom-game-input-row {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 6px;
  padding: 8px 10px;
  border: 1px solid var(--line-soft);
  border-radius: var(--radius-md);
  background: var(--panel-alt);
}

.custom-game-input-row label {
  flex: 0 1 auto;
  min-width: 0;
  font-size: var(--text-sm, 13px);
  font-weight: 600;
  color: var(--ink);
}

.custom-game-input-row select,
.custom-game-input-row input[type="number"],
.custom-game-input-row input[type="text"] {
  flex: 1 1 100px;
  min-width: 0;
  padding: 4px 8px;
  border: 2px solid var(--line);
  border-radius: 4px;
  background: var(--panel);
  color: var(--ink);
  font-size: calc(14px + var(--font-size-step, 0px));
  font-weight: 500;
  box-sizing: border-box;
}

/* A bet/count amount reads better narrow, centered, and bold — matching
   .banker-number-input exactly — rather than flex-growing to fill whatever
   space is left in the row (that stretch reads fine for a player-picker
   select showing a name, not for a 1-3 digit number). */
.custom-game-input-row input[type="number"] {
  flex: 0 1 84px;
  text-align: center;
  font-weight: 600;
  border-color: var(--accent);
}

/* [touch-fix] 22x22 was a real checkbox-sized (not touch-sized) target; this
   is only the last-resort fallback widget now that boolean fields default to
   'toggle' (see ALLOWED_WIDGETS_BY_TYPE in custom-game-interpreter.js) — a
   generated/legacy definition can still request it explicitly though, so it
   still needs a real tap target, not just a bigger checkbox glyph. */
.custom-game-input-row input[type="checkbox"] {
  width: var(--touch-min);
  height: var(--touch-min);
  flex: 0 0 auto;
  accent-color: var(--accent);
}

/* [2026-07-20 fix] One card per hole, holding ALL of that hole's controls
   together (hole-scoped fields + a compact per-player row for holePlayer/
   holePlayerPair/tally fields) -- replaces the old "one full-width row per
   declared input, repeated 18x, in input order not hole order" layout that
   read as a wall of near-identical boxes for any game with more than one
   hole-scoped input (e.g. Banker's picker + bet + doubled-toggle). */
.custom-game-hole-group {
  display: flex;
  flex-direction: column;
  gap: 8px;
  padding: 10px 12px;
  border: 1px solid var(--line-soft);
  border-radius: var(--radius-md);
  background: var(--panel-alt);
}

.custom-game-hole-group-header {
  align-self: flex-start;
  padding: 3px 10px;
  font-size: var(--text-sm, 13px);
}

.custom-game-hole-group-fields,
.custom-game-hole-group-player {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 8px;
}

.custom-game-hole-group-players {
  display: flex;
  flex-direction: column;
  gap: 6px;
}

.custom-game-hole-group-player-name {
  flex: 0 0 auto;
  min-width: 64px;
  font-size: var(--text-sm, 13px);
  font-weight: 600;
  color: var(--muted);
}

/* Nested inside a hole group, an individual control reads as a compact
   inline label+field pair -- the GROUP itself is the bordered card now, so
   the per-control border/background from the standalone (Options-panel)
   presentation would just be visual noise here. */
.custom-game-hole-group .custom-game-input-row {
  flex: 1 1 160px;
  padding: 0;
  border: none;
  background: transparent;
}

.custom-game-hole-group .custom-game-input-row label {
  flex: 0 0 auto;
  font-weight: 500;
}

/* [oneHoleAtATime-result-panel] Inputs + that hole's own result side by
   side on desktop -- a divider border (left on desktop, top on mobile)
   separates them without the heavier full-card treatment
   .custom-game-hole-group itself already uses, since this is nested inside
   that same bordered card. Row is the base (desktop-first, matching this
   file's existing @media (max-width: 480px) override convention below). */
.custom-game-hole-group-layout {
  display: flex;
  flex-direction: row;
  align-items: flex-start;
  gap: 12px;
}

.custom-game-hole-group-inputs-side {
  flex: 1 1 auto;
  min-width: 0;
  display: flex;
  flex-direction: column;
  gap: 8px;
}

.custom-game-hole-result {
  flex: 0 0 auto;
  width: 220px;
  padding-left: 12px;
  border-left: 1px solid var(--line-soft);
  display: flex;
  flex-direction: column;
  gap: 4px;
}

.custom-game-hole-result-label {
  font-size: var(--text-sm, 13px);
  font-weight: 600;
  color: var(--muted);
}

.custom-game-hole-result-row {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: 8px;
}

.custom-game-hole-result-desc {
  color: var(--ink);
  font-size: var(--text-sm, 13px);
}

.custom-game-hole-result-amount {
  flex: 0 0 auto;
  text-align: right;
  font-weight: 600;
  white-space: nowrap;
}

/* Below the inputs on phone widths, not beside them -- a fixed 220px side
   column has no room next to Banker's own multi-field rows on a real phone
   screen. Divider moves from left border to top border to match. */
@media (max-width: 480px) {
  .custom-game-hole-group-layout {
    flex-direction: column;
  }
  .custom-game-hole-result {
    width: auto;
    padding-left: 0;
    padding-top: 10px;
    border-left: none;
    border-top: 1px solid var(--line-soft);
  }
}

/* Small stat pill (hole number, or "H3 · Player Name") — copied from
   .strokeplay-hcp-badge's exact formula (game-shell.css). */
.custom-game-hole-badge {
  display: inline-flex;
  align-items: center;
  flex: 0 0 auto;
  min-width: 16px;
  height: 16px;
  padding: 2px 6px;
  margin-right: 2px;
  font-size: 10px;
  font-weight: 700;
  border-radius: 999px;
  background: color-mix(in srgb, var(--panel-alt) 80%, transparent);
  color: var(--muted);
  white-space: nowrap;
}

/* Tally toggle chips — copied from junk-sheet.js's .junk-sheet-chip family
   (junk.css) formula exactly: same grid layout, same 3-part emoji/label/pts
   child structure, same [data-active] state hook (not a class — matches the
   app's established toggle-state convention) and the same touch-friendly
   min-height, which matters most at mobile widths where a native checkbox
   would be too small a tap target. */
.custom-game-tally-chips {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(96px, 1fr));
  gap: 8px;
  flex: 0 1 auto;
  width: 100%;
}

.custom-game-tally-chip {
  appearance: none;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: 2px;
  min-height: 76px;
  padding: 10px 6px;
  border: 2px solid var(--line);
  border-radius: var(--radius-md);
  background: var(--panel-alt);
  color: var(--ink);
  font-size: var(--text-sm, 13px);
  cursor: pointer;
  transition: background var(--transition-fast, 0.15s), border-color var(--transition-fast, 0.15s), color var(--transition-fast, 0.15s), transform var(--transition-fast, 0.15s);
}

.custom-game-tally-chip:hover {
  border-color: var(--accent);
}

.custom-game-tally-chip:active {
  transform: scale(0.97);
}

.custom-game-tally-chip[data-active="true"] {
  background: var(--accent);
  border-color: var(--accent);
  color: var(--on-accent, #fff);
}

.custom-game-tally-chip[data-active="true"] .custom-game-tally-chip-pts {
  color: rgba(255, 255, 255, 0.85);
}

.custom-game-tally-chip-emoji {
  font-size: calc(20px + var(--font-size-step, 0px));
}

.custom-game-tally-chip-label {
  font-weight: 600;
  font-size: var(--text-sm, 13px);
  line-height: 1.1;
  text-align: center;
}

.custom-game-tally-chip-pts {
  font-size: var(--text-xs, 11px);
  color: var(--muted);
  line-height: 1;
}

/* [2026-07-21 banker-parity fix] This used to flip the row to
   flex-direction:column at phone widths — but a flex item's `flex-basis`
   (here, select/input's `flex: 1 1 100px`, meant as a WIDTH basis in row
   mode) becomes its MAIN-axis size once the container is column, i.e. a
   100px HEIGHT, completely overriding the control's intended auto height.
   That's exactly why a "Banker" select/"Bet" number input rendered ~100px
   tall on a real phone (confirmed live) despite no `height` rule anywhere
   claiming that. Banker's own inline rows (.banker-inline-wrap) never flip
   direction at any width — matching that (row stays row, just wraps via the
   existing flex-wrap:wrap when a control genuinely doesn't fit) removes the
   bug at its root instead of patching the column-mode flex-basis math. */
@media (max-width: 480px) {
  .custom-game-tally-chips {
    grid-template-columns: repeat(auto-fill, minmax(84px, 1fr));
  }
}

/* [layout-studio] Alternate boolean widget — a switch-styled track+thumb,
   toggled via aria-pressed (same on/off contract as the plain checkbox it
   replaces). No text content: the field's own <label> already names it.
   [touch-fix] height bumped 26->30px and touch-action/user-select added to
   match this app's own touch conventions (--touch-min, banker.css's
   growing-not-shrinking tap targets, junk.css's no-hover-only-state rule) —
   width already met --touch-min (44px), only height and tap-safety were
   short. Thumb/translate distance scaled to match the taller track. */
.custom-game-toggle-switch {
  appearance: none;
  flex: 0 0 auto;
  position: relative;
  width: 44px;
  height: 30px;
  padding: 2px;
  border: 1px solid var(--line);
  border-radius: 999px;
  background: var(--panel-alt);
  cursor: pointer;
  touch-action: manipulation;
  user-select: none;
  -webkit-user-select: none;
  transition: background var(--transition-fast, 0.15s), border-color var(--transition-fast, 0.15s);
}

.custom-game-toggle-switch-thumb {
  display: block;
  width: 24px;
  height: 24px;
  border-radius: 50%;
  background: var(--muted);
  transition: transform var(--transition-fast, 0.15s), background var(--transition-fast, 0.15s);
}

.custom-game-toggle-switch[aria-pressed="true"] {
  background: var(--accent);
  border-color: var(--accent);
}

.custom-game-toggle-switch[aria-pressed="true"] .custom-game-toggle-switch-thumb {
  background: var(--on-accent, #fff);
  transform: translateX(16px);
}

/* [on-off-chips, 2026-07-24] Default boolean widget — a compact, explicitly
   labeled Off/On button pair (see renderInputControl's 'chip' branch),
   reusing utilities.css's .hcp-mode-btn-group/.hcp-mode-btn convention
   as-is (already --touch-min tall, already sized to its own content rather
   than stretching) — this class exists only as a stable hook for the rare
   custom-game-specific override, not because the shared convention needed
   any change. Replaces the old single full-width always-on-screen chip
   (.custom-game-toggle-chip, removed) whose only on/off signal was a color
   fill — hard to scan across several boolean fields stacked in one hole
   card (real user feedback), and wasted width every field this compact
   doesn't need. */
.custom-game-onoff-group {
  flex: 0 0 auto;
}

/* [layout-studio] Alternate number widget — a "− value +" stepper,
   respecting the input's declared min/max the same way the plain number
   field does. */
.custom-game-stepper {
  display: flex;
  align-items: center;
  gap: 8px;
  flex: 0 0 auto;
}

/* [touch-fix] Bumped 32x32 -> --touch-min (44px), matching Banker's own
   .banker-sheet-stepbtn precedent (44px baseline, grows further on phones)
   instead of inventing a smaller stepper button just for this feature. */
.custom-game-stepper-btn {
  appearance: none;
  width: var(--touch-min);
  height: var(--touch-min);
  border: 1px solid var(--line);
  border-radius: var(--radius-sm, 6px);
  background: var(--panel);
  color: var(--ink);
  font-size: 16px;
  font-weight: 700;
  line-height: 1;
  cursor: pointer;
  touch-action: manipulation;
  user-select: none;
  -webkit-user-select: none;
}

.custom-game-stepper-btn:active {
  transform: scale(0.95);
}

.custom-game-stepper-value {
  min-width: 32px;
  text-align: center;
  font-size: 14px;
  font-weight: 600;
  color: var(--ink);
}

/* [layout-studio] Prev/next bar shown above the single rendered hole when
   an instance's layout.displayMode is "oneHoleAtATime" — matches the
   .custom-game-hole-badge visual weight (this IS that same badge, just
   made clickable-adjacent) rather than introducing a new chrome language. */
.custom-game-hole-nav {
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 12px;
  padding: 4px 0 2px;
}

/* [touch-fix] Bumped 36x36 -> --touch-min (44px) to clear the app-wide
   touch-target floor every other control uses. */
.custom-game-hole-nav-btn {
  appearance: none;
  width: var(--touch-min);
  height: var(--touch-min);
  border: 1px solid var(--line);
  border-radius: var(--radius-sm, 6px);
  background: var(--panel);
  color: var(--ink);
  font-size: 18px;
  line-height: 1;
  cursor: pointer;
  touch-action: manipulation;
  user-select: none;
  -webkit-user-select: none;
}

.custom-game-hole-nav-btn:disabled {
  opacity: 0.4;
  cursor: default;
}

.custom-game-hole-nav-label {
  min-width: 64px;
  text-align: center;
  font-size: var(--text-sm, 13px);
  font-weight: 700;
  color: var(--ink);
}

/* [layout-studio] The 🎨 Layout Studio panel (docs/js/games/custom/
   layout-studio.js), opened via the shared GameOptionsModal -- same
   dialog chrome as .custom-game-options-panel, own layout-studio-*
   classes for its reorder list + widget selects. */
.layout-studio-panel {
  display: flex;
  flex-direction: column;
  gap: 8px;
}

.layout-studio-section-label {
  font-size: var(--text-sm, 13px);
  font-weight: 700;
  color: var(--muted);
  margin-top: 4px;
}

.layout-studio-reorder-list {
  display: flex;
  flex-direction: column;
  gap: 6px;
}

/* [touch-fix] gap widened 8->12px -- the up/down reorder buttons sit
   directly adjacent in this row, and 8px between two --touch-min-sized
   buttons was tight enough to risk a fat-finger tap on the wrong one. */
.layout-studio-reorder-row {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 12px;
  padding: 8px 10px;
  border: 1px solid var(--line-soft);
  border-radius: var(--radius-md);
  background: var(--panel-alt);
}

.layout-studio-reorder-label {
  flex: 1 1 120px;
  min-width: 0;
  font-size: var(--text-sm, 13px);
  font-weight: 600;
  color: var(--ink);
}

/* [touch-fix] Height bumped 34px -> --touch-min; a native <select> is an
   already-accepted touch pattern in this app (see banker.css's own
   .banker-select), so it's kept rather than replaced, just resized to
   clear the floor every other control here uses. */
.layout-studio-widget-select {
  flex: 1 1 140px;
  min-width: 0;
  height: var(--touch-min);
  padding: 0 8px;
  border: 1px solid var(--line);
  border-radius: var(--radius-sm, 6px);
  background: var(--panel);
  color: var(--ink);
  font-size: 13px;
}

/* [touch-fix] Bumped 32x32 -> --touch-min (44px), matching every other
   glyph-only control in this file; touch-action/user-select added since
   these are exactly the "rapid repeat-tap" controls (reordering several
   fields in a row) this app's own convention (header.css, junk.css)
   guards against text-selection/double-tap-zoom on. */
.layout-studio-reorder-btn {
  appearance: none;
  width: var(--touch-min);
  height: var(--touch-min);
  flex: 0 0 auto;
  border: 1px solid var(--line);
  border-radius: var(--radius-sm, 6px);
  background: var(--panel);
  color: var(--ink);
  font-size: 15px;
  cursor: pointer;
  touch-action: manipulation;
  user-select: none;
  -webkit-user-select: none;
}

.layout-studio-reorder-btn:disabled {
  opacity: 0.35;
  cursor: default;
}

.layout-studio-save-btn {
  margin-top: 8px;
  touch-action: manipulation;
}

/* [instance-tiles] The static #customToggleItem tile (docs/partials/
   games-launcher.html) is kept only as a stable insertion anchor for
   docs/js/games/custom/engine.js's renderLauncherTiles() and so
   getGameConfig('custom').toggle keeps resolving to a real element for
   game-tab-ui.js's cosmetic sync — it must never be visible itself now
   that every active instance gets its own real tile. Permanently hidden
   here regardless of whatever create-modal.js's updateToggleVisibility()
   sets its `hidden` attribute to. */
.game-toggle-item[data-game-key="custom"]:not([data-instance-id]) {
  display: none !important;
}

/* AI-generated instance names can run long; truncate instead of breaking
   the gamesbar's fixed-height row (every other game's label is a short
   static string that never needed this). */
.game-toggle-item[data-game-key="custom"][data-instance-id] .game-toggle-label {
  max-width: 120px;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

/* ── [mobile-sheet-mode] Bottom-sheet input modal ─────────────────────────
   Mirrors docs/stylesheet/score-sheet.css's own backdrop/slide-up/centered-
   dialog convention exactly (same 220ms cubic-bezier, same 720px breakpoint
   where the sheet becomes a centered dialog instead of a full-bleed bottom
   sheet) -- see hole-sheet.js's header comment for why this hand-rolls its
   own DOM rather than sharing score-sheet.js's implementation. z-index band
   (9200/9201) is deliberately its own, distinct from Banker's (9000/9001)
   and score-sheet's (9100/9101) -- each hand-rolled sheet in this app picks
   a free band rather than sharing one. */
.custom-game-sheet-backdrop {
  position: fixed;
  inset: 0;
  background: rgba(0, 0, 0, 0.2);
  opacity: 0;
  pointer-events: none;
  transition: opacity 180ms ease;
  z-index: 9200;
}

.custom-game-sheet-backdrop.is-open {
  opacity: 1;
  pointer-events: auto;
}

.custom-game-sheet {
  position: fixed;
  left: 0;
  right: 0;
  bottom: 0;
  max-height: calc(100dvh - max(env(safe-area-inset-top, 0px), 56px));
  display: flex;
  flex-direction: column;
  background: var(--panel);
  color: var(--ink);
  border-top: 2px solid var(--line);
  border-top-left-radius: var(--radius-lg);
  border-top-right-radius: var(--radius-lg);
  box-shadow: none;
  transform: translateY(100%);
  transition: transform 220ms cubic-bezier(0.2, 0.8, 0.2, 1);
  z-index: 9201;
}

.custom-game-sheet.is-open {
  transform: translateY(0);
  box-shadow: 0 -12px 32px rgba(0, 0, 0, 0.4);
}

/* Dark theme: --panel is intentionally translucent (rgba(13,26,39,0.9)) for
   INLINE panels layered over already-opaque page chrome, but this sheet is a
   fixed OVERLAY above the blurred backdrop — a translucent card lets the
   blurred table/header behind it visibly bleed through (same fix already
   applied to .jca-modal / .game-options-panel.is-options-modal). Same hue as
   --panel's dark value, alpha 1. */
:root:not([data-theme="light"]) .custom-game-sheet { background: rgb(13, 26, 39); }

@media (min-width: 720px) {
  .custom-game-sheet {
    left: 50%;
    right: auto;
    bottom: 24px;
    transform: translate(-50%, 120%);
    width: min(760px, calc(100vw - 24px));
    max-height: min(90vh, 900px);
    border-radius: var(--radius-lg);
    border: 2px solid var(--line);
  }

  .custom-game-sheet.is-open {
    transform: translate(-50%, 0);
  }
}

.custom-game-sheet-header {
  display: grid;
  grid-template-columns: 1fr auto;
  align-items: center;
  gap: 8px;
  padding: 12px 14px;
  border-bottom: 2px solid var(--line);
  background: var(--panel-alt);
}

.custom-game-sheet-nav {
  display: flex;
  align-items: center;
  gap: 8px;
  min-width: 0;
}

.custom-game-sheet-title {
  margin: 0;
  flex: 1 1 auto;
  min-width: 0;
  text-align: center;
  font-size: var(--text-lg, 16px);
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.custom-game-sheet-navbtn,
.custom-game-sheet-close {
  flex: 0 0 auto;
  min-width: var(--touch-min);
  min-height: var(--touch-min);
  border: 2px solid var(--line);
  border-radius: 8px;
  background: transparent;
  color: var(--ink);
  cursor: pointer;
  font-size: 18px;
  font-weight: 700;
}

.custom-game-sheet-navbtn:disabled {
  opacity: 0.45;
  cursor: not-allowed;
}

.custom-game-sheet-body {
  padding: 12px 14px;
  overflow-y: auto;
  overscroll-behavior: contain;
}

.custom-game-sheet-footer {
  padding: 10px 14px calc(10px + var(--safe-bottom-inset, env(safe-area-inset-bottom, 0px)));
  border-top: 2px solid var(--line);
  background: var(--panel-alt);
}

.custom-game-sheet-footer .btn {
  min-height: 44px;
  width: 100%;
}

/* ── [mobile-sheet-mode] Compact tap-to-open row (replaces inline inputs) ──
   Shown in place of inline fields/playersWrap whenever isSheetMode() is
   true — reuses .custom-game-hole-result (buildCurrentHoleResult's own
   markup) so results stay visible on the main scrollable area without
   opening anything, per the same "scorecard shows results, tap opens
   input" convention every hand-coded game already uses. */
.custom-game-sheet-trigger {
  display: flex;
  align-items: center;
  gap: 10px;
}

.custom-game-sheet-trigger .custom-game-hole-result {
  flex: 1 1 auto;
  width: auto;
  padding-left: 0;
  padding-top: 0;
  border-left: none;
  border-top: none;
}

.custom-game-sheet-trigger-empty {
  flex: 1 1 auto;
  font-size: var(--text-sm, 13px);
  color: var(--muted);
}

.custom-game-sheet-open-btn {
  flex: 0 0 auto;
  min-height: var(--touch-min);
  padding: 0 16px;
}

/* [2026-07-22 fix] Static floor for the scroll-room reservation behind
   #customResultsWrap's fixed bottom bar -- docs/js/scorecard/layout-sync.js's
   syncActiveGamePinnedResultsLayout() computes a precise inline padding-
   bottom/max-height on .game-scroll-body once its rAF-scheduled pass runs,
   but that scheduler (schedulePanelHeightSync's panelSyncRaf guard) has no
   timeout/retry: if its one pending requestAnimationFrame is ever delayed or
   never fires (a backgrounded tab, a suspended frame, main-thread contention),
   every later call becomes a silent no-op until a fresh reload, and the last
   holes/ledger rows are left with zero clearance under the fixed bar. This is
   a pure floor -- whenever the JS DOES run, its inline style (always higher
   specificity than this class rule) overrides it with the exact value;
   this only matters on the rAF-never-fired path. ID-scoped (matches
   #strokePlaySection's own .game-scroll-body override above) so it can't
   drift into the shared cross-game rule. 200px comfortably clears the
   results card even fully expanded with several matchup rows -- the
   dynamic value is usually much smaller (a collapsed card is ~50px). */
#customSection .game-scroll-body {
  padding-bottom: 200px;
}

/* [wager-ledger] renderWagerLedgerCard — same bordered-card idiom as
   .custom-game-hole-group (nested inside the same hole sheet), one row per
   open/pending wager plus a compact "open a new one" row. Deliberately no
   new visual language: reuses .btn.hcp-mode-btn for every action button and
   the app's existing --muted/--line-soft tokens throughout. */
.custom-game-wager-card {
  display: flex;
  flex-direction: column;
  gap: 6px;
  margin-top: 8px;
  padding-top: 8px;
  border-top: 1px solid var(--line-soft);
}

.custom-game-wager-title {
  margin: 0;
  font-size: var(--text-sm, 13px);
  font-weight: 600;
  color: var(--muted);
}

.custom-game-wager-row {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  justify-content: space-between;
  gap: 8px;
}

.custom-game-wager-row-label {
  font-size: var(--text-sm, 13px);
}

.custom-game-wager-row-actions {
  display: flex;
  flex-wrap: wrap;
  gap: 6px;
}

.custom-game-wager-open-row {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 6px;
}

.custom-game-wager-open-row input[type="number"] {
  width: 72px;
}

/* =============================================================================
   Visual UGE Builder — read-only formula value-trace / tree view
   (notes/VISUAL_UGE_BUILDER_PLAN_2026-07-24.md §4). Node boxes use the same
   left-border nesting rail as .custom-game-form-followup-cluster above,
   scaled to real formula depth (11-15 levels in shipped presets) rather than
   the single level that pattern was originally used for.
   ============================================================================= */

.custom-game-formula-value-view {
  margin-top: var(--space-md, 12px);
  padding-top: var(--space-md, 12px);
  border-top: 1px solid var(--line);
}

.cg-formula-empty,
.cg-formula-value-note {
  color: var(--muted);
  font-size: var(--text-sm);
}

.cg-formula-picker {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--space-sm, 8px);
  margin-bottom: var(--space-sm, 8px);
}

.cg-formula-picker-label {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--space-sm, 8px);
  font-size: var(--text-sm);
  color: var(--muted);
}

.cg-formula-scenario-select {
  max-width: 100%;
}

.cg-formula-summary {
  font-size: var(--text-sm);
  margin-bottom: var(--space-sm, 8px);
}

/* ── Scorecard: 18 holes x up to 4 players ─────────────────────────── */
/* [composer-scorecard 2026-07-25] The table itself is the MAIN APP's own
   scorecard markup verbatim (composer-scorecard.js) — .player-row /
   .name-edit / .ch-input / .score-input / .par-row / .hcp-row / .split /
   .total / .to-par / .net — so docs/stylesheet/scorecard.css does all the
   real styling and there is no lookalike to keep in visual sync. Per the
   requirement: "the scorecard should be the exact same style as the
   scorecard we have in the main app."

   Everything here is only the Composer-specific chrome around it, plus the
   few places the real sheet's IN-ROUND behaviour has to be neutralised (its
   sticky header rows assume a full-height scoring pane; this is an inline
   panel inside a longer page). */
.cg-formula-scorecard {
  margin-bottom: var(--space-md, 12px);
  padding: 10px 12px;
  border: 1px solid var(--line-soft, var(--line));
  border-radius: var(--radius-md);
  background: var(--panel-alt);
}

.cg-scorecard-header {
  display: flex;
  align-items: center;
  justify-content: space-between;
  flex-wrap: wrap;
  gap: 8px 12px;
  margin-bottom: 10px;
}
.cg-scorecard-title {
  margin: 0;
  font-size: var(--text-xs);
  font-weight: 600;
  text-transform: uppercase;
  letter-spacing: 0.02em;
  color: var(--muted);
}
.cg-scorecard-controls {
  display: flex;
  align-items: center;
  flex-wrap: wrap;
  gap: 6px;
}
.cg-scorecard-btn { min-width: 34px; padding: 4px 8px; }
.cg-scorecard-btn:disabled { opacity: 0.4; cursor: default; }
.cg-scorecard-fill { padding: 4px 10px; font-size: var(--text-sm); }
.cg-scorecard-count {
  min-width: 1.5em;
  text-align: center;
  font-size: var(--text-sm);
  font-weight: 600;
}
.cg-scorecard-collabel {
  font-size: var(--text-xs);
  color: var(--muted);
}

/* The real sheet is ~1040px wide by design; scroll it inside this panel
   rather than letting it widen the page. */
/* scorecard.css sizes .scorecard-container to `min(62vh, 560px)` because in
   the app it IS the scrolling score pane. Here it's an inline panel in a
   longer page, so it sizes to its own content and only scrolls sideways. */
.cg-scorecard-container {
  overflow-x: auto;
  overflow-y: hidden;
  -webkit-overflow-scrolling: touch;
  max-width: 100%;
  height: auto;
  max-height: none;
}
.cg-scorecard-table { min-width: 1040px; }

/* scorecard.css makes the header/par/hcp rows sticky and pointer-events:none
   against the main app's own scrolling score pane. Neither applies here —
   this is a short inline table, and its par/HCP cells are genuinely
   editable in the Composer (the user is describing a hypothetical course,
   not reading a fixed one). */
.cg-scorecard-table thead th,
.cg-scorecard-table .par-row td,
.cg-scorecard-table .par-row th,
.cg-scorecard-table .hcp-row td,
.cg-scorecard-table .hcp-row th {
  position: static;
  pointer-events: auto;
}

.cg-scorecard-note {
  margin: 8px 0 0;
  font-size: var(--text-xs);
  color: var(--muted);
}

/* ── The REAL game surface ─────────────────────────────────────────────
   [composer-inputs 2026-07-26] Replaces the old "try different numbers"
   seed editor (.cg-formula-seed*, deleted with it). What sits inside these
   two wrappers is not Composer markup at all — it is engine.js's own
   renderOptionsPanel and renderInputs output, so it is already fully styled
   by the .custom-game-* rules above. These wrappers therefore do nothing
   but frame it and title it; do NOT add rules that restyle anything inside,
   or the Composer would start showing a game that doesn't look like the
   game (the whole point is that it does).                                */
.cg-formula-options,
.cg-formula-inputs {
  margin-bottom: var(--space-md, 12px);
  padding: 10px 12px;
  border: 1px solid var(--line-soft, var(--line));
  border-radius: var(--radius-md);
  background: var(--panel-alt);
}
.cg-formula-options:empty,
.cg-formula-inputs:empty {
  display: none;
  border: 0;
  padding: 0;
  margin: 0;
}

.cg-formula-surface-label {
  margin: 0 0 8px;
  font-size: var(--text-xs);
  font-weight: 600;
  text-transform: uppercase;
  letter-spacing: 0.02em;
  color: var(--muted);
}

/* The engine's options panel is built to fill a modal body; inside the
   Composer's narrower reading column its rows should simply wrap. */
.cg-formula-options-panel {
  display: flex;
  flex-direction: column;
  gap: 8px;
  min-width: 0;
}

/* renderInputs' own hole card already handles its internal layout — this
   only stops a wide 4-player row from forcing the whole page to scroll
   sideways on a phone. */
.cg-formula-inputs-host {
  min-width: 0;
  overflow-x: auto;
}

/* "Open as it appears on your phone" — the real bottom sheet, on demand. */
.cg-formula-phoneview {
  margin-bottom: var(--space-md, 12px);
}
.cg-formula-phoneview:empty {
  display: none;
  margin: 0;
}
.cg-formula-phoneview-btn {
  font-size: var(--text-xs);
}

/* ── Composed controls ─────────────────────────────────────────────────
   [composer-playable 2026-07-26] The one shape a game's REAL table draws
   that "one widget per declared input" cannot: a merged control (Wolf's
   single "— / Lone Wolf / Blind Wolf / Partner: <name>" dropdown) and the
   read-only role-holder cell beside it. Both are built from the engine's
   OWN primitives and dropped into the engine's own .custom-game-input-row,
   so they inherit that row's layout — these rules only style the one node
   the row shape has no equivalent for (the role NAME, which is text where
   every other row holds a control).                                      */
.cg-formula-role-name {
  font-weight: 600;
  color: var(--ink);
}

/* The one honest sentence about a game whose in-round LAYOUT differs from the
   engine's own card (Banker's bet grid, Junk's per-cell "Dots" dropdown). */
.cg-formula-inround-note {
  margin: 8px 0 0;
  font-size: var(--text-xs);
  color: var(--muted);
}

/* ── The data-path story ───────────────────────────────────────────────
   Where a tap actually goes: leaf key -> store path -> command -> stateMap
   -> result. Collapsed by default (see composer-data-path.js for why), so
   it costs nothing until a reader asks for it.                           */
.cg-formula-datapath:empty {
  display: none;
}
.cg-datapath {
  margin-bottom: var(--space-md, 12px);
  border: 1px solid var(--line-soft, var(--line));
  border-radius: var(--radius-md);
  background: var(--panel-alt);
}
.cg-datapath-summary {
  cursor: pointer;
  padding: 9px 12px;
  font-size: var(--text-xs);
  font-weight: 600;
  color: var(--muted);
}
.cg-datapath-body {
  padding: 0 12px 12px;
}
.cg-datapath-note,
.cg-datapath-empty {
  margin: 0 0 8px;
  font-size: var(--text-xs);
  color: var(--muted);
}
.cg-datapath-label {
  margin: 12px 0 6px;
  font-size: var(--text-xs);
  font-weight: 600;
  color: var(--muted);
}
.cg-datapath-list {
  margin: 0;
  padding-left: 18px;
  display: flex;
  flex-direction: column;
  gap: 8px;
}
.cg-datapath-item {
  font-size: var(--text-xs);
  color: var(--ink);
}
.cg-datapath-item-title {
  display: block;
  font-weight: 600;
}
.cg-datapath-item-text {
  display: block;
  color: var(--muted);
}
.cg-datapath-code,
.cg-datapath-leaf-key {
  display: inline-block;
  max-width: 100%;
  overflow-wrap: anywhere;
  margin-top: 3px;
  padding: 1px 5px;
  border-radius: var(--radius-sm, 6px);
  background: var(--panel);
  border: 1px solid var(--line);
  font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
  font-size: var(--text-xxs, 11px);
  color: var(--ink);
}
.cg-datapath-leaves {
  display: flex;
  flex-direction: column;
  gap: 4px;
}
.cg-datapath-leaf {
  display: flex;
  flex-wrap: wrap;
  align-items: baseline;
  gap: 8px;
  padding: 4px 6px;
  border-radius: var(--radius-sm, 6px);
}
.cg-datapath-leaf-latest {
  background: var(--panel);
  outline: 1px solid var(--accent, var(--line));
}
.cg-datapath-leaf-desc {
  flex: 1 1 140px;
  min-width: 0;
  font-size: var(--text-xxs, 11px);
  color: var(--muted);
}
.cg-datapath-leaf-value {
  font-size: var(--text-xs);
  font-weight: 600;
  color: var(--ink);
}

/* The generic fallback controls renderRealVarWidget paints for a var with
   no richer real widget available (a plain number/select) — booleans and
   any inputs.<id> with a real chip/multiplier/playerPicker/enum/tally widget
   use THAT widget's own real classes instead (.hcp-mode-btn-group etc, see
   formula-box-editors.js's renderRealVarWidget). */
.cg-formula-real-number,
.cg-formula-real-select {
  width: 84px;
  padding: 5px 7px;
  border: 1px solid var(--line);
  border-radius: var(--radius-sm, 6px);
  background: var(--panel);
  color: var(--ink);
  font-size: var(--text-sm);
}
.cg-formula-real-select {
  width: auto;
  max-width: 160px;
}

/* ── Round totals ("where this goes") ──────────────────────────────── */
.cg-formula-totals {
  margin-bottom: var(--space-md, 12px);
}

.cg-formula-totals-label {
  margin: 0 0 8px;
  font-size: var(--text-sm);
  color: var(--muted);
}

.cg-formula-totals-list {
  display: flex;
  flex-wrap: wrap;
  gap: 8px 14px;
}

.cg-formula-totals-row {
  display: flex;
  align-items: center;
  gap: 6px;
  padding: 4px 10px;
  border: 1px solid var(--line-soft, var(--line));
  border-radius: var(--radius-md);
  background: var(--panel-alt);
}

.cg-formula-totals-name {
  font-size: var(--text-sm);
  color: var(--ink);
}

/* Colour comes from the win/loss tone classes (banker-total-positive/
   -negative, applied by renderRoundTotals) — a flat accent for every row
   made a player down $13,000 look identical to one up $17,500. This is the
   zero/untoned fallback only. */
.cg-formula-totals-value {
  font-size: var(--text-sm);
  font-weight: 600;
  color: var(--ink);
}

/* ── "How these totals are worked out" ─────────────────────────────── */
/* [totals-story 2026-07-25] The across-all-holes explanation: the settlement
   rule, any state that accumulates hole to hole, the end-of-round
   settlement, and a per-player hole-by-hole breakdown that adds up to the
   displayed total. See settlement-narrate.js. */
.cg-formula-howtotal {
  margin-top: 10px;
  padding-top: 10px;
  border-top: 1px solid var(--line-soft, var(--line));
}
.cg-formula-howtotal-label {
  margin: 0 0 6px;
  font-size: var(--text-xs);
  font-weight: 600;
  text-transform: uppercase;
  letter-spacing: 0.02em;
  color: var(--muted);
}
.cg-formula-howtotal-line {
  margin: 0 0 6px;
  font-size: var(--text-sm);
  color: var(--ink);
  line-height: 1.4;
}
.cg-formula-howtotal-list {
  margin: 0 0 6px;
  padding-left: 18px;
  font-size: var(--text-sm);
  color: var(--ink);
  line-height: 1.45;
}
.cg-formula-howtotal-term { font-weight: 600; }

/* The real achievement/tally catalog behind "how these are worked out" —
   see describeTallyFormulaSource. Same chip shape as the real in-game tally
   widget (.custom-game-tally-chip), just non-interactive here: this is the
   catalog explainer, not the editable copy (that's the seed editor below). */
.cg-formula-tally-catalog {
  display: flex;
  flex-wrap: wrap;
  gap: 6px;
  margin: 0 0 6px;
}
.cg-formula-tally-catalog-chip {
  display: inline-flex;
  align-items: center;
  gap: 4px;
  padding: 3px 8px;
  border: 1px solid var(--line);
  border-radius: var(--radius-lg, 16px);
  background: var(--panel);
  font-size: var(--text-xs);
}
.cg-formula-tally-catalog-label { color: var(--ink); }
.cg-formula-tally-catalog-pts { color: var(--muted); font-weight: 600; }

.cg-formula-howtotal-details { margin-top: 6px; }
.cg-formula-howtotal-summary {
  font-size: var(--text-xs);
  color: var(--muted);
  cursor: pointer;
}
.cg-formula-howtotal-summary:hover { color: var(--ink); }

.cg-formula-holebreak { margin-top: 8px; }
.cg-formula-holebreak-name {
  margin: 0 0 4px;
  font-size: var(--text-sm);
  font-weight: 600;
  color: var(--ink);
}
.cg-formula-holebreak-strip {
  display: flex;
  flex-wrap: wrap;
  gap: 4px;
}
.cg-formula-holebreak-cell {
  display: flex;
  flex-direction: column;
  align-items: center;
  min-width: 46px;
  padding: 3px 5px;
  border: 1px solid var(--line);
  border-radius: var(--radius-sm, 6px);
  background: var(--panel);
  font-variant-numeric: tabular-nums;
}
.cg-formula-holebreak-total {
  border-width: 2px;
  border-color: var(--accent);
}
.cg-formula-holebreak-hole {
  font-size: var(--text-xs);
  color: var(--muted);
}
.cg-formula-holebreak-delta {
  font-size: var(--text-sm);
  font-weight: 600;
}

/* ── Story (numbered step-by-step narrative) ───────────────────────── */
.cg-formula-story {
  margin-bottom: var(--space-md, 12px);
}

.cg-formula-story-label {
  margin: 0 0 6px;
  font-size: var(--text-xs);
  font-weight: 600;
  text-transform: uppercase;
  letter-spacing: 0.02em;
  color: var(--muted);
}

.cg-formula-story-list {
  margin: 0;
  /* No padding-left / list marker — .cg-formula-story-item's own flex
     display suppresses the native <ol> marker anyway, so the step number is
     painted explicitly as .cg-formula-story-num (see renderStorySteps). */
  padding-left: 0;
  list-style: none;
  display: flex;
  flex-direction: column;
  gap: 6px;
}

.cg-formula-story-item {
  font-size: var(--text-sm);
  color: var(--ink);
  display: flex;
  flex-wrap: wrap;
  align-items: baseline;
  gap: 6px;
}

/* The step's own "Step N." — every other sentence in the list references
   these numbers, so they have to be scannable down the left edge. */
.cg-formula-story-num {
  flex: 0 0 auto;
  min-width: 4.5em;
  font-weight: 600;
  color: var(--muted);
  font-variant-numeric: tabular-nums;
}

.cg-formula-story-text {
  flex: 1 1 auto;
  min-width: 0;
}

.cg-formula-story-value {
  flex: 0 0 auto;
  font-weight: 600;
  color: var(--accent);
  background: color-mix(in srgb, var(--accent) 16%, transparent);
  border-radius: var(--radius-sm, 6px);
  padding: 1px 6px;
  white-space: nowrap;
}

/* [2026-07-27] "I have no idea what those [green] numbers are for" — see
   renderStoryStepItem's own header comment for the full reasoning. These
   three rules are purely presentational: the "=" makes each row read as an
   equation instead of a sentence with a mystery chip stuck on the end; the
   legend sentence above the list says once, in plain English, what a chip
   IS and how later rows reuse earlier ones; and the FINAL row (the actual
   formula outcome) is called out so it doesn't look like just another
   intermediate number in the middle of the list. */
.cg-formula-story-legend {
  margin: 0 0 8px;
  font-size: var(--text-xs);
  color: var(--muted);
  line-height: 1.4;
}

.cg-formula-story-eq {
  flex: 0 0 auto;
  color: var(--muted);
  font-weight: 600;
}

.cg-formula-story-item-final {
  margin-top: 2px;
  padding-top: 6px;
  border-top: 1px dashed color-mix(in srgb, var(--ink) 15%, transparent);
}

.cg-formula-story-final-tag {
  flex: 0 0 auto;
  font-size: var(--text-xs);
  font-weight: 600;
  text-transform: uppercase;
  letter-spacing: 0.02em;
  color: var(--muted);
}

.cg-formula-story-value-final {
  font-size: var(--text-base, 1rem);
  padding: 2px 9px;
}
/* banker-total-positive/negative (formatAmountToned's tone classes, the
   cross-app money sign convention) override the chip's default accent
   background/color when the final result is money — same posture as every
   other total in this app, see custom-game.css's other references to this
   convention. */
.cg-formula-story-value-final.banker-total-positive,
.cg-formula-story-value-final.banker-total-negative {
  background: color-mix(in srgb, currentColor 16%, transparent);
}

/* The untaken branches — reachable, but not interleaved with the story of
   what actually happened. */
.cg-formula-story-dead {
  margin-top: 8px;
}
.cg-formula-story-dead-summary {
  font-size: var(--text-xs);
  color: var(--muted);
  cursor: pointer;
}
.cg-formula-story-dead-summary:hover { color: var(--ink); }
.cg-formula-story-dead .cg-formula-story-list {
  margin-top: 6px;
  opacity: 0.75;
}

/* The wiring diagram is the deepest-detail surface on the page — collapsed
   by default so the scorecard/story/totals aren't buried under 4+ screens
   of boxes (measured: Banker 3890px, Vegas 6750px before the depth collapse
   was restored). */
.cg-formula-graph-details {
  margin-top: var(--space-md, 12px);
  border-top: 1px solid var(--line-soft, var(--line));
  padding-top: var(--space-md, 12px);
}
.cg-formula-graph-summary {
  font-size: var(--text-sm);
  font-weight: 600;
  color: var(--ink);
  cursor: pointer;
}
.cg-formula-graph-summary:hover { color: var(--accent); }

.cg-formula-tree-wrap {
  overflow-x: auto;
}

.cg-formula-node {
  display: flex;
  flex-direction: column;
  gap: 4px;
  margin: 4px 0;
}

.cg-formula-leaf {
  flex-direction: row;
  align-items: center;
  gap: 6px;
  flex-wrap: wrap;
}

.cg-formula-op-head,
.cg-formula-if-head {
  display: flex;
  align-items: center;
  gap: 6px;
  flex-wrap: wrap;
}

.cg-formula-op-label {
  font-weight: 600;
  font-size: var(--text-sm);
  color: var(--ink);
}

.cg-formula-reducer-over {
  font-size: var(--text-xs);
  color: var(--muted);
}

.cg-formula-const {
  font-size: var(--text-sm);
  color: var(--muted);
}

.cg-formula-var {
  font-size: var(--text-sm);
  color: var(--ink);
  background: color-mix(in srgb, var(--accent) 12%, transparent);
  border-radius: var(--radius-sm, 6px);
  padding: 1px 6px;
}

.cg-formula-value {
  font-size: var(--text-xs);
  font-weight: 600;
  color: var(--accent);
  background: color-mix(in srgb, var(--accent) 16%, transparent);
  border-radius: var(--radius-sm, 6px);
  padding: 1px 6px;
  white-space: nowrap;
}

.cg-formula-value-na {
  color: var(--muted);
  background: transparent;
}

.cg-formula-branch,
.cg-formula-child-label {
  padding-left: 12px;
  border-left: 2px solid color-mix(in srgb, var(--line) 70%, transparent);
}

.cg-formula-child-label {
  border-left: none;
  padding-left: 0;
  font-size: var(--text-xs);
  color: var(--muted);
  text-transform: uppercase;
  letter-spacing: 0.02em;
}

.cg-formula-branch-then .cg-formula-child-label,
.cg-formula-branch-else .cg-formula-child-label,
.cg-formula-branch-cond .cg-formula-child-label {
  border-left: 2px solid color-mix(in srgb, var(--line) 70%, transparent);
  padding-left: 12px;
  text-transform: none;
  letter-spacing: normal;
}

/* Which branch actually fired for the selected scenario — the single
   highest-leverage affordance in the whole view, since it's what turns a
   static formula shape into "here's what actually happened". */
.cg-formula-branch-taken {
  border-left-color: var(--accent);
}

.cg-formula-branch-not-taken {
  opacity: 0.45;
}

.cg-formula-reducer-of {
  border-left-style: dashed;
}

.cg-formula-collapsed {
  flex-direction: row;
  align-items: center;
  gap: 6px;
}

.cg-formula-expand-btn {
  background: none;
  border: 1px dashed var(--line);
  border-radius: var(--radius-sm, 6px);
  color: var(--muted);
  font-size: var(--text-xs);
  padding: 2px 8px;
  cursor: pointer;
}

.cg-formula-expand-btn:hover,
.cg-formula-expand-btn:focus-visible {
  color: var(--ink);
  border-color: var(--accent);
}

.cg-formula-expanded-wrap {
  display: contents;
}

/* =============================================================================
   Visual UGE Builder — interactive formula editor (Phase 2)
   ============================================================================= */

.custom-game-formula-header-row {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--space-sm, 8px);
  flex-wrap: wrap;
}

.custom-game-formula-edit-toggle,
.custom-game-formula-json-toggle {
  font-size: var(--text-sm);
}

/* [uge-builder 2026-07-24] raw-JSON editor — the escape hatch for
   state/stateUpdate/finalSettlement, which the visual formula editor
   doesn't reach yet (see appendJsonEditor's own header in create-modal.js). */
.custom-game-json-editor-label {
  font-size: var(--text-sm);
  color: var(--muted);
  margin: 0 0 var(--space-sm, 8px);
}

.custom-game-json-editor-input {
  width: 100%;
  min-height: 320px;
  font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
  font-size: var(--text-xs);
  line-height: 1.4;
  background: var(--panel-alt);
  color: var(--ink);
  border: 1px solid var(--line);
  border-radius: var(--radius-sm, 6px);
  padding: var(--space-sm, 8px);
  resize: vertical;
  box-sizing: border-box;
}

.custom-game-json-editor-status {
  font-size: var(--text-sm);
  margin: var(--space-sm, 8px) 0 0;
}

.custom-game-json-editor-status-ok {
  color: var(--accent);
}

.custom-game-json-editor-status-error {
  color: var(--danger, #ff6b6b);
}

.cg-formula-builder-status {
  border-radius: var(--radius-sm, 6px);
  padding: var(--space-sm, 8px) var(--space-md, 12px);
  margin-bottom: var(--space-sm, 8px);
  font-size: var(--text-sm);
}

.cg-formula-builder-status-ok {
  color: var(--accent);
  background: color-mix(in srgb, var(--accent) 10%, transparent);
}

.cg-formula-builder-status-error {
  color: var(--danger, #ff6b6b);
  background: color-mix(in srgb, var(--danger, #ff6b6b) 10%, transparent);
}

.cg-formula-builder-error-title {
  font-weight: 600;
  margin-bottom: 4px;
}

.cg-formula-builder-error-list {
  margin: 0;
  padding-left: 18px;
}

.cg-formula-op-picker {
  font-size: var(--text-xs);
  background: var(--panel-alt);
  color: var(--ink);
  border: 1px solid var(--line);
  border-radius: var(--radius-sm, 6px);
  padding: 1px 4px;
  max-width: 160px;
}

.cg-formula-var-picker,
.cg-formula-over-picker {
  font-size: var(--text-sm);
  background: color-mix(in srgb, var(--accent) 12%, transparent);
  color: var(--ink);
  border: 1px solid var(--line);
  border-radius: var(--radius-sm, 6px);
  padding: 1px 6px;
  max-width: 220px;
}

.cg-formula-const-input {
  font-size: var(--text-sm);
  background: var(--panel-alt);
  color: var(--ink);
  border: 1px solid var(--line);
  border-radius: var(--radius-sm, 6px);
  padding: 1px 6px;
  width: 90px;
}

.cg-formula-bool-toggle {
  font-size: var(--text-sm);
  background: color-mix(in srgb, var(--accent) 16%, transparent);
  color: var(--accent);
  border: 1px solid var(--line);
  border-radius: var(--radius-sm, 6px);
  padding: 1px 8px;
  cursor: pointer;
}

.cg-formula-op-picker:focus-visible,
.cg-formula-var-picker:focus-visible,
.cg-formula-over-picker:focus-visible,
.cg-formula-const-input:focus-visible,
.cg-formula-bool-toggle:focus-visible {
  outline: 2px solid var(--accent);
  outline-offset: 1px;
}

/* =============================================================================
   Node/wire "workflow editor" formula canvas (docs/js/games/custom/
   formula-node-canvas.js) — replaces the indented tree above as the default
   "see how this game works" view. Depth flows left-to-right via nested
   flexbox (browser does the subtree-centering math); a scrollable outer
   wrap handles real formulas running 11-15 levels deep the same way
   .cg-formula-tree-wrap already does above. Wires are a read-only,
   absolutely-positioned overlay computed from real box geometry after
   layout — see that file's own header for the full design.
   ============================================================================= */

/* States the reading direction and offers an explicit jump to the result —
   the canvas is far wider than a phone viewport and nothing else on screen
   said it scrolled. Replaces an earlier mount-time scrollIntoView() that
   hijacked the whole page's scroll position. */
.cgc-canvas-guide {
  display: flex;
  align-items: center;
  justify-content: space-between;
  flex-wrap: wrap;
  gap: 6px 10px;
  margin-bottom: 6px;
}
.cgc-canvas-guide-text {
  font-size: var(--text-xs);
  color: var(--muted);
}
.cgc-canvas-guide-jump {
  flex: 0 0 auto;
  padding: 4px 10px;
  border: 1px solid var(--line);
  border-radius: var(--radius-sm, 6px);
  background: var(--panel);
  color: var(--ink);
  font-size: var(--text-xs);
  cursor: pointer;
}
.cgc-canvas-guide-jump:hover { border-color: var(--accent); }

.cgc-canvas-scroll {
  overflow: auto;
  -webkit-overflow-scrolling: touch;
  position: relative;
  max-width: 100%;
}
.cgc-canvas-root {
  position: relative;
  display: inline-flex;
  min-width: 100%;
  box-sizing: border-box;
  padding: var(--space-lg, 16px);
}
.cgc-subtree {
  display: flex;
  flex-direction: row;
  align-items: center;
}
.cgc-children {
  display: flex;
  flex-direction: column;
  justify-content: center;
  gap: var(--space-md, 12px);
  /* Children render left of their parent box (see formula-node-canvas.js's
     buildSubtree) so the graph reads left-to-right: inputs → result. */
  margin-right: 40px;
}
.cgc-child {
  display: flex;
  flex-direction: row;
  align-items: center;
  gap: var(--space-sm, 8px);
}
.cgc-box {
  position: relative;
  z-index: 1;
  display: flex;
  align-items: center;
  flex-wrap: wrap;
  gap: 6px;
  border: 1px solid var(--line);
  border-radius: var(--radius-sm, 6px);
  padding: 6px 10px;
  background: var(--panel-alt);
  white-space: nowrap;
  max-width: 300px;
}
.cgc-box.cgc-collapsed {
  background: transparent;
  border-style: dashed;
}
.cgc-op-label {
  font-weight: 600;
  font-size: var(--text-sm);
}
.cgc-const,
.cgc-var {
  font-size: var(--text-sm);
}
.cgc-branch-label {
  font-size: var(--text-xs);
  color: var(--muted);
  text-transform: uppercase;
  letter-spacing: 0.02em;
  white-space: nowrap;
}
.cgc-reducer-over {
  font-size: var(--text-xs);
  color: var(--muted);
}
/* An IF box's condition result — kept visually distinct from the value
   chip, which always shows the box's OWN output (see paintBoxHead). */
.cgc-cond-badge {
  font-size: var(--text-xs);
  color: var(--muted);
  border: 1px solid var(--line);
  border-radius: var(--radius-sm, 6px);
  padding: 0 5px;
  white-space: nowrap;
}
.cgc-node-description {
  flex-basis: 100%;
  white-space: normal;
  font-size: var(--text-xs);
  color: var(--muted);
  line-height: 1.35;
}
/* Real user feedback: "I want the Actual UI elements... drawn in the flow
   canvas so its clear where things are being used" — a read-only preview of
   the SAME real widget (2x/3x buttons, Off/On chips, a player picker) real
   gameplay renders for this leaf's variable (formula-box-editors.js's
   renderRealVarWidget, readOnly:true). Its own controls are natively
   `disabled` (no listeners attached either) — this wrapper just gives it
   its own wrapped row within the box and keeps it legible rather than
   heavily dimmed, since the whole point is to show what it actually looks
   like. */
.cgc-real-widget {
  flex-basis: 100%;
  white-space: normal;
  margin-top: 2px;
}
.cgc-real-widget button:disabled,
.cgc-real-widget select:disabled,
.cgc-real-widget input:disabled {
  cursor: default;
  opacity: 1;
}
/* Add-a-step / remove-a-step — the fixed-arity AST's only honest form of
   "add and remove elements" (wrap / unwrap). See formula-edit-model.js. */
.cgc-box-actions {
  flex-basis: 100%;
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 6px;
  margin-top: 3px;
}
.cgc-box-action {
  background: transparent;
  border: 1px dashed var(--line);
  border-radius: var(--radius-sm, 6px);
  color: var(--muted);
  padding: 2px 7px;
  font: inherit;
  font-size: var(--text-xs);
  cursor: pointer;
  white-space: nowrap;
}
.cgc-box-action:hover:not(:disabled) {
  border-color: var(--accent);
  color: var(--ink);
}
.cgc-box-action:disabled { opacity: 0.45; cursor: default; }
.cgc-box-action:focus-visible { outline: 2px solid var(--accent); outline-offset: 1px; }
.cgc-box-action-cancel { border-style: solid; }
.cgc-box-keep-chooser {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 6px;
}
.cgc-box-keep-label {
  font-size: var(--text-xs);
  color: var(--muted);
}
.cgc-box-action-picker { max-width: 100%; }

.cgc-expand-btn {
  background: transparent;
  border: 1px dashed var(--line);
  border-radius: var(--radius-sm, 6px);
  color: var(--muted);
  padding: 4px 8px;
  font-size: var(--text-xs);
  cursor: pointer;
}
.cgc-expand-btn:focus-visible {
  outline: 2px solid var(--accent);
  outline-offset: 1px;
}
.cgc-wire-layer {
  position: absolute;
  inset: 0;
  pointer-events: none;
  z-index: 0;
}
.cgc-wire {
  position: absolute;
  height: 2px;
  background: var(--line);
  transform-origin: 0 50%;
}

/* Live-path highlighting (value-trace mode only — see formula-node-
   canvas.js's markLivePath): every box the executed path actually passed
   through vs. the untaken branch of an 'if' it did NOT. */
.cgc-box-live {
  border-color: var(--accent);
  background: color-mix(in srgb, var(--accent) 10%, var(--panel-alt));
}
.cgc-box-dead {
  opacity: 0.45;
}
/* The root box — the formula's own final result, distinguished beyond the
   rest of the live path. */
.cgc-box-final {
  border-width: 2px;
  background: color-mix(in srgb, var(--accent) 18%, var(--panel-alt));
}
.cgc-final-tag {
  flex-basis: 100%;
  order: -1;
  font-size: var(--text-xs);
  font-weight: 700;
  letter-spacing: 0.04em;
  color: var(--accent);
}
/* Real result-card markup reused verbatim (custom-game-hole-result-row/
   -desc/-amount — engine.js's buildCurrentHoleResult) so this reads as the
   EXACT UI a player would see, not a lookalike — see formula-node-
   canvas.js's own comment. Only the row/desc/amount classes are reused,
   not the outer .custom-game-hole-result wrapper (that one's fixed
   220px/border-left is scoped to its real side-column layout, which
   doesn't apply inside a graph box). */
.cgc-final-row {
  flex-basis: 100%;
  width: 100%;
  margin-top: 4px;
  padding-top: 4px;
  border-top: 1px solid color-mix(in srgb, var(--accent) 30%, transparent);
}
/* This app's shared sign-prefix + toFixed + win/loss tone convention
   (formatAmountCellOpts in engine.js) — defined there and in banker.css,
   neither of which composer.html loads; mirrored here so
   .custom-game-hole-result-amount's tone class actually renders on this
   page. Keep in sync with banker.css's own copy if either ever changes. */
.banker-total-positive {
  color: var(--accent);
}
.banker-total-negative {
  color: var(--danger);
}

/* =============================================================================
   Visual UGE Builder — "open real rules in the Composer" preset-fork shortcut
   ============================================================================= */

.custom-game-skip-to-composer {
  border: 1px dashed var(--accent);
  border-radius: var(--radius-md, 12px);
  padding: var(--space-sm, 8px) var(--space-md, 12px);
  margin-bottom: var(--space-md, 12px);
  background: color-mix(in srgb, var(--accent) 8%, transparent);
}

.custom-game-skip-to-composer .btn {
  width: 100%;
}
