/* =============================================================================
   MoneyEdu Live - standardized design system.

   Rule: COLOR is the only thing a credit union changes (two colors + a logo,
   applied at runtime by brand.js). Everything else here - type, spacing,
   radii, tap targets, shadows, semantic colors - is identical for every
   customer, so there is exactly one look to maintain.

   Those two colors arrive raw and unvetted. brand.js derives a contrast-safe
   set from them; anything carrying text or a border must consume a derived
   token (--on-*, *-ink, *-line, *-bright) rather than the raw brand color.

   Design language: quiet, precise, Apple-like. Hairline borders, soft layered
   shadows, tight heading tracking, one accent at a time.

   CONSTRAINT, not preference: the top of every page is a PURE WHITE band that
   holds the customer logo. We are not guaranteed a transparent logo - many CU
   logos ship with a baked-in white background, so a logo on any tinted, dark
   or gray surface shows as a white rectangle. One white band per surface,
   nothing above it. Brand everywhere else with COLOR instead.
   ============================================================================= */

:root {
  /* ---- Brand slots (overwritten at runtime by brand.js) ----
     These static values are the pre-branding fallback and are generated by
     brand.js from the SAME defaults SessionService.hashColor() falls back to
     (#1E2761 / #CADCFC). They used to be a different brand entirely
     (#002F87 / #00A5B5), so a surface painted before the branding call
     returned flashed one brand and settled on another. To regenerate:
       node -e "const B=require('./app/shared/brand.js');
                const t=B.deriveTokens('#1E2761','#CADCFC');
                for(const k in t) console.log('  '+k+': '+t[k]+';')"

     Tokens ending -ink and -line are contrast-targeted shades for brand
     colour used AS TEXT or AS A BORDER on a light card. Use those, never the
     raw --brand-primary, anywhere the brand colour has to be legible against
     --card: the raw value is whatever the credit union sent us. */
  --brand-primary: #1e2761;
  --brand-secondary: #cadcfc;
  --on-primary: #ffffff;
  --on-secondary: #000000;
  --brand-primary-hover: #12173a;
  --brand-primary-active: #0b0e23;
  --brand-primary-ink: #1e2761;
  --brand-secondary-ink: #1c69f2;
  --brand-primary-line: #1e2761;
  --brand-primary-border: #1e2761;
  --brand-primary-soft: #ecedf9;
  --brand-secondary-soft: #e7effe;
  --brand-primary-bright: #6775d0;
  --brand-secondary-bright: #cadcfc;

  /* ---- Standardized: never varies per customer ---- */
  --font-sans: -apple-system, BlinkMacSystemFont, "SF Pro Text", system-ui,
               "Segoe UI", Roboto, "Helvetica Neue", Arial, "Noto Sans", sans-serif;
  --ink: #1d1d1f;
  --ink-soft: #6e6e73;
  /* CONVERGED WITH --ink-soft ON PURPOSE, and the name is kept only so the
     ~10 call sites do not churn.
     It was #86868b: 3.62:1 on white and 3.33:1 on --surface, both under WCAG
     1.4.3's 4.5:1 for normal text, and this token carries real information on
     four surfaces (the audience header's nickname/connection line, the
     presenter and remote kickers, the screen's deck kicker, the admin section
     name and panel labels, and .npc-name inside a game bubble, which sits on
     --surface). An intermediate #757579 cleared white at 4.59:1 but only
     managed 4.21:1 on --surface and would have shipped a rule that passes on
     one background and fails on the other.
     There is no grey that is meaningfully FAINTER than --ink-soft and still
     AA for normal text on a #f5f5f7 panel - so this stops pretending there
     is. #6e6e73 clears --surface at 4.66:1 and --card at 5.07:1.
     Anything that lightens this again must clear 4.5:1 against --surface, not
     just against white; tests/layout/accessibility.spec.js enforces it. */
  --ink-faint: #6e6e73;
  --surface: #f5f5f7;
  --card: #ffffff;
  --line: #e8e8ed;
  --line-strong: #d2d2d7;
  /* The projector. These are pre-branding fallbacks: brand.js overwrites all
     but --stage-line at runtime with shades carrying the customer's hue, so
     the one surface a whole room looks at for 45 minutes is theirs rather
     than a flat near-black identical for every credit union.
     --stage-line stays a white alpha on purpose - it composites correctly
     over any stage colour, so there is nothing to derive. */
  --stage: #090a10;          /* big-screen background */
  --stage-panel: #191b29;
  --stage-ink: #8d91b0;      /* muted copy on the stage (>=4.5:1) */
  --stage-ink-soft: #737aa5; /* hairline/graphic weight only (>=3:1) */
  --stage-line: rgba(255, 255, 255, .08);

  --ok: #23a55a;             /* semantic - correct answers stay green */
  --ok-soft: #e8f6ee;
  --warn: #e6a23c;           /* for every brand; only brand accents change */
  /* It was #d64545, which was fine while this token only drew borders and
     backgrounds. The admin console's row actions made it TEXT - a 13.6px
     "cancel" on --card - and 4.37:1 is under WCAG 1.4.3's 4.5:1 for normal
     text. Every place this token carries text sits on --card or --surface
     (admin row actions, the builder's retired chip, .mini.danger, .json-err
     and the asset-path warning, the audience reveal's down-delta), never on
     --stage, so darkening it only ever helps. #c93c3c clears --card at
     5.01:1 and --surface at 4.60:1; anything that lightens this again has to
     clear BOTH, and tests/layout/accessibility.spec.js enforces it. */
  --danger: #c93c3c;

  --radius-card: 20px;
  --radius-btn: 12px;
  --radius-chip: 999px;
  --shadow-card: 0 1px 2px rgba(16, 19, 31, .04), 0 10px 30px rgba(16, 19, 31, .07);
  --shadow-pop: 0 2px 6px rgba(16, 19, 31, .07), 0 18px 50px rgba(16, 19, 31, .14);
  --tap-min: 48px;
}

