/* JNY shell — phone-first. Tablet & desktop tweaks at the bottom. */

* { box-sizing: border-box; }

/* Build Edition theme tints — body[data-edition] set by app.js so users
   see which Edition they're on without reading the URL. */
body[data-edition="jf"]  .status-strip { border-bottom-color: #6cd6c0; }
body[data-edition="jnf"] .status-strip { border-bottom-color: #ff8c66; }
body[data-edition="jf"]  .status-strip::after,
body[data-edition="jnf"] .status-strip::after {
  position: absolute; right: 10px; top: 4px;
  font-size: 10px; font-weight: 600;
  text-transform: uppercase; letter-spacing: .5px;
}
body[data-edition="jf"]  .status-strip::after { content: "Journey Friends"; color: #6cd6c0; }
body[data-edition="jnf"] .status-strip::after { content: "First Responder"; color: #ff8c66; }
.status-strip { position: relative; }


/* ─── Theme tokens — three modes for varying light conditions ──────────────
   DARK:    indoor/evening default. Low-fatigue.
   OUTDOOR: bright-sun, glare-resistant. Near-white text on near-black bg,
            saturated high-chroma accents, heavier type weight, thicker
            scrollbars + focus rings. Built for a grandparent on a park bench
            in July noon.
   LIGHT:   full-sun alternative. Dark text on bright bg — sometimes reads
            better than dark-mode+glare since bright screens at max nit
            beat reflected skylight. User picks.

   Toggle cycles dark → outdoor → light and sets data-theme on <html>.
   prefers-contrast: more auto-upgrades dark → outdoor. prefers-color-scheme:
   light auto-picks light when the OS says so (user can still override). */

html[data-theme="dark"] {
  --bg:             #070812;
  --surface:        #0f1226;
  --surface-2:      #171a33;
  --text:           #e9eaf2;
  --text-dim:       #a7a9bd;
  --border:         rgba(255,255,255,0.08);
  --accent:         #8c8ff0;
  --accent-strong:  #a7aaff;
  --danger:         #ff6b7a;
  --success:        #4ad29a;
  --focus-ring:     0 0 0 2px rgba(167, 170, 255, 0.5);
  --jny-scroll-thumb:       rgba(180, 170, 220, 0.28);
  --jny-scroll-thumb-hover: rgba(200, 190, 240, 0.52);
  --jny-scroll-thumb-active:rgba(220, 210, 250, 0.75);
  --jny-scroll-track:       transparent;
  /* Chrome (status-strip / flip-bar / comm-bar) — dark mode keeps the dark navy band. */
  --chrome-bg:      #1a1f3a;
  --chrome-bg-2:    #252b4a;
  --chrome-text:    #e8eaf5;
  --chrome-text-dim:#c9cce8;
  --chrome-border:  #252b4a;
  --chrome-accent:  #D4A84B;  /* Honey, per gm-brand-tokens */
  --emer-bg:        #b03a2e;  /* button bg — solid red */
  --emer-fg:        #ff8a7a;  /* heading on dark surfaces */
}

html[data-theme="outdoor"] {
  /* WCAG AAA-target contrast. Pure white on pure black. Wider borders,
     thicker focus, heavier scrollbars. No subtle grays that wash to white
     under direct sunlight. */
  --bg:             #000000;
  --surface:        #0b0b14;
  --surface-2:      #151525;
  --text:           #ffffff;
  --text-dim:       #e0e2ef;
  --border:         rgba(255,255,255,0.24);
  --accent:         #b9bcff;     /* brighter — survives glare */
  --accent-strong:  #d8dbff;
  --danger:         #ff9ea6;
  --success:        #7fffbd;
  --focus-ring:     0 0 0 3px #ffffff, 0 0 0 6px #000000;
  --jny-scroll-thumb:       rgba(255,255,255,0.6);
  --jny-scroll-thumb-hover: rgba(255,255,255,0.85);
  --jny-scroll-thumb-active:#ffffff;
  --jny-scroll-track:       rgba(255,255,255,0.1);
  /* Chrome — outdoor mode is pure-black/pure-white extremes for sun glare */
  --chrome-bg:      #000000;
  --chrome-bg-2:    #151525;
  --chrome-text:    #ffffff;
  --chrome-text-dim:#e0e2ef;
  --chrome-border:  rgba(255,255,255,0.4);
  --chrome-accent:  #E8C977;  /* Honey-light for outdoor visibility */
  --emer-bg:        #b03a2e;
  --emer-fg:        #ffb0a4;  /* lighter for outdoor glare */
}

:root, html[data-theme="light"] {
  /* L1 Midnight Pearl — luxury light theme, and the DEFAULT root (Ogilvy #1,
     FOUNDATIONAL_PRINCIPLES #1: surfaces default LIGHT). Navy/ink on pearl/white.
     A themeless / first-paint / JS-failed render lands here = LIGHT, never a
     flash-of-dark. Dark is opt-in only via html[data-theme="dark"].
     WCAG-verified: body/heading AAA, muted AA. See _CANON/brand/README.md. */
  --bg:             #fbf9e4;   /* pearl ground */
  --surface:        #ffffff;
  --surface-2:      #f2ead7;
  --text:           #14171f;   /* ink — AAA */
  --text-dim:       #5b6473;   /* muted — AA */
  --border:         #c9c3ac;
  --accent:         #122c4f;   /* deep navy */
  --accent-strong:  #1b3a66;
  --danger:         #b3261e;
  --success:        #1e7d4f;
  --focus-ring:     0 0 0 2px rgba(18,44,79,0.55);
  --jny-scroll-thumb:       rgba(18,44,79,0.30);
  --jny-scroll-thumb-hover: rgba(18,44,79,0.52);
  --jny-scroll-thumb-active:rgba(18,44,79,0.78);
  --jny-scroll-track:       rgba(18,44,79,0.06);
  /* Chrome — navy band, white text, ocean accent (no honey/indigo). */
  --chrome-bg:      #122c4f;
  --chrome-bg-2:    #1b3a66;
  --chrome-text:    #ffffff;
  --chrome-text-dim:#c8d0dc;
  --chrome-border:  rgba(255,255,255,0.12);
  --chrome-accent:  #5b88b2;   /* ocean */
  --emer-bg:        #b3261e;
  --emer-fg:        #b3261e;
}

/* High-contrast themeless render: stay LIGHT (Ogilvy #1 — never a dark default),
   but push to pure black-on-white for maximum contrast. An explicit user toggle
   still wins (they'd have set data-theme); JS may map prefers-contrast to the
   'outdoor' theme post-load, but first paint must remain light. */
@media (prefers-contrast: more) {
  :root:not([data-theme]) {
    --bg:             #ffffff;
    --surface:        #ffffff;
    --surface-2:      #f2ead7;
    --text:           #000000;
    --text-dim:       #26303d;
    --border:         #14171f;
    --accent:         #0a1f3a;
    --accent-strong:  #122c4f;
    --danger:         #8f0e08;
    --success:        #0d5a34;
    --focus-ring:     0 0 0 3px #000000, 0 0 0 6px #ffffff;
    --jny-scroll-thumb:       rgba(0,0,0,0.6);
    --jny-scroll-thumb-hover: rgba(0,0,0,0.85);
    --jny-scroll-thumb-active:#000000;
    --jny-scroll-track:       rgba(0,0,0,0.1);
  }
}

/* ─── Scrollbars (use the tokens above) ───────────────────────────────────── */
html { scrollbar-width: thin; scrollbar-color: var(--jny-scroll-thumb) var(--jny-scroll-track); }
*::-webkit-scrollbar        { width: 8px; height: 8px; }
*::-webkit-scrollbar-track  { background: var(--jny-scroll-track); }
*::-webkit-scrollbar-thumb  {
  background: var(--jny-scroll-thumb);
  border-radius: 8px;
  border: 2px solid transparent;      /* visual breathing room inside the 8px lane */
  background-clip: padding-box;
}
*::-webkit-scrollbar-thumb:hover  { background: var(--jny-scroll-thumb-hover); background-clip: padding-box; }
*::-webkit-scrollbar-thumb:active { background: var(--jny-scroll-thumb-active); background-clip: padding-box; }
*::-webkit-scrollbar-corner { background: transparent; }
/* Don't draw a scrollbar lane just for overflow-x on mobile — looks noisy. */
@media (pointer: coarse) {
  *::-webkit-scrollbar        { width: 3px; height: 3px; }
  *::-webkit-scrollbar-thumb  { border: 0; background-clip: border-box; }
}

html, body {
  margin: 0;
  padding: 0;
  height: 100%;
  overflow: hidden;
  overflow-x: hidden;
  overscroll-behavior: contain;
  font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif;
  background: var(--bg);
  color: var(--text);
  -webkit-font-smoothing: antialiased;
  -webkit-tap-highlight-color: transparent;
}

/* App frame: phone-width on all viewports. Web = phone-in-a-window, not expanded desktop. */
body {
  display: flex;
  align-items: stretch;
  justify-content: center;
}

.shell {
  display: grid;
  grid-template-rows: auto 1fr auto auto;
  position: relative;    /* menu-inbounds 2026-06-29: containing block for in-shell popovers */
  width: 100%;
  max-width: 420px;      /* phone baseline */
  /* Use svh (small viewport height) so our layout fits WITH Safari's URL bar
     visible. dvh shrinks/grows as the bar appears, causing jumpy content.
     On keyboard-open, --jny-kbd-h is set by wireKeyboardLayout() so the
     shell shrinks enough to keep the compose bar above the keyboard. */
  height: calc(100svh - var(--jny-kbd-h, 0px));
  max-height: calc(100svh - var(--jny-kbd-h, 0px));
  background: var(--surface);
  /* safe-area-dedup 2026-06-30: status-strip owns top inset, .nav/.comm-bar owns bottom inset */
  padding-top: 0;
  padding-bottom: 0;
  box-shadow: 0 0 48px rgba(0,0,0,.5);
  overflow-x: hidden;    /* prevent any child from forcing horizontal scroll */
  transition: height 180ms ease-out, max-height 180ms ease-out;
}
/* When the keyboard is open, suppress the status strip animation noise
   and lock the comm panel to max, so typing surface is always visible. */
body.kbd-open .status-strip { position: relative; }

/* Grid-item min-width gotcha: grid children default to min-width:auto,
   which lets content expand tracks and push past the shell. Force 0. */
.shell > * { min-width: 0; }

/* iOS Safari (not standalone) — Safari's URL bar + bottom-controls bar aren't
   captured by safe-area insets. Give ourselves explicit breathing room. */
body.is-ios.mode-browser .shell {
  /* safe-area-dedup 2026-06-30: status-strip + .nav own their own insets;
     leaving a tiny non-zero floor avoids hairline-touch with Safari chrome. */
  padding-top: 0;
  padding-bottom: 0;
}
/* In standalone (installed PWA) on iOS, safe-area-inset-bottom captures the
   home indicator; no fallback needed. */
body.mode-standalone .shell {
  height: 100dvh;
  max-height: 100dvh;
}

/* ─── Theme toggle — cycles dark ☾ / outdoor ◉ / light ☀ ─── */
.theme-toggle {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 28px;
  height: 28px;
  margin-left: 8px;
  padding: 0;
  border: 1px solid var(--border);
  border-radius: 14px;
  background: var(--surface-2);
  color: var(--text);
  font-size: 15px;
  line-height: 1;
  cursor: pointer;
  transition: background 120ms, border-color 120ms, transform 120ms;
  -webkit-tap-highlight-color: transparent;
}
.theme-toggle:hover { border-color: var(--accent); }
.theme-toggle:active { transform: scale(0.94); }
.theme-toggle:focus-visible { outline: none; box-shadow: var(--focus-ring); }
html[data-theme="dark"]    .theme-toggle::after { content: "☾"; }   /* moon */
html[data-theme="outdoor"] .theme-toggle::after { content: "◉"; }   /* high-contrast dot */
html[data-theme="light"]   .theme-toggle::after { content: "☀"; }  /* sun */
/* When no theme explicitly set: show the dark moon (matches root defaults). */
html:not([data-theme])     .theme-toggle::after { content: "☾"; }

/* ─── Force-refresh button — always visible, always works ─── */
/* Persistent escape hatch: if the user thinks the app is stuck on an old
   version (no update pill shown, but something feels wrong), one tap here
   unregisters service workers, deletes all caches, and reloads with a
   cache-bust query. Distinct from the update pill which only shows when
   a NEW SW is waiting. */
.force-refresh {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 28px;
  height: 28px;
  margin-left: 6px;
  padding: 0;
  border: 1px solid var(--border);
  border-radius: 14px;
  background: var(--surface-2);
  color: var(--text);
  font-size: 15px;
  line-height: 1;
  cursor: pointer;
  transition: background 120ms, border-color 120ms, transform 120ms;
  -webkit-tap-highlight-color: transparent;
  flex: 0 0 auto;
}
.force-refresh:hover { border-color: var(--accent); }
.force-refresh:active { transform: rotate(180deg); }
.force-refresh:focus-visible { outline: none; box-shadow: var(--focus-ring); }
.force-refresh.is-working { animation: jny-refresh-spin 0.8s linear infinite; pointer-events: none; }
@keyframes jny-refresh-spin { from { transform: rotate(0deg); } to { transform: rotate(360deg); } }

/* ─── EMER button (placeholder, concept-stage)
   Capability-aware progressive enhancement — see GMX7/GVR/.
   No tech stack wired yet; tap opens an info modal. ─── */
.emer-btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  margin-left: 6px;
  padding: 4px 10px;
  border: none;
  border-radius: 12px;
  background: var(--emer-bg);
  color: #fff;
  font-size: 11px;
  font-weight: 700;
  letter-spacing: 0.05em;
  cursor: pointer;
  box-shadow: 0 0 0 2px rgba(232, 90, 79, 0.25);
  -webkit-tap-highlight-color: transparent;
  flex: 0 0 auto;
}
.emer-btn:hover { filter: brightness(1.08); }
.emer-btn:active { transform: scale(0.96); }
.emer-btn:focus-visible { outline: none; box-shadow: 0 0 0 3px rgba(232, 90, 79, 0.5); }

.emer-modal-backdrop {
  position: fixed; inset: 0;
  background: rgba(0, 0, 0, 0.7);
  display: flex; align-items: center; justify-content: center;
  z-index: 9999;
  padding: 20px;
}
.emer-modal {
  background: var(--surface-2, #1a2030);
  color: var(--text, #e8edf5);
  border: 1px solid var(--border, #2c3550);
  border-radius: 16px;
  max-width: 440px;
  width: 100%;
  max-height: 86vh;
  overflow-y: auto;
  padding: 20px 22px;
  box-shadow: 0 16px 48px rgba(0, 0, 0, 0.5);
}
.emer-modal h2 {
  margin: 0 0 4px;
  color: var(--emer-fg);
  font-size: 18px;
}
.emer-modal .sub {
  color: var(--text-dim, #8a93a8);
  font-size: 12px;
  margin: 0 0 14px;
}
.emer-modal h3 {
  font-size: 13px;
  margin: 16px 0 6px;
  color: var(--accent, #6cd6c0);
}
.emer-modal p, .emer-modal li {
  font-size: 13px;
  line-height: 1.5;
  margin: 6px 0;
}
.emer-modal ol { padding-left: 20px; margin: 6px 0; }
.emer-modal .pill {
  display: inline-block;
  font-size: 11px;
  padding: 3px 8px;
  border-radius: 999px;
  background: rgba(176, 58, 46, 0.10);
  color: #b03a2e;
  border: 1px solid rgba(176, 58, 46, 0.35);
  margin-bottom: 8px;
  font-weight: 600;
}
.emer-modal .close-btn {
  display: block;
  width: 100%;
  margin-top: 16px;
  padding: 12px;
  border: 1px solid var(--border, #2c3550);
  background: var(--surface, #232b3e);
  color: var(--text, #e8edf5);
  border-radius: 10px;
  font-size: 14px;
  font-weight: 600;
  cursor: pointer;
}
.theme-toggle              { color: transparent; }   /* hide the fallback glyph */
.theme-toggle::after       { color: var(--text); }

/* ─── Status strip ─── */
.status-strip {
  display: flex;
  align-items: center;
  justify-content: space-between;
  padding: calc(8px + env(safe-area-inset-top, 0px)) calc(14px + env(safe-area-inset-right, 0px)) 8px calc(14px + env(safe-area-inset-left, 0px));
  background: var(--chrome-bg);
  color: var(--chrome-text);
  font-size: 12px;
  letter-spacing: 0.02em;
  border-bottom: 1px solid var(--chrome-border);
}
/* Children inherit chrome-text token instead of body --text (which is near-black in light mode and would be invisible on the dark chrome). */
.status-strip .identity,
.status-strip .circle,
.status-strip .theme-toggle,
.status-strip .theme-toggle::after,
.status-strip .force-refresh,
.status-strip .force-refresh::before { color: var(--chrome-text); }
.identity { font-weight: 600; opacity: .85; min-width: 0; max-width: 45%; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.circle { opacity: .85; color: var(--chrome-text-dim); min-width: 0; max-width: 40%; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.circle:hover { color: var(--chrome-text); }

/* Circle picker dropdown */
/* Device settings overlay */
.device-settings {
  position: fixed;
  inset: 0;
  background: rgba(0,0,0,.6);
  display: flex;
  align-items: center;
  justify-content: center;
  z-index: 200;
  padding: 20px;
}
.ds-card {
  width: 100%;
  max-width: 420px;
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: 14px;
  padding: 20px;
  color: var(--text);
}
.ds-card h2 { margin: 0 0 6px; font-size: 18px; }
.ds-card h3.ds-section { margin: 20px 0 6px; font-size: 13px; opacity: .7; text-transform: uppercase; letter-spacing: .08em; }
.ds-label { display: block; margin: 12px 0 6px; font-size: 12px; opacity: .7; }
.ds-card input[type="text"] {
  width: 100%;
  padding: 10px 12px;
  background: var(--surface-2);
  border: 1px solid var(--border);
  border-radius: 8px;
  color: var(--text);
  font-size: 14px;
  font-family: inherit;
  outline: none;
}
.ds-card input[type="text"]:focus { border-color: var(--accent); }
.ds-row { display: flex; gap: 8px; margin-top: 10px; }
.ds-row-wrap { flex-wrap: wrap; }
.ds-row-right { justify-content: flex-end; margin-top: 16px; }
.ds-btn {
  padding: 8px 14px;
  background: var(--accent);
  color: #fff;
  border: 0;
  border-radius: 8px;
  cursor: pointer;
  font-size: 13px;
  font-family: inherit;
}
.ds-btn:hover { background: var(--accent); }
.ds-btn-ghost { background: transparent; color: var(--text-dim); border: 1px solid var(--border); }
.ds-btn-ghost:hover { color: #c9cce8; background: rgba(80,98,199,.1); }

/* Identity menu (click on name in status strip) */
.identity-menu {
  position: fixed;
  top: max(44px, calc(env(safe-area-inset-top) + 8px));
  left: 10px;
  right: auto;
  background: var(--chrome-bg);
  border: 1px solid var(--border);
  border-radius: 12px;
  box-shadow: 0 12px 40px rgba(0,0,0,.5);
  z-index: 100;
  min-width: 260px;
  max-width: min(90vw, 360px);
  padding: 8px;
  color: var(--text);
  font-size: 14px;
  /* drawerscroll 2026-06-11: long account lists were clipped (Sign out unreachable).
     Bound height to the viewport below the top offset and scroll the overflow. */
  max-height: calc(100vh - max(44px, calc(env(safe-area-inset-top) + 8px)) - 12px);
  max-height: calc(100dvh - max(44px, calc(env(safe-area-inset-top) + 8px)) - 12px);
  overflow-y: auto;
  -webkit-overflow-scrolling: touch;
  overscroll-behavior: contain;
}
.im-header {
  padding: 10px 10px 12px;
  border-bottom: 1px solid var(--border);
  margin-bottom: 6px;
}
.im-name { font-weight: 600; font-size: 15px; }
.im-device { font-size: 12px; opacity: .65; margin-top: 2px; }
.im-section-label {
  font-size: 10px;
  text-transform: uppercase;
  letter-spacing: 0.1em;
  opacity: .45;
  padding: 8px 10px 4px;
}
.im-item {
  display: flex;
  align-items: center;
  gap: 10px;
  width: 100%;
  padding: 10px 12px;
  background: transparent;
  border: 0;
  border-radius: 6px;
  color: var(--text);
  cursor: pointer;
  font-size: 14px;
  text-align: left;
  font-family: inherit;
}
.im-item:hover { background: var(--surface-2); }
.im-item span:first-child { font-size: 16px; line-height: 1; }
.im-signout { color: #d87b7b; }
.im-signout:hover { background: rgba(216,123,123,0.1); }
.im-muted { opacity: .55; font-size: 11px; }
.im-device-list { padding: 4px 0; }
.im-device-row {
  padding: 6px 12px;
  border-radius: 6px;
  font-size: 13px;
}
.im-device-row.is-this { background: rgba(80,98,199,0.15); }
.im-device-name { display: flex; align-items: center; gap: 7px; }
/* D005 P1 — presence/recency dot + last-seen meta */
.im-device-dot {
  width: 8px; height: 8px; border-radius: 50%;
  background: rgba(140,150,170,0.45);   /* dim = idle/offline */
  flex: 0 0 auto;
}
.im-device-dot.is-active { background: #4caf72; }  /* green = recently active */
.im-device-meta { padding: 1px 0 0 15px; }

.circle-menu {
  position: fixed;
  top: 44px;
  left: 50%;
  transform: translateX(-50%);
  background: var(--chrome-bg);
  border: 1px solid var(--border);
  border-radius: 10px;
  box-shadow: 0 12px 40px rgba(0,0,0,.5);
  z-index: 99;
  min-width: 220px;
  padding: 6px;
  /* drawerscroll 2026-06-11: same clip fix as .identity-menu for long circle lists. */
  max-height: calc(100vh - 44px - 12px);
  max-height: calc(100dvh - 44px - 12px);
  overflow-y: auto;
  -webkit-overflow-scrolling: touch;
  overscroll-behavior: contain;
}
.circle-opt {
  display: flex;
  justify-content: space-between;
  align-items: center;
  width: 100%;
  padding: 10px 12px;
  background: transparent;
  border: 0;
  border-radius: 6px;
  color: var(--text);
  cursor: pointer;
  font-size: 14px;
  text-align: left;
}
.circle-opt:hover { background: var(--surface-2); }
.circle-opt.is-active { background: rgba(80,98,199,.2); color: #ffffff; }
.co-room { font-size: 12px; opacity: .6; }
.conn {
  padding: 2px 8px;
  border-radius: 999px;
  font-size: 11px;
  background: #2a2a2a;
  color: #aaa;
}
.conn[data-state="online"]  { background: #1d3a2a; color: #7bd89a; }
.conn[data-state="offline"] { background: #3a1d1d; color: #d87b7b; }
.conn[data-state="pending"] { background: #2a2a3a; color: var(--text-dim); }

/* ─── Stage (L2/L3 swap area) ─── */
.stage {
  overflow-y: auto;
  padding: 16px;
  background: var(--surface-2);
}
/* Pass-2 sweep 2026-07-02 (iPhone-footer fix): when the L0 springboard
   (#homeNext) is mounted, the stage hosts a cream page. The stage's own gray
   --surface-2 bg + 16px padding was leaking around the bottom + sides of the
   cream card as a "dead strip" — visible on T1 iPhone/Android portrait as an
   unused footer area below the last dock row. Two changes: (a) remove padding
   when hosting homeNext (the cream page + dock own their own padding), and
   (b) match stage bg to the cream page bg so any residual gap reads as intended
   page background, not orphan gray. Non-homeNext stages unaffected. */
.stage:has(#homeNext) {
  padding: 0;
  background: #fbf9e4;  /* matches --hn-page (l3-home-next.js) */
}
/* Fallback for :has()-less browsers — apply the same via body-class the
   springboard sets when it mounts (l3-home-next.js:mount adds body.has-home-next). */
body.has-home-next .stage {
  padding: 0;
  background: #fbf9e4;
}
.stage .loading { opacity: .6; text-align: center; padding: 48px 0; }
.stage h1 { margin: 0 0 12px; font-size: 20px; }
.stage h2 { margin: 0 0 8px; font-size: 16px; }
.stage p  { margin: 0 0 12px; opacity: .8; line-height: 1.4; }

.stage .card {
  background: var(--surface-2);
  border: 1px solid var(--border);
  border-radius: 12px;
  padding: 14px;
  margin-bottom: 12px;
  display: flex;
  gap: 12px;
  align-items: center;
}
.stage .card-art {
  width: 60px; height: 60px;
  border-radius: 10px;
  background: linear-gradient(135deg, var(--accent-strong), var(--accent));
  display: flex; align-items: center; justify-content: center;
  font-size: 28px;
  flex-shrink: 0;
}
.stage .card-meta { flex: 1; min-width: 0; }
.stage .card-meta h2 { margin: 0 0 2px; font-size: 15px; }
.stage .card-meta p  { margin: 0; font-size: 13px; opacity: .65; }
.stage button.card.game-card {
  width: 100%;
  text-align: left;
  cursor: pointer;
  font: inherit;
  color: inherit;
}
.stage button.card.game-card:hover {
  background: var(--surface-2);
  border-color: var(--accent-strong);
}

/* Tic-Tac-Toe */
.game-back {
  background: transparent;
  color: var(--text-dim);
  border: 0;
  font-size: 13px;
  padding: 4px 0 10px;
  cursor: pointer;
}
.game-back:hover { color: #c9cce8; }
.game-title { font-size: 18px; margin: 0 0 6px; }
.game-status { font-size: 13px; opacity: .7; margin-bottom: 12px; }
.ttt-board {
  display: grid;
  grid-template-columns: repeat(3, 1fr);
  gap: 6px;
  max-width: 280px;
}
.ttt-cell {
  aspect-ratio: 1 / 1;
  background: var(--surface-2);
  border: 1px solid var(--border);
  border-radius: 10px;
  color: var(--text);
  font-size: 40px;
  font-weight: 700;
  cursor: pointer;
  transition: background 120ms ease, border-color 120ms ease;
}
.ttt-cell:hover:not(:disabled) { background: var(--surface-2); }
.ttt-cell:disabled { cursor: default; }
.ttt-cell.is-win { background: #3a4e2a; border-color: #7bd89a; color: #7bd89a; }
.game-reset {
  margin-top: 14px;
  padding: 8px 16px;
  background: var(--accent);
  color: #fff;
  border: 0;
  border-radius: 8px;
  cursor: pointer;
  font-size: 14px;
}
.game-reset:hover { background: var(--accent); }

/* ─── Flip bar ─── */
.flip-bar {
  display: flex;
  background: var(--chrome-bg);
  border-top: 1px solid var(--chrome-border);
  border-bottom: 1px solid var(--chrome-border);
}
.flip-btn {
  flex: 1;
  padding: 10px 8px;
  background: transparent;
  border: 0;
  color: var(--text-dim);
  font-size: 13px;
  display: flex; flex-direction: column; align-items: center; gap: 2px;
  cursor: pointer;
  transition: color 120ms ease, background 120ms ease;
}
.flip-btn .flip-icon { font-size: 20px; line-height: 1; }
.flip-btn:hover { color: #c9cce8; }
.flip-btn.is-active {
  color: #ffffff;
  background: linear-gradient(180deg, rgba(80,98,199,.15), rgba(80,98,199,.0));
  box-shadow: inset 0 2px 0 var(--accent);
}

/* ─── Comm bar (bottom, fixed) ─── */
.comm-bar {
  height: 38vh;
  min-height: 260px;
  max-height: 50vh;
  border-top: 1px solid var(--chrome-border);
  background: #0c0f1e;
}
.comm-bar iframe {
  width: 100%;
  height: 100%;
  border: 0;
  display: block;
}

/* ─── In-app Jitsi call overlay (Phase C tier-2) ─── */
.jny-call-overlay {
  position: fixed;
  inset: 0;
  z-index: 11000;                /* above login overlay (9999) and popovers (10000) */
  background: #0a0d1a;
  color: #eaecff;
  display: flex;
  flex-direction: column;
}
.jny-call-overlay[hidden] { display: none !important; }

/* Global hidden-attribute override — beats any explicit display: rule.
   Without this, elements like .emer-modal-backdrop with display:flex
   stay visible even when their `hidden` attribute is set. */
[hidden] { display: none !important; }
.jny-call-header {
  display: flex;
  align-items: center;
  padding: 10px 14px;
  background: var(--surface-2);
  border-bottom: 1px solid var(--border);
  padding-top: max(10px, env(safe-area-inset-top));
  flex: 0 0 auto;
}
.jny-call-title {
  flex: 1;
  font-weight: 600;
  font-size: 15px;
  color: #c9cce8;
}
.jny-call-hangup {
  padding: 8px 14px;
  border: 0;
  border-radius: 8px;
  background: #c53030;
  color: #fff;
  font-weight: 600;
  cursor: pointer;
  font-size: 13px;
}
.jny-call-hangup:hover { filter: brightness(1.1); }
.jny-call-timer {
  font-variant-numeric: tabular-nums;
  font-size: 13px;
  color: var(--text-dim, #a7a9bd);
  margin-right: 10px;
  min-width: 38px;
  text-align: right;
}
.jny-call-min {
  width: 32px;
  height: 32px;
  border: 1px solid var(--border, rgba(255,255,255,0.08));
  border-radius: 8px;
  background: transparent;
  color: var(--text, var(--text));
  font-size: 16px;
  cursor: pointer;
  margin-right: 8px;
  display: flex;
  align-items: center;
  justify-content: center;
}
.jny-call-min:hover { background: rgba(255,255,255,0.06); }
.jny-call-frame {
  flex: 1 1 auto;
  min-height: 0;
  background: #000;
  position: relative;
}

/* Minimized banner mode — fixed strip at top, rest of app fully usable.
   Iframe stays attached (visibility: hidden) so audio keeps flowing. */
.jny-call-overlay.is-mini {
  inset: auto 0 auto 0;          /* top-anchored only */
  top: 0;
  height: auto;
  z-index: 11000;
  background: #0a0d1a;
  border-bottom: 1px solid var(--border);
  box-shadow: 0 2px 8px rgba(0,0,0,0.35);
}
.jny-call-overlay.is-mini .jny-call-header {
  padding-top: max(8px, env(safe-area-inset-top));
  padding-bottom: 8px;
}
.jny-call-overlay.is-mini .jny-call-title {
  cursor: pointer;                /* tap title to expand */
}
.jny-call-overlay.is-mini .jny-call-title::before {
  content: "🔊 ";
}
.jny-call-overlay.is-mini .jny-call-frame {
  flex: 0 0 auto;
  height: 1px;                    /* effectively invisible */
  visibility: hidden;             /* keep iframe alive — audio stays connected */
  overflow: hidden;
}
.jny-call-overlay.is-mini .jny-call-status,
.jny-call-overlay.is-mini .jny-call-vm-cta,
.jny-call-overlay.is-mini .jny-call-vm-mount {
  display: none;                  /* banner mode is bare-bones; expand for VM */
}

/* When call is mini, push the rest of the app down so the banner doesn't
   cover content. Approximate header height. */
body.has-mini-call {
  padding-top: 48px;
}
/* The iframe Jitsi injects fills the frame box */
.jny-call-frame iframe {
  width: 100% !important;
  height: 100% !important;
  border: 0 !important;
}
.jny-call-status {
  padding: 10px 14px;
  background: #3a1d1d;
  color: #ffb4b4;
  font-size: 13px;
  flex: 0 0 auto;
}

/* ─── Login overlay ─── */
.jny-login {
  position: fixed;
  inset: 0;
  background: var(--page); /* #fbf9e4 — H2 relight (was #070812); forms default light */
  display: flex;
  align-items: center;
  justify-content: center;
  z-index: 9999;
  padding: 24px;
}
.jny-login-card {
  width: 100%;
  max-width: 360px;
  background: var(--card); /* #fff — H2 relight (was navy --chrome-bg w/ ink text = illegible) */
  border: 1px solid var(--border);
  border-radius: 16px;
  padding: 28px 24px;
  color: var(--text);
  box-shadow: 0 16px 40px -18px rgba(20,30,55,.35);
}
.jny-login-card h1 {
  margin: 0 0 2px;
  font-size: 32px;
  letter-spacing: 0.04em;
  display: flex;
  align-items: center;
  gap: 6px;
}
.j-wordmark {
  color: var(--accent); /* H2 relight — solid navy (gradient's light end failed on white card) */
  font-weight: 700;
}
.j-mark {
  font-size: 24px;
  color: var(--accent); /* H2 relight — was #ffd166 gold, illegible on light card */
  margin-left: 2px;
}
.jny-login-card .j-tag {
  margin: 0 0 16px;
  font-size: 11.5px;
  color: var(--muted); /* H2 relight — was opacity:.55 (AA fail on light) */
  letter-spacing: 0.08em;
  text-transform: uppercase;
}
.jny-login-card p { margin: 0 0 18px; color: var(--muted); font-size: 14px; }
.jny-login-card input {
  display: block;
  width: 100%;
  padding: 12px 14px;
  margin-bottom: 10px;
  background: var(--surface-2);
  border: 1px solid var(--border);
  border-radius: 10px;
  color: var(--text);
  font-size: 15px;
  font-family: inherit;
  outline: none;
}
.jny-login-card input:focus { border-color: var(--accent); }
.jny-login-card button {
  display: block;
  width: 100%;
  padding: 12px;
  margin-top: 6px;
  background: linear-gradient(135deg, var(--accent), var(--accent-strong));
  color: #ffffff;
  border: 0;
  border-radius: 10px;
  font-size: 15px;
  font-weight: 600;
  cursor: pointer;
}
.jny-login-card button:hover { filter: brightness(1.1); }
/* Primary OIDC button (anchor tag) — matches the gradient button style */
.jny-login-card .kc-btn {
  display: block;
  width: 100%;
  padding: 14px 12px;
  margin-top: 6px;
  background: linear-gradient(135deg, var(--accent), var(--accent-strong));
  color: #ffffff !important;
  border: 0;
  border-radius: 10px;
  font-size: 16px;
  font-weight: 600;
  text-align: center;
  text-decoration: none;
  cursor: pointer;
  box-sizing: border-box;
  transition: filter 120ms, transform 120ms;
  -webkit-tap-highlight-color: transparent;
}
.jny-login-card .kc-btn:hover { filter: brightness(1.1); }
.jny-login-card .kc-btn:active { transform: scale(0.98); }
.jny-login-card .kc-btn:focus-visible { outline: none; box-shadow: var(--focus-ring); }
.pw-wrap {
  position: relative;
  margin-bottom: 10px;
}
.pw-wrap input {
  padding-right: 44px !important;
  margin-bottom: 0 !important;
}
.pw-toggle {
  position: absolute;
  right: 4px;
  top: 50%;
  transform: translateY(-50%);
  background: transparent;
  border: 0;
  color: var(--text-dim);
  font-size: 18px;
  padding: 6px 8px;
  margin: 0 !important;
  width: auto !important;
  cursor: pointer;
}

.jny-login-err {
  margin: 10px 0 0;
  padding: 8px 10px;
  background: #3a1d1d;
  color: #ffb4b4;
  border-radius: 8px;
  font-size: 13px;
}
.jny-login-msg {
  margin: 10px 0 0;
  padding: 8px 10px;
  background: #1d3a2a;
  color: #7bd89a;
  border-radius: 8px;
  font-size: 13px;
}
.magic-btn {
  display: block;
  width: 100%;
  padding: 10px !important;
  margin-top: 8px !important;
  background: transparent !important;
  color: var(--text-dim) !important;
  border: 1px solid var(--border) !important;
  font-size: 13px !important;
}
.magic-btn:hover { color: #c9cce8 !important; border-color: var(--accent-strong) !important; }

.jny-login-footer {
  margin: 16px 0 0;
  text-align: center;
  font-size: 13px;
  color: var(--text-dim);
}
.jny-login-footer a {
  color: #c9cce8;
  text-decoration: none;
  border-bottom: 1px dotted var(--accent-strong);
  padding-bottom: 1px;
}
.jny-login-footer a:hover { color: #ffffff; border-bottom-color: var(--accent); }

/* Join-free CTA — standalone, prominent, a couple lines below sign-in.
   Free membership is the biggest top-of-funnel CTA; surfaced (not buried in prose).
   Brand floor: Navy Luxe, warm/giver, multigenerational-legible. MARK owns final copy/treatment/A-B. */
.jny-join-cta {
  margin: 20px 0 0;
  padding: 18px 0 4px;
  border-top: 1px solid var(--border);
  text-align: center;
}
.jny-join-line {
  margin: 0 0 12px !important;
  font-size: 16px !important;
  font-weight: 600;
  color: var(--text) !important;
  opacity: 1 !important;
  letter-spacing: 0.01em;
}
.jny-login-card a.jny-join-btn {
  display: block;
  width: 100%;
  padding: 14px 12px;
  margin: 0 auto;
  max-width: 100%;
  background: transparent;
  color: #ffffff !important;
  border: 1.5px solid var(--accent-strong);
  border-radius: 10px;
  font-size: 16px;
  font-weight: 700;
  text-align: center;
  text-decoration: none;
  cursor: pointer;
  box-sizing: border-box;
  transition: background 120ms, filter 120ms, transform 120ms;
  -webkit-tap-highlight-color: transparent;
}
.jny-login-card a.jny-join-btn:hover {
  background: linear-gradient(135deg, var(--accent), var(--accent-strong));
  filter: brightness(1.05);
}
.jny-login-card a.jny-join-btn:active { transform: scale(0.98); }
.jny-login-card a.jny-join-btn:focus-visible { outline: none; box-shadow: var(--focus-ring); }
.jny-join-sub {
  margin: 12px 0 0 !important;
  font-size: 13px !important;
  line-height: 1.5;
  color: var(--text-dim) !important;
  opacity: 1 !important;
}

/* iOS "Add to Home Screen" hint */
.ios-install-hint {
  position: fixed;
  /* Anchored to the TOP (under the status strip) so it can never cover the
     bottom comm bar / nav on phone. Was bottom-anchored and overlapped the
     comm bar until dismissed (QA 2026-05-24). */
  top: max(12px, env(safe-area-inset-top));
  left: 20px;
  right: 20px;
  background: var(--chrome-bg);
  color: var(--text);
  border: 1px solid var(--border);
  border-radius: 12px;
  padding: 14px 16px;
  font-size: 13px;
  z-index: 100;
  box-shadow: 0 8px 24px rgba(0,0,0,.5);
}
.ios-install-hint button {
  /* a11y 2026-05-25: white-on-lavender (--accent #8c8ff0) was 2.87:1 (WCAG 1.4.3).
     Pin to deep navy bg + white text — theme-independent, ~13:1 in every theme. */
  background: #122c4f;
  color: #fff;
  border: 0;
  border-radius: 6px;
  padding: 6px 12px;
  font-size: 12px;
  margin-left: 8px;
  cursor: pointer;
}
body.mode-standalone .ios-install-hint { display: none; }

/* Pass-1 sweep 2026-07-02: on landscape phones (Tier 2) the shell is only
   ~340-390px tall — the install hint eats half the usable height. On tablets
   (Tiers 3, 4) the shell is letterboxed or wide, and iOS "Add to Home Screen"
   is a portrait-phone UX pattern anyway. Restrict the hint to portrait phones. */
@media (orientation: landscape) { .ios-install-hint { display: none !important; } }
@media (min-width: 768px)       { .ios-install-hint { display: none !important; } }

/* Tablet — keep phone width, just a touch more comfort */
@media (min-width: 768px) {
  .shell { max-width: 480px; }
  .stage h1 { font-size: 22px; }
}

/* Landscape layout: two-column (stage | comm) for any landscape-oriented
   device wide enough to make sense — covers phone landscape, tablet
   landscape (iPad, Android tablets), and small desktop windows.
   Portrait on any size stays single-column phone-width. */
/* Landscape / wide: single CENTERED column. The old two-column stage|comm grid
   was retired with the comm-bar consolidation (comm is now the floating launcher),
   which left an empty reserved column = white dead space on tablet/desktop.
   Removed 2026-06-16. A true multi-column tablet layout is a separate follow-up. */
@media (orientation: landscape) and (min-width: 720px) {
  .shell { max-width: 600px; }
}

/* ─── Wide-screen presentation (tablet + desktop): JNY stays a lean, phone-shaped
   column — per phone-first canon (JNY = "Journey", on-the-go) — but is PRESENTED as a
   deliberate, branded app card floating on a soft on-brand backdrop, not a phone
   stranded on an empty field. Phone (<768px) is untouched: edge-to-edge, no frame.
   Real multi-panel/2-col layout belongs to DaisyOne/PRO, not here. (operator 2026-06-16) */
@media (min-width: 768px) {
  html, body { min-height: 100svh; }
  body {
    align-items: center;            /* float the card, vertically centered */
    background:
      radial-gradient(140% 100% at 50% -10%, var(--surface-2) 0%, var(--bg) 55%);
    background-attachment: fixed;
  }
  .shell {
    max-width: 600px;
    height:     calc(min(100svh - 48px, 1024px) - var(--jny-kbd-h, 0px));
    max-height: calc(min(100svh - 48px, 1024px) - var(--jny-kbd-h, 0px));
    border: 1px solid var(--border);
    border-radius: 22px;
    overflow: hidden;               /* clip header/stage to the rounded corners */
    box-shadow:
      0 1px 2px rgba(0,0,0,.18),
      0 14px 30px rgba(0,0,0,.26),
      0 44px 88px rgba(0,0,0,.30);
  }
}

/* Phone landscape (short, not wide enough for side-by-side comm):
   single-column, comm stays at the bottom, shell fills viewport. */
@media (orientation: landscape) and (max-width: 719px) and (max-height: 520px) {
  .shell {
    max-width: none;
    width: 100%;
    max-height: 100dvh;
    box-shadow: none;
  }
}

/* ─────────────────────────────────────────────────────────────────────────
   TIER 4 — Tablet Horizontal / PC baseline (≥1024w, landscape).
   Doctrine (operator 2026-07-02, project_jny_ecosystem_form_factor_model):
   Tier 1 (phone-V) = primary, unchanged. Tier 4 = SECONDARY primary and
   the PC baseline. Old rules clamped .shell to 600px even on a 1280w
   tablet-landscape → the L0 4×6 grid + dock got chopped in landscape.
   Fix: widen shell to fill, expand L0 grid + dock to 8 columns so tiles
   breathe horizontally instead of overflowing vertically. Visual language
   (Navy Luxe, IBM Plex + Source Serif 4, iPhone L0 model at Tier 1) is
   preserved — this is layout tiers only.
   Tiers 2 (phone-land) + 3 (tablet-V) = follow-up pass.
   ───────────────────────────────────────────────────────────────────────── */
/* ─────────────────────────────────────────────────────────────────────────
   TIER 2 — Phone Landscape (Pass 2 · 2026-07-02).
   Doctrine (operator 2026-07-02): match iOS+Android home-screen landscape
   convention. On iPhone landscape home / Android landscape home the wallpaper
   fills, the dock stays at the bottom, and the tile grid gains columns rather
   than clamping to a phone-portrait card floating in the middle. Old rule
   `orientation:landscape and min-width:720` clamped shell to 600px — 244px
   of dead cream on 844×390. Kill the letterbox. L0 header + banner stay.
   Trigger: landscape + shortest side ≤ 480 (phone-class). Excludes tablets
   (which get Tier 3/T4 rules).
   ───────────────────────────────────────────────────────────────────────── */
@media (orientation: landscape) and (max-height: 520px) {
  body {
    align-items: stretch;
    background: var(--bg);
  }
  .shell {
    max-width: none;
    width: 100%;
    height: 100dvh;
    max-height: 100dvh;
    margin: 0;
    border: 0;
    border-radius: 0;
    box-shadow: none;
  }
  /* Phone-landscape L0: 6 columns matches iOS/Android landscape home density
     (portrait=4, landscape=6). Grid + dock both widen. */
  #homeNext .grid { grid-template-columns: repeat(6, 1fr) !important; }
  #homeNext .dock { grid-template-columns: repeat(6, 1fr) !important; }
  #homeNext .page { padding: 6px 20px 10px; }
  #homeNext .dock { padding: 6px 20px 10px; }
}

/* ─────────────────────────────────────────────────────────────────────────
   TIER 3 — Tablet Portrait (Pass 2 · 2026-07-02).
   Doctrine (operator 2026-07-02): match iPadOS + Android-tablet portrait
   home-screen convention. iPad portrait home = 6 columns + dock at bottom.
   Android tablet portrait = 5-6 columns. Old rule clamped shell to 600px on
   768×1024 → 168px cream letterbox each side + 4-col of chopped phone width.
   Kill the letterbox. L0 header + banner stay.
   Trigger: portrait + shortest side ≥ 720 (tablet-class).
   ───────────────────────────────────────────────────────────────────────── */
@media (orientation: portrait) and (min-width: 720px) {
  body {
    align-items: stretch;
    background: var(--bg);
  }
  .shell {
    max-width: none;
    width: 100%;
    height: 100dvh;
    max-height: 100dvh;
    margin: 0;
    border: 0;
    border-radius: 0;
    box-shadow: none;
  }
  /* Tablet-portrait L0: 6 columns (iPad convention). Grid + dock widen. */
  #homeNext .grid { grid-template-columns: repeat(6, 1fr) !important; }
  #homeNext .dock { grid-template-columns: repeat(6, 1fr) !important; }
  #homeNext .page { padding: 12px 28px 16px; }
  #homeNext .dock { padding: 10px 28px 14px; }
  footer#comm gmad-comm { max-width: 640px; }
}

@media (min-width: 1024px) and (orientation: landscape) {
  /* Drop the letterbox: fill the viewport with a wide shell, no card frame. */
  body {
    align-items: stretch;
    background: var(--bg);
  }
  .shell {
    max-width: min(1400px, 100vw);
    width: 100%;
    height: 100dvh;
    max-height: 100dvh;
    margin: 0 auto;
    border: 0;
    border-radius: 0;
    box-shadow: none;
  }
  /* L0 springboard: iOS/Android platform pattern — fixed tile size, auto-fit
     column count, centered row. Prevents wide-tile-small-icon at desktop where
     previous fixed 8-col forced 155px cells with 104px icon caps. Supersedes
     the 7/2 fixed 8-col decision per operator canon 2026-07-06 (PC renders the
     tablet shape; follow platform tile conventions, no fixed count). */
  #homeNext .grid { grid-template-columns: repeat(auto-fit, minmax(104px, 120px)) !important; justify-content: center; }
  #homeNext .dock { grid-template-columns: repeat(auto-fit, minmax(104px, 120px)) !important; justify-content: center; }
  /* Page interior gains breathing room; keep tiles a comfortable icon size
     rather than stretched. */
  #homeNext .page { padding: 12px 32px 18px; }
  #homeNext .dock { padding: 10px 32px 16px; }
  /* Comm hub host: allow wider than 480px on tablet-landscape. */
  footer#comm gmad-comm { max-width: 900px; }
}

/* Utility: a surface can opt OUT of phone-width lock and fill the viewport
   (e.g., a game or video that wants landscape). Add class `.landscape-free`
   to <body> while that surface is active, remove when done. */
body.landscape-free .shell {
  max-width: none;
  width: 100%;
  box-shadow: none;
}

/* ─── Partial-render modes (via ?part= query param) ─── */
/* Shell applies a data-part attribute based on URL; CSS below shows only that region. */

/* iOS-tab redesign 2026-04-29: flip-bar (Play / Journey) is hidden globally.
 * Per project_jny1_is_production_jny2_stays_spike.md + Martin 2026-04-29:
 * Play and Journey are Minis on the JNY circle's WHAT'S HERE grid, not modes
 * forced onto every screen. The bottom is the iOS-style 4-tab comm rail
 * (Call / Video / Chat / All DMs) — see comm.css .comm-icons-ios. */
.flip-bar { display: none !important; }

body[data-part="comm"] .status-strip,
body[data-part="comm"] .stage,
body[data-part="comm"] .flip-bar { display: none; }
body[data-part="comm"] .shell { grid-template-rows: 1fr; max-width: none; box-shadow: none; }
body[data-part="comm"] .comm-bar { height: 100dvh; max-height: none; min-height: 0; }

body[data-part="stage"] .status-strip,
body[data-part="stage"] .flip-bar,
body[data-part="stage"] .comm-bar { display: none; }
body[data-part="stage"] .shell { grid-template-rows: 1fr; max-width: none; box-shadow: none; }

body[data-part="flip"] .status-strip,
body[data-part="flip"] .comm-bar { display: none; }
body[data-part="flip"] .shell { grid-template-rows: 1fr auto; max-width: none; box-shadow: none; }

/* Update-available pill — lives inside the status strip next to the conn
   chip, never escapes the phone-width shell. Tap to open a small popover
   anchored to the pill with Update / Later. */
.jny-update-pill {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 22px;
  height: 22px;
  margin: 0 6px;
  padding: 0;
  border: 1px solid var(--accent);
  border-radius: 11px;
  background: var(--accent);
  color: #fff;
  font-size: 13px;
  line-height: 1;
  cursor: pointer;
  animation: jny-update-pulse 1.8s ease-in-out infinite;
  -webkit-tap-highlight-color: transparent;
  flex: 0 0 auto;
}
.jny-update-pill:focus-visible { outline: none; box-shadow: var(--focus-ring); }
html[data-theme="light"] .jny-update-pill { color: #fff; }
@keyframes jny-update-pulse {
  0%, 100% { transform: scale(1); box-shadow: 0 0 0 0 rgba(140, 143, 240, 0); }
  50%      { transform: scale(1.08); box-shadow: 0 0 0 4px rgba(140, 143, 240, 0.22); }
}
@media (prefers-reduced-motion: reduce) {
  .jny-update-pill { animation: none; }
}

.jny-update-pop {
  position: fixed;
  z-index: 10000;
  width: 220px;
  padding: 12px 14px;
  background: var(--surface-2);
  color: var(--text);
  border: 1px solid var(--border);
  border-radius: 12px;
  box-shadow: 0 10px 28px rgba(0,0,0,.45);
  font-size: 14px;
}
.jny-update-pop-msg   { margin-bottom: 10px; line-height: 1.35; }
.jny-update-pop-actions { display: flex; justify-content: flex-end; gap: 8px; }
.jny-update-pop button {
  border: none;
  border-radius: 999px;
  padding: 6px 14px;
  font-size: 13px;
  font-weight: 600;
  cursor: pointer;
}
.jny-update-later { background: transparent; color: var(--text-dim); }
.jny-update-now   { background: var(--accent); color: #fff; }
html[data-theme="light"] .jny-update-now { background: var(--accent-strong); }

/* Global incognito/private-window banner — one warning across all L3s. */
.jny-incog-banner {
  position: fixed;
  top: 0;
  left: 0;
  right: 0;
  z-index: 9999;
  display: flex;
  align-items: center;
  gap: 10px;
  padding: max(10px, env(safe-area-inset-top)) 14px 10px;
  background: #3a2a12;
  color: #ffe8c7;
  border-bottom: 1px solid #6a4a1e;
  font-size: 13px;
  line-height: 1.35;
}
.jny-incog-banner button {
  margin-left: auto;
  background: transparent;
  border: none;
  color: #ffe8c7;
  font-size: 18px;
  cursor: pointer;
  padding: 0 4px;
}

/* ============================================================================
   SETTINGS overlay + slimmed top-bar chrome   (2026-05-25)
   - #setQuick = page-set quick selector (replaced the off-page circle picker)
   - .theme-toggle is hidden unless the user pins it (Settings → Appearance)
   - .comm-bar is hidden (interim) until the Comm hub ships — nothing dials direct
   - .jny-settings = the Settings "card" overlay (app.js openSettings())
   ============================================================================ */

/* Top-bar PAGE-SET quick selector. Keeps the browser's native dropdown arrow. */
.status-strip .set-quick {
  margin: 0 6px;
  padding: 4px 8px;
  border: 1px solid rgba(255,255,255,0.42);
  border-radius: 999px;
  background: rgba(255,255,255,0.14);
  color: #f4f1ea;
  font: 600 12.5px/1.2 inherit;
  max-width: 44%;
  cursor: pointer;
}
.status-strip .set-quick option { color: #1a1f3a; background: #ffffff; }
.status-strip .set-quick:focus-visible { outline: none; box-shadow: var(--focus-ring); }

/* Theme toggle: the [hidden] attribute must beat `.theme-toggle { display:inline-flex }`. */
.theme-toggle[hidden] { display: none !important; }

/* INTERIM (2026-05-25): in-app comm is OFF until the Comm hub ships. Hide the bottom
   comm bar everywhere so nothing dials direct. Delete this rule to restore comm. */
.comm-bar { display: none !important; }

/* ---- Settings card overlay (phone-bounded inside .shell) ----
   LIGHT Navy-Luxe treatment (ink on pearl), matching the springboard home, so the
   multi-word body copy is NOT reversed text. Ogilvy rule: >1-2 words of light-on-dark
   reading text fatigues the eyes and gets skipped unless the user opted into dark mode.
   Reversed is limited here to ≤1-2-word controls (active segmented button, filled CTA). */
/* ══════════════════════════════════════════════════════════════════════════
   DAY-1 WELCOME POPUP (① — 2026-07-19)
   ART spec: technical/_canon/art/specs/DAY1_SPINE_VISUAL_2026-07-19.md §1①/§3
   Light surface. The ONLY reverse type is the filled CTA label (FOUNDATIONAL #1).
   Vertical budget @375x812 (ART): 560px used of 734 usable → never scrolls, and
   the ONE primary CTA is visible without scrolling.
   ══════════════════════════════════════════════════════════════════════════ */
.jny-welcome {
  position: absolute; inset: 0; z-index: 240;   /* above .jny-settings (200) */
  display: flex; align-items: center; justify-content: center;
  padding: 24px;
  background: rgba(18, 44, 79, .45);            /* scrim — never place text on it */
  font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Inter, system-ui, sans-serif;
  animation: jny-set-in 160ms ease;
}
@media (prefers-reduced-motion: reduce) { .jny-welcome { animation: none; } }
.jny-welcome .wel-card {
  position: relative;
  width: 100%; max-width: 327px;
  max-height: 100%; overflow-y: auto;
  background: #ffffff; color: #14171f;          /* 17.92:1 */
  border-radius: 16px; padding: 20px;
  box-shadow: 0 18px 48px -12px rgba(18, 44, 79, .5);
  display: flex; flex-direction: column; align-items: center; gap: 12px;
}
.jny-welcome .wel-close {
  position: absolute; top: 8px; right: 8px;
  width: 48px; height: 48px;                    /* senior floor, not 44 */
  background: none; border: 0; font-size: 26px; line-height: 1;
  color: #5b6473; cursor: pointer;              /* 5.97:1 */
}
.jny-welcome .wel-portrait {
  width: 200px; height: 200px; border-radius: 16px;
  object-fit: cover; background: #f2ead7; flex: none;
}
.jny-welcome .wel-portrait--placeholder { background: #f2ead7; }
/* The <audio> element is driven programmatically and never shown — the UA's own
   controls are ~20-30px on mobile, well under the 48px senior floor. */
.jny-welcome audio { display: none; }
.jny-welcome .wel-controls {
  display: flex; align-items: center; gap: 12px; width: 100%;
}
.jny-welcome .wel-play {
  display: inline-flex; align-items: center; justify-content: center; gap: 8px;
  flex: 1; min-height: 48px; padding: 0 16px;
  background: #ffffff; color: #122c4f;          /* 14.01:1 */
  border: 2px solid #122c4f; border-radius: 10px;
  font-size: 18px; font-weight: 600; cursor: pointer;
}
.jny-welcome .wel-play__glyph { font-size: 15px; line-height: 1; }
.jny-welcome .wel-play:active { background: #f2ead7; }
.jny-welcome .wel-caps {
  width: 100%; min-height: 56px;                /* 2 lines reserved: no CTA reflow */
  background: #f7f4e6; border: 1px solid #c9c3ac; /* GAP-110: 3.06:1, not the hairline */
  border-radius: 10px; padding: 10px 12px;
}
.jny-welcome .wel-capline {
  margin: 0 0 6px; font-size: 18px; line-height: 1.45; font-weight: 500;
  color: #14171f;                               /* 16.24:1 on the band */
}
.jny-welcome .wel-capline:last-child { margin-bottom: 0; }
.jny-welcome .wel-body {
  margin: 0; font-size: 20px; line-height: 1.5; font-weight: 500;
  color: #14171f; text-align: center;
}
.jny-welcome .wel-capstoggle {
  min-width: 44px; min-height: 48px; padding: 0 14px;
  background: none; border: 2px solid transparent; border-radius: 10px;
  font-size: 16px; color: #5b6473; cursor: pointer;   /* 5.97:1 */
}
/* Captions OFF must be visible without relying on colour alone. */
.jny-welcome .wel-capstoggle[aria-pressed="true"] { border-color: #c9c3ac; color: #122c4f; font-weight: 600; }
.jny-welcome .wel-capstoggle[aria-pressed="false"]::after { content: " off"; }
.jny-welcome .wel-btn {
  width: 100%; min-height: 52px; padding: 0 16px;
  font-size: 18px; font-weight: 600; border-radius: 10px; cursor: pointer;
  border: 2px solid #122c4f;
}
.jny-welcome .wel-btn--primary { background: #122c4f; color: #ffffff; }  /* 14.01:1 */
.jny-welcome .wel-btn--primary:active { background: #1b3a66; }           /* 10.72:1 */
.jny-welcome .wel-btn--ghost { background: #ffffff; color: #122c4f; min-height: 48px; }
@media (min-width: 768px) {
  .jny-welcome .wel-card { max-width: 480px; padding: 24px; }
  .jny-welcome .wel-portrait { width: 260px; height: 260px; }
}

.jny-settings {
  position: absolute; inset: 0; z-index: 200;
  display: flex; flex-direction: column;
  background: #fbf9e4; color: #14171f;
  font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Inter, system-ui, sans-serif;
  animation: jny-set-in 160ms ease;
}
@keyframes jny-set-in { from { opacity: 0; transform: translateY(8px); } to { opacity: 1; transform: none; } }
@media (prefers-reduced-motion: reduce) { .jny-settings { animation: none; } }
/* .set-head = the Settings overlay's own L0 status-strip. Mirrors shell
   /app/app.css .status-strip exactly (--chrome-bg #1a1f3a, --chrome-text #e8eaf5,
   border #252b4a, 12px) so Settings reads as just another JNY card-with-L0, not
   a separate cream modal. (WKR1 2026-06-06, operator 'settings lost its L0' QA.) */
.jny-settings .set-head {
  display: flex; align-items: center; gap: 10px;
  padding: max(8px, env(safe-area-inset-top)) 14px 8px;
  background: #1a1f3a; color: #e8eaf5;
  border-bottom: 1px solid #252b4a;
  font-size: 12px; letter-spacing: 0.02em;
}
/* New L0 elements injected by openSettings 2026-06-06 — match shell Self/Kin-Board L0:
   [‹ back] [⌂ home] [identity dim] [Settings TITLE bold] ...spacer... [online pill] [✕ close] */
.jny-settings .set-l0-nav {
  color: #e8eaf5; text-decoration: none;
  font-size: 16px; line-height: 1; opacity: .9;
  padding: 2px 4px;
}
.jny-settings .set-l0-nav:hover, .jny-settings .set-l0-nav:focus { opacity: 1; outline: none; }
.jny-settings .set-l0-identity {
  font-weight: 500; opacity: .75; min-width: 0;
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap; max-width: 30%;
}
.jny-settings .set-l0-flex { flex: 1 1 auto; }
.jny-settings .set-l0-conn {
  display: inline-flex; align-items: center; gap: 6px;
  background: rgba(80,200,120,.16); color: #7ee082;
  border: 1px solid rgba(126,224,130,.32); border-radius: 999px;
  padding: 3px 9px; font-size: 11px; text-transform: uppercase; letter-spacing: .08em;
}
.jny-settings .set-l0-conn::before {
  content: ''; width: 6px; height: 6px; border-radius: 50%;
  background: #7ee082; display: inline-block;
}
.jny-settings .set-head h2 {
  margin: 0; font-size: 13px; font-weight: 700; opacity: 1;
  color: #e8eaf5; letter-spacing: 0.02em;
}
.jny-settings .set-close {
  display: inline-flex; align-items: center; justify-content: center;
  min-width: 32px; min-height: 28px; border-radius: 7px;
  border: 1px solid rgba(255,255,255,.14);
  background: transparent; color: #e8eaf5;
  font-size: 16px; line-height: 1; cursor: pointer;
  transition: background .12s;
}
.jny-settings .set-close:hover { background: rgba(255,255,255,.08); }
.jny-settings .set-close:focus-visible { outline: none; background: rgba(255,255,255,.10); box-shadow: 0 0 0 2px rgba(255,255,255,.20); }
.jny-settings .set-tabs {
  display: flex; gap: 2px; padding: 6px 10px 0; overflow-x: auto;
  border-bottom: 1px solid #c9c3ac;
}
.jny-settings .set-tab {
  flex: 0 0 auto; border: none; background: transparent; color: #5b6473;
  font-weight: 700; font-size: 13px; padding: 10px 12px; min-height: 44px;
  border-bottom: 2.5px solid transparent; cursor: pointer; white-space: nowrap;
}
.jny-settings .set-tab[aria-selected="true"] { color: #122c4f; border-bottom-color: #122c4f; }
.jny-settings .set-body { flex: 1; overflow-y: auto; padding: 16px; }
.jny-settings .set-pane[hidden] { display: none; }
.jny-settings .set-pane h3 { margin: 0 0 6px; font-size: 14px; font-weight: 700; color: #122c4f; }
.jny-settings .set-muted { color: #5b6473; font-size: 13px; line-height: 1.5; margin: 0 0 14px; }
.jny-settings .set-soon { color: #5b6473; font-size: 12px; font-style: italic; margin: 14px 0 0; }
/* segmented theme control */
.jny-settings .set-seg { display: inline-flex; border: 1px solid #c9c3ac; border-radius: 999px; overflow: hidden; margin-bottom: 18px; background: #fff; }
.jny-settings .set-seg button {
  border: none; background: #fff; color: #14171f;
  font-weight: 600; font-size: 13px; padding: 9px 18px; min-height: 44px; cursor: pointer;
}
.jny-settings .set-seg button[aria-pressed="true"] { background: #122c4f; color: #fff; }
/* switch row */
.jny-settings .set-switch { display: flex; align-items: flex-start; gap: 10px; font-size: 13.5px; line-height: 1.4; cursor: pointer; color: #14171f; }
.jny-settings .set-switch input { width: 20px; height: 20px; margin-top: 1px; flex: 0 0 auto; accent-color: #122c4f; }
.jny-settings .set-switch small { display: block; color: #5b6473; font-size: 12px; }
/* action buttons */
.jny-settings .set-btn {
  display: block; width: 100%; text-align: left; margin: 0 0 10px;
  border: 1px solid #c9c3ac; border-radius: 12px; background: #fff;
  color: #14171f; font-weight: 600; font-size: 14px; padding: 13px 15px; min-height: 48px; cursor: pointer;
}
.jny-settings .set-btn small { color: #5b6473; font-weight: 400; }
.jny-settings .set-btn.primary { background: #122c4f; color: #fff; border-color: #122c4f; }
.jny-settings .set-btn:focus-visible { outline: none; box-shadow: 0 0 0 2px rgba(18,44,79,.55); }

/* ---- Dropdown menus → LIGHT (Ogilvy reverse-text rule) ----
   The identity menu + circle-picker were dark (var(--chrome-bg)) with stacks of light-on-dark
   labels. Flip to Navy-Luxe ink-on-pearl so the menu reads easily. Appended last so these win
   by source order over the dark base rules above. */
.identity-menu, .circle-menu {
  background: #fbf9e4;
  border-color: #c9c3ac;
  color: #14171f;
  box-shadow: 0 14px 44px rgba(7,11,18,.28);
}
.identity-menu .im-header { border-bottom-color: #c9c3ac; }
.identity-menu .im-name { color: #122c4f; }
.identity-menu .im-device,
.identity-menu .im-section-label,
.identity-menu .im-muted { color: #5b6473; opacity: 1; }
.identity-menu .im-item,
.circle-menu .circle-opt { color: #14171f; }
.identity-menu .im-item:hover,
.circle-menu .circle-opt:hover { background: #efe9d2; }
.identity-menu .im-signout { color: #b3261e; }
.identity-menu .im-signout:hover { background: rgba(179,38,30,.10); }
.identity-menu .im-device-row.is-this { background: rgba(18,44,79,.10); }
.circle-menu .circle-opt.is-active { background: rgba(18,44,79,.12); color: #122c4f; }
.circle-menu .co-room { color: #5b6473; opacity: 1; }

/* ===== IDENTITY-SWAP P1 (additive) — swap slots in the account menu + manager ===== */
.im-swapset { padding: 2px 0 4px; display: flex; flex-direction: column; gap: 2px; }
.im-slot.is-active { background: rgba(80,98,199,0.15); }
.im-slot .im-muted { margin-left: auto; }
.identity-menu .im-slot.is-active { background: rgba(18,44,79,0.10); }
/* manager (reuses .device-settings backdrop + .ds-card sheet) */
.identity-manager .idm-sheet { max-width: 440px; }
.idm-head { display: flex; align-items: center; justify-content: space-between; }
.idm-close { padding: 4px 10px; }
.idm-list { display: flex; flex-direction: column; gap: 10px; margin: 10px 0; }
.idm-row { border: 1px solid var(--border); border-radius: 10px; padding: 10px 12px; }
.idm-name { font-size: 14px; font-weight: 600; }
.idm-meta { font-size: 11px; opacity: .6; margin: 2px 0 8px; }
.idm-actions { display: flex; flex-wrap: wrap; gap: 8px; }
.idm-btn {
  font-family: inherit; font-size: 12px; padding: 6px 10px; border-radius: 8px;
  border: 1px solid var(--border); background: var(--surface-2); color: var(--text); cursor: pointer;
}
.idm-btn:hover { filter: brightness(1.08); }
.idm-danger { color: #d87b7b; border-color: rgba(216,123,123,.4); }
.idm-add { width: 100%; }
.self-tag { font-size: 10px; opacity: .6; }

/* ===== caretfix global guard (2026-06-06) =====
   Clickable chrome is a single SURFACE: no text caret, no selection. Every card benefits.
   Inputs are re-enabled BELOW so they ALWAYS keep their caret + selection (last rule wins). */
.app, .hit, .ic, .lbl, .tile, [role="button"], button:not(input) {
  -webkit-user-select: none;
  user-select: none;
}
button, [role="button"], .app, .tile {
  caret-color: transparent;
}
/* re-enable real fields — never harm input (placed last + targets form controls) */
input, textarea, select, [contenteditable="true"] {
  -webkit-user-select: text;
  user-select: text;
  caret-color: auto;
  pointer-events: auto;
}

/* 2026-06-30 (operator): hide QA Capture floating pill from the grid view.
   TODO: full removal — gate at injection in /_shared/qa-capture.js or skip <script>
   in index.html for non-QA roles. CSS-hide is the smallest-blast-radius interim. */
.qac-fab{display:none !important;}

/* ─── safe-area-dedup-iosbrowser 2026-06-30 ───
   In iOS Safari (non-PWA), Safari's own URL bar already gives the L0 / chrome
   clearance from the iOS status bar. Reserving env(safe-area-inset-*) again
   creates a fat empty navy band above .status-strip and a cream gap below
   the .nav dock. Override to just the visual breathing-room (8px). PWA
   standalone keeps the full inset (notch passes through to our chrome).
   --------------------------------------------------------------------- */
body.is-ios.mode-browser .status-strip { padding-top: 8px; }
body.is-ios.mode-browser .nav,
body.is-ios.mode-browser .comm-bar { padding-bottom: 8px; }

/* ─── L0 mode-tag selector (live / sandbox / ...) 2026-06-30 ───
   Sits in .status-strip next to #identity. Defaults: live + sandbox for
   everyone; SecJSON may add fr / demo / others (read by app.js initModeTag).
   Sandbox state paints a subtle gold edge so the operator never confuses
   modes when both are open in different tabs. */
.status-strip .mode-tag {
  margin: 0 6px;
  padding: 4px 10px 4px 10px;
  border: 1px solid rgba(255,255,255,0.42);
  border-radius: 999px;
  background: rgba(255,255,255,0.06);
  color: var(--chrome-text);
  font: inherit; font-size: 12px; font-weight: 600;
  appearance: none; -webkit-appearance: none;
  background-image: linear-gradient(45deg, transparent 50%, rgba(255,255,255,0.85) 50%),
                    linear-gradient(135deg, rgba(255,255,255,0.85) 50%, transparent 50%);
  background-position: calc(100% - 12px) 50%, calc(100% - 7px) 50%;
  background-size: 5px 5px, 5px 5px;
  background-repeat: no-repeat;
  padding-right: 22px;
}
.status-strip .mode-tag option { color: #1a1f3a; background: #ffffff; }
.status-strip .mode-tag:focus-visible { outline: none; box-shadow: var(--focus-ring); }
body[data-mode-tag="sandbox"] .status-strip { box-shadow: inset 0 -2px 0 #d7be80; }
body[data-mode-tag="sandbox"] .status-strip .mode-tag {
  border-color: #d7be80; background: rgba(215,190,128,0.18); color: #ffe8b3;
}

/* ─── per-card demo data-source badge 2026-06-30 ───
   Surfaces which cards still serve demo/local-seed data regardless of the L0
   mode-tag selector (D-003 isolation not wired yet). Six cards as of 2026-06-30:
   daily-word, sudoku, word-search, jigsaw, solitaire, mahjong — all local-only
   game state w/ sample seed; api-backed cards (family-moments/checkers/bingo,
   karaoke) get no badge. In sandbox mode the badge widens to all tiles. */
#homeNext .app { position: relative; }
#homeNext .app[data-key="daily-word"]::after,
#homeNext .app[data-key="sudoku"]::after,
#homeNext .app[data-key="word-search"]::after,
#homeNext .app[data-key="jigsaw"]::after,
#homeNext .app[data-key="solitaire"]::after,
#homeNext .app[data-key="mahjong"]::after {
  content: "demo";
  position: absolute; top: 4px; right: 4px;
  background: rgba(215,190,128,0.95); color: #1a1f3a;
  font: 700 9px/1 -apple-system,BlinkMacSystemFont,system-ui,sans-serif;
  letter-spacing: .3px; text-transform: uppercase;
  padding: 3px 6px; border-radius: 8px; pointer-events: none;
  box-shadow: 0 1px 2px rgba(0,0,0,.18);
  z-index: 2;
}
/* Pass-2 sweep 2026-07-02 (D02 fix): removed the blanket sandbox "demo" ::after
   rule. Per lane-model canon (project_jny_lane_model_live_sandbox_demo) live /
   sandbox / demo are 3 DISTINCT modes. Painting DEMO on every sandbox tile
   conflated the two. Sandbox mode is signalled by the shell orange border +
   SANDBOX chip (below). The per-card "demo" data-source badge on the fixed 6
   cards (rule above) stays — it marks data-source only, not lane. */

/* ─── Sandbox mode — strong visual indicator 2026-06-30 ───
   Operator ask: 'make it work proper' — augment the subtle gold status-strip
   edge with an unmistakable orange shell border + SANDBOX badge so the
   operator can never confuse modes across tabs / windows. Sits ON TOP of the
   existing data-mode-tag=sandbox styles (additive, not replacing). */
body[data-mode-tag="sandbox"] .shell {
  box-shadow: inset 0 0 0 4px #ff8a3d, 0 8px 32px rgba(0,0,0,0.18);
  position: relative;
}
body[data-mode-tag="sandbox"] .shell::after {
  content: 'SANDBOX';
  position: absolute;
  /* Pass-3 D02c 2026-07-02: was top:8px right:8px — collided with the
     .status-strip 'online' conn pill at top-right. Drop the badge below
     the strip band (strip ~= 32px + safe-area-top); safe-area-inset-top
     covers notched devices. */
  top: calc(40px + env(safe-area-inset-top, 0px)); right: 8px;
  background: #ff8a3d;
  color: #fff;
  font: 700 10px/1 -apple-system,BlinkMacSystemFont,system-ui,sans-serif;
  letter-spacing: .5px;
  padding: 3px 8px;
  border-radius: 4px;
  z-index: 100;
  pointer-events: none;
  box-shadow: 0 1px 3px rgba(0,0,0,0.25);
}
/* Mode-flip toast — one-second confirmation when toggled from menu.
   Pass-1 sweep 2026-07-02: was top:64px, which collided with the L0 greeting
   header ("Good morning, ...") on Tier 1 iPhone-portrait. Move to bottom-
   anchored (well clear of status-strip AND stats-banner/greeting). */
.jny-mode-toast {
  position: fixed;
  left: 50%; transform: translateX(-50%);
  bottom: calc(24px + env(safe-area-inset-bottom, 0px));
  top: auto;
  background: #122c4f; color: #fff;
  font: 700 13px/1 -apple-system,BlinkMacSystemFont,system-ui,sans-serif;
  padding: 10px 18px;
  border-radius: 999px;
  z-index: 9998;
  box-shadow: 0 8px 24px rgba(0,0,0,0.32);
  pointer-events: none;
  opacity: 0;
  transition: opacity .18s ease;
}
.jny-mode-toast.show { opacity: 1; }
body[data-mode-tag="sandbox"] .jny-mode-toast {
  background: #ff8a3d;
}

/* ─── DEMO mode — strong visual indicator (Pass 2 · 2026-07-02) ───
   Per lane-model canon (project_jny_lane_model_live_sandbox_demo): live=cream,
   sandbox=orange, demo=purple. Parallel to sandbox visual: purple shell edge
   + DEMO badge + purple mode-tag pill + purple toast. Cheat-sheet operator ask. */
body[data-mode-tag="demo"] .status-strip { box-shadow: inset 0 -2px 0 #9c6ade; }
body[data-mode-tag="demo"] .status-strip .mode-tag {
  border-color: #9c6ade; background: rgba(156,106,222,0.22); color: #e6d8ff;
}
body[data-mode-tag="demo"] .shell {
  box-shadow: inset 0 0 0 4px #9c6ade, 0 8px 32px rgba(0,0,0,0.18);
  position: relative;
}
body[data-mode-tag="demo"] .shell::after {
  content: 'DEMO';
  position: absolute;
  /* Pass-3 D02c 2026-07-02: parallel to sandbox — clear the conn pill. */
  top: calc(40px + env(safe-area-inset-top, 0px)); right: 8px;
  background: #9c6ade;
  color: #fff;
  font: 700 10px/1 -apple-system,BlinkMacSystemFont,system-ui,sans-serif;
  letter-spacing: .5px;
  padding: 3px 8px;
  border-radius: 4px;
  z-index: 100;
  pointer-events: none;
  box-shadow: 0 1px 3px rgba(0,0,0,0.25);
}
body[data-mode-tag="demo"] .jny-mode-toast {
  background: #9c6ade;
}

/* =====================================================================================
   TT-FLOOR (2026-07-19) — the 48px touch-target floor, declared ONCE.

   WHY THIS BLOCK EXISTS (the class of problem, not the instance):
   The tranche-2 pass raised several controls 36/38 -> 48 by editing individual rules.
   Three controls still measured under floor afterwards, each escaping for a DIFFERENT
   reason — which is why "fix the failing selector" kept not converging:

     .cal-tab        34px  — in panels/l3-calendar.css but had NO min-height declaration
                             at all, so a find-and-raise pass had nothing to match on.
     .nav-btn        34px  — declared in an inline <style> inside index.html, a file the
                             pass never scanned (it scanned app.css + panels/*.css).
     .feed-invite-btn 38px — declared in an inline style="" ATTRIBUTE, which no external
                             stylesheet can override at any specificity short of
                             !important. It must be fixed at the attribute.

   So the floor was never one bug; it was "the floor is re-typed at each control and
   nothing enumerates the controls." The durable fix is the audit
   (technical/dev/QA/tt-floor-audit.mjs), which measures EVERY rendered interactive
   element and cannot be fooled by where the declaration happens to live.

   WHY NOT a blanket `button { min-height: 48px !important }`:
   it would inflate the calendar month-grid day cells, filter chips, and dense icon rows
   that are legitimately small and are not primary tap targets — trading an
   accessibility miss for a layout regression across the app. The floor is applied to
   PRIMARY controls; density-critical controls are exempt by design and the audit
   carries an explicit allowlist for them.
====================================================================================== */
:root { --tt-min: 48px; }

/* Utility for new controls — prefer this over re-typing the number. */
.tt-floor { min-height: var(--tt-min); box-sizing: border-box; }

/* === a11y-wave1 2026-08-06 (AA-verified, reversible) === */
.stage .loading { color:#656361; }                       /* loading text 4.42 -> 4.99:1 AA */
.jny-login-card a.jny-join-btn { background:#1e3a5f; }    /* REAL BUG: ghost btn white-on-cream 1.19 -> navy 11.5:1 */
/* === end a11y-wave1 === */