/* ---- Shared component kit (audience-facing light surfaces) ---- */

body.mel-app {
  background: var(--surface); font-family: var(--font-sans); color: var(--ink);
  -webkit-font-smoothing: antialiased; text-rendering: optimizeLegibility;
}
body.mel-app h1, body.mel-app h2, body.mel-app h3 { letter-spacing: -.015em; }

/* Brand ribbon: the one always-on brand element on light surfaces. Works
   with any two colors because it never carries text. Sits BELOW the white
   header band - the very top of the page stays white for the logo. */
.brand-ribbon {
  height: 3px;
  background: linear-gradient(90deg, var(--brand-primary), var(--brand-secondary));
}

/* The header band is PURE WHITE, full-width, and the logo sits directly on
   it: many CU logos ship with a baked-in white background, and white-on-white
   makes rectangular logos and transparent ones look identical. Never place a
   logo on a tinted, dark, or gray surface - and never put anything above
   this band. */
.mel-headerband {
  background: #ffffff;
  border-bottom: 1px solid var(--line);
}
.mel-header {
  display: flex; justify-content: space-between; align-items: center;
  padding: .8rem 0; min-height: 56px;
}
.mel-header .cu-logo { max-height: 36px; max-width: 200px; object-fit: contain; }
.mel-header .cu-name { font-weight: 700; letter-spacing: -.01em; color: var(--brand-primary); }
.mel-header .header-meta { font-size: .85rem; color: var(--ink-faint); }

.card-elev { border: 0; border-radius: var(--radius-card); background: var(--card);
  box-shadow: var(--shadow-card); }

/* =============================================================================
   Buttons - and the cascade trap that made them disappear.

   These kit classes are used BOTH standalone and alongside Bootstrap's .btn
   (e.g. `class="w-100 btn btn-join"` on the join screen). That combination
   used to make the button VANISH on hover. Bootstrap 5.3 says:

     .btn       { --bs-btn-bg: transparent; background-color: var(--bs-btn-bg); }
     .btn:hover { color: var(--bs-btn-hover-color);
                  background-color: var(--bs-btn-hover-bg); }

   For a bare .btn with no variant class (.btn-primary etc.) --bs-btn-hover-bg
   is never defined. An unresolvable var() makes the declaration invalid at
   computed-value time, so background-color computes to `initial` - transparent
   - and does NOT fall back to the earlier cascade value. `.btn:hover` is
   (0,2,0); our rest-state background was (0,1,0), so on hover the fill was
   wiped while the label colour survived: white text on a white panel.

   That is invisible on a phone (no hover) and fatal on a Chromebook or tablet
   trackpad - including on the Join button, the single most important control
   in the product.

   The fix is two-part, and both parts are load-bearing:
     (a) define the --bs-btn-* variables Bootstrap reads, so its own rules
         resolve to the right colour instead of to `initial`;
     (b) set background-color explicitly in every state, so the button is
         still correct when Bootstrap is not on the page at all.

   Hover lives inside @media (hover: hover) so a tap on a touch device does
   not leave the button stuck in its hover fill.

   Backgrounds come from brand.js's derived --brand-primary-hover/-active,
   which move AWAY from the label colour. Do not go back to filter:
   brightness() - a filter brightens the label along with the fill, clips at
   white, and cannot be asserted in a test.
   ============================================================================= */

.btn-brand {
  --bs-btn-color: var(--on-primary);
  --bs-btn-bg: var(--brand-primary);
  --bs-btn-border-color: transparent;
  --bs-btn-hover-color: var(--on-primary);
  --bs-btn-hover-bg: var(--brand-primary-hover);
  --bs-btn-hover-border-color: transparent;
  --bs-btn-active-color: var(--on-primary);
  --bs-btn-active-bg: var(--brand-primary-active);
  --bs-btn-active-border-color: transparent;
  --bs-btn-disabled-color: var(--on-primary);
  --bs-btn-disabled-bg: var(--brand-primary);
  --bs-btn-disabled-border-color: transparent;

  display: block; width: 100%; min-height: var(--tap-min);
  background-color: var(--brand-primary); color: var(--on-primary);
  border: 0; border-radius: var(--radius-btn);
  font-weight: 600; font-size: 1.02rem; letter-spacing: -.005em;
  padding: .7rem 1rem;
  transition: background-color .15s ease, transform .1s ease, box-shadow .15s ease;
}
@media (hover: hover) {
  .btn-brand:hover:not(:disabled) {
    background-color: var(--brand-primary-hover); color: var(--on-primary);
  }
}
.btn-brand:disabled { opacity: .5; background-color: var(--brand-primary); color: var(--on-primary); }
.btn-brand:active:not(:disabled) {
  transform: scale(.985); background-color: var(--brand-primary-active); color: var(--on-primary);
}

.option-btn {
  display: block; width: 100%; min-height: var(--tap-min); text-align: left;
  padding: .95rem 1.1rem; margin-bottom: .65rem;
  border: 1.5px solid var(--line-strong); border-radius: 14px;
  background-color: var(--card); color: var(--ink); font-size: 1.02rem;
  transition: transform .08s ease, border-color .15s ease, background-color .15s ease;
}
@media (hover: hover) {
  .option-btn:hover:not(:disabled):not(.selected) { border-color: var(--brand-primary-line); }
}
.option-btn:active { transform: scale(.985); }
/* Selected state carries THREE signals, not one. The border used to be the
   raw --brand-primary, which fails 1.4.11's 3:1 against white for every warm
   or mid-tone brand (#FFD200 = 1.45:1, #F5A623 = 2.03:1, #E87722 = 2.96:1) -
   so for those customers a selected answer looked unselected. It now uses the
   contrast-targeted line shade, and weight plus a checkmark carry the state
   for anyone who cannot separate the hues at all (1.4.1). */
.option-btn.selected {
  border-color: var(--brand-primary-line);
  border-width: 2px;
  background-color: var(--brand-primary-soft);
  font-weight: 600;
}
.option-btn.selected::after {
  content: '\2713';
  float: right; margin-left: .5rem;
  font-weight: 700; color: var(--brand-primary-ink);
}
.option-btn .opt-key { font-weight: 700; text-transform: uppercase;
  color: var(--brand-primary-ink); margin-right: .55rem; }

.rating-btn {
  width: 52px; height: 52px; border-radius: 50%;
  border: 1.5px solid var(--line-strong); background-color: var(--card);
  font-size: 1.15rem; font-weight: 700; color: var(--ink);
  transition: transform .08s ease, border-color .15s ease, background-color .15s ease;
}
@media (hover: hover) {
  .rating-btn:hover:not(.selected) { border-color: var(--brand-primary-line); }
}
.rating-btn:active { transform: scale(.94); }
.rating-btn.selected { background-color: var(--brand-primary); color: var(--on-primary);
  border-color: var(--brand-primary-line); }

/* The countdown is the one piece of chrome that competes with the answer
   buttons for attention while a question is open, and it used to be the same
   --brand-primary they are. Secondary is the token with the least to do on
   light surfaces, and "time remaining" is exactly the kind of ambient,
   non-actionable signal it should carry. -ink rather than the raw colour
   because the raw secondary is often a nav-hover tint: measured, it clears
   4.06:1 on its own track for the worst brand in the fixture set, against the
   3:1 that 1.4.11 asks of a graphic. When a customer's two colours are close,
   this degrades to looking exactly as it did before. */
.timer-track { height: 5px; background: var(--brand-secondary-soft); border-radius: 3px; }
.timer-bar { height: 5px; background: var(--brand-secondary-ink); border-radius: 3px;
  transition: width .25s linear; }

/* Brand colour AS TEXT on a light card needs the contrast-targeted ink shade:
   the raw brand colour is unreadable here for any light brand. */
.score-toast { font-size: 2.4rem; font-weight: 800; letter-spacing: -.02em;
  color: var(--brand-primary-ink); font-variant-numeric: tabular-nums; }

/* Status glyphs: quiet circled icons instead of emoji - the audience app is
   often a roomful of adults at a member seminar. */
.status-glyph { width: 56px; height: 56px; margin: 0 auto .75rem; }
.status-glyph svg { width: 100%; height: 100%; display: block; }

.conn-dot { width: 8px; height: 8px; border-radius: 50%; display: inline-block; }
.pulse { animation: mel-pulse 1.6s infinite; }
@keyframes mel-pulse { 0%, 100% { opacity: .55; } 50% { opacity: 1; } }

/* =============================================================================
   Focus.

   Applies to every surface, not just body.mel-app - that class is only added
   to the audience app AFTER join, which left the join screen, the admin
   console, the presenter console, the remote and the projector with no
   visible focus indicator at all.

   The ring is drawn with box-shadow so it can sit inside the element's own
   radius, but box-shadow is STRIPPED in Windows High Contrast / forced-colors
   mode, which schools do enable. So every ring is paired with a transparent
   outline that the UA repaints as a system colour in that mode.
   ============================================================================= */
/* `.btn:focus-visible` is listed explicitly because Bootstrap has the SAME
   undefined-variable trap on focus that it has on hover:
     .btn:focus-visible { box-shadow: var(--bs-btn-focus-box-shadow); }
   undefined for a bare .btn, so it computes to `none` - and at (0,2,0) it
   outranks a plain `:focus-visible`, silently erasing the ring on every
   button that pairs a kit class with .btn. Matching its specificity and
   loading after Bootstrap is what makes the ring stick. */
:focus-visible,
.btn:focus-visible {
  outline: 2px solid transparent;
  outline-offset: 2px;
  box-shadow: 0 0 0 3px var(--card), 0 0 0 5px var(--brand-primary-line);
}
/* Dark working surfaces (presenter, remote, projector): the inner halo has to
   be the dark panel, not white, or the ring reads as a glowing box. */
body.mel-dark :focus-visible,
body.mel-dark .btn:focus-visible {
  box-shadow: 0 0 0 3px var(--stage-panel), 0 0 0 5px var(--brand-primary-bright);
}
@media (forced-colors: active) {
  :focus-visible, .btn:focus-visible { outline-color: Highlight; box-shadow: none; }
}
@media (prefers-reduced-motion: reduce) {
  .btn-brand, .option-btn, .rating-btn { transition: none; }
}
