/* Graduate, self-hosted — see static/fonts. Was a Google Fonts link. */
@font-face {
  font-family: 'Graduate';
  font-style: normal;
  font-weight: 400;
  font-display: swap;
  src: url('/fonts/graduate.woff2') format('woff2');
}

/* ================================================================
   KreyolDefandè — NervalFrost shell
   Aesthetic: NervalFrost Mobile (Palefò platform chrome) — periwinkle
   backdrop, frost panel, white cards on soft lavender strokes, brand
   purple + bubble blue. Rendered inside the Palefò Phone Frame +
   Vertical Lock pattern (430×932 canvas, bezel at ≥480px — same as
   Mesaj Pa m / FotoLekti / Tchala).
   Game identity kept: falling plank, ivory Scrabble tiles, burn FX.
   ================================================================ */

* { margin: 0; padding: 0; box-sizing: border-box; }
[hidden] { display: none !important; }

:root {
  --nf-backdrop: #e3e5fa;              /* periwinkle */
  --nf-frost: #f5f6fb;                 /* Nerval Frost — panel */
  --nf-card: #ffffff;
  --nf-blue: rgb(49, 86, 142);         /* primary text blue */
  --nf-purple: rgba(86, 44, 195, 0.85);/* brand purple */
  --nf-purple-solid: #542CC2;
  --mp-tip-blue: #3f34e6;              /* mic disc deep blue */
  --nf-disc: rgba(84, 44, 194, 0.08);  /* pale lavender disc */
  --nf-stroke-50: rgba(86, 44, 195, 0.5);
  --nf-frame-stroke: rgba(160, 140, 225, 0.5); /* default card border */
  --nf-hairline-25: rgba(160, 140, 225, 0.25);
  --bubble-blue: #6D8AFF;              /* Palefò result bubble */
  /* action gradient — app.palefo.dev --gradient-primary, verbatim */
  --nf-grad: linear-gradient(96deg, rgb(95, 165, 250) 0%, rgb(167, 78, 222) 104%);
  --breather: 14px;
  --ember: #ff7a45;                    /* intruder burn (functional) */
  --ember-deep: #e04f1f;
  --hit-green: #2fb47c;
  --tile-ink: #4a3418;                 /* Scrabble tile lettering */
}

html {
  font-size: 16px;
  -webkit-font-smoothing: antialiased;
  -webkit-text-size-adjust: 100%;
  text-size-adjust: 100%;
}

body {
  /* ONE FACE, EVERYWHERE: Graduate, the letterform on the bones. It was the
     tile alphabet only — the syllable wordmark under the hero, the score
     plaque, the leaderboard numerals — and it is now the whole app, so the
     chrome is made of the same letters as the game.

     It is a display face and it knows it: single weight (bold below is
     synthesised), all-caps in feel, and ~29% wider than the serif it
     replaced at the same size. Sizes are unchanged, so anything that fits
     is fitting on purpose. */
  font-family: 'Graduate', Georgia, serif;
  width: 100vw;
  height: 100vh;
  height: 100dvh;
  overflow: hidden;
  display: flex;
  justify-content: center;
  align-items: center;
  background: linear-gradient(160deg, #ccd4f4 0%, #e3e5fa 45%, #ece2f6 75%, #f2e4f2 100%);
  color: var(--nf-blue);
}

/* === DEVICE FRAME — 430×932 canvas at every width, scaled by JS === */
.device-frame {
  width: 430px;
  height: 932px;
  max-width: 430px;
  max-height: 932px;
  position: relative;
  overflow: hidden;
  background: var(--nf-backdrop);
  transform-origin: center center;
}

/* real phones: the frame IS the screen — fluid width, dynamic height,
   no canvas lock (the scaling JS skips the transform below 480px) */
@media (max-width: 479px) {
  .device-frame {
    width: 100vw;
    height: 100vh;
    height: 100dvh;
    max-width: none;
    max-height: none;
  }
  /* tall menu screens scroll instead of clipping on short viewports */
  #screen-home, #screen-level, #screen-end, #screen-klasman {
    overflow-y: auto;
    scrollbar-width: none;
  }
  #screen-home::-webkit-scrollbar,
  #screen-level::-webkit-scrollbar,
  #screen-end::-webkit-scrollbar,
  #screen-klasman::-webkit-scrollbar { display: none; }
}

@media (min-width: 480px) {
  body {
    /* periwinkle → lavender wash around the bezel (mesaj canon) */
    background: linear-gradient(135deg,
      #ccd4f4 0%, #e3e5fa 45%, #ece2f6 75%, #f2e4f2 100%);
  }
  .device-frame {
    border-radius: 2.8rem;
    border: 0.7rem solid #0d0d0d;
    box-shadow:
      0 1.8rem 4.5rem rgba(85, 70, 150, 0.35),
      0 0.6rem 1.6rem rgba(85, 70, 150, 0.22),
      inset 0 0 0 1px rgba(255, 255, 255, 0.06);
  }
}

.dash-layout {
  position: relative;
  z-index: 1;
  height: 100%;
  margin: 0 8px;
  padding: 8px 0 0;
  display: flex;
  flex-direction: column;
}

/* ---- frost panel ---- */
.dash-panel {
  flex: 1 1 auto;
  min-width: 0;
  /* min-height:0 keeps the flex chain constrained: without it a tall
     screen (MoDomino end card + Mo wonn lan) grows past the frame and
     gets clipped instead of letting #screen-end scroll */
  min-height: 0;
  background: linear-gradient(180deg, #fafbfe 0%, #f2f3fb 48%, #e9e5f8 100%);
  border-radius: 25px;
  margin-bottom: 8px;
  overflow: clip;
  display: flex;
  flex-direction: column;
  padding: 18px 16px 16px;
}

/* ---- top bar ---- */
.top-bar {
  position: relative;
  display: flex;
  align-items: center;
  justify-content: center;
  min-height: 44px;
  flex-shrink: 0;
}

.page-title {
  font-family: 'Graduate', Georgia, serif;
  font-style: italic;
  font-weight: 500;
  font-size: 27px;
  letter-spacing: 0.01em;
  text-align: center;
  color: var(--nf-blue);
}

.back-btn {
  position: absolute;
  left: 0; top: 50%;
  transform: translateY(-50%);
  width: 44px; height: 44px;
  display: flex; align-items: center; justify-content: center;
  background: none; border: none; padding: 0;
  color: var(--nf-purple);
  cursor: pointer;
  transition: color 0.2s ease, transform 0.2s ease;
}
.back-btn svg { width: 24px; height: 24px; }
.back-btn:hover { color: var(--nf-purple-solid); }
.back-btn:active { transform: translateY(-50%) scale(0.9); }

.hud {
  position: absolute;
  right: 0; top: 50%;
  transform: translateY(-50%);
}

/* global sound toggle — sits where the HUD sits; showScreen swaps them */
.sound-btn {
  position: absolute;
  right: 0; top: 50%;
  transform: translateY(-50%);
  width: 44px; height: 44px;
  display: flex; align-items: center; justify-content: center;
  background: none; border: none; padding: 0;
  color: var(--nf-purple);
  cursor: pointer;
  transition: color 0.2s ease, transform 0.2s ease;
}
.sound-btn svg { width: 24px; height: 24px; }
.sound-btn .sound-off { display: none; }
.sound-btn.is-muted .sound-on { display: none; }
.sound-btn.is-muted .sound-off { display: block; }
.sound-btn.is-muted { color: rgba(86, 44, 195, 0.45); }
.sound-btn:hover { color: var(--nf-purple-solid); }
.sound-btn:active { transform: translateY(-50%) scale(0.9); }

/* volume popover — opens under the sound/music icon on tap. Fixed-positioned
   from JS so it clears the header rather than being clipped by it. */
.vol-pop {
  position: fixed;
  z-index: 60;
  display: flex;
  align-items: center;
  gap: 10px;
  padding: 9px 12px;
  background: var(--nf-card);
  border: 1px solid var(--nf-frame-stroke);
  border-radius: 12px;
  box-shadow: 0 8px 24px rgba(40, 22, 90, 0.18);
}
.vol-pop[hidden] { display: none; }   /* beat the display:flex above when closed */
.vol-pop .vol-range { width: 118px; accent-color: var(--nf-purple-solid); cursor: pointer; }
.vol-pop .vol-pct {
  min-width: 34px; text-align: right;
  font-size: 13px; color: var(--nf-blue);
  font-variant-numeric: tabular-nums;
}
.vol-pop .vol-mute {
  flex: 0 0 auto;
  width: 30px; height: 30px;
  display: flex; align-items: center; justify-content: center;
  padding: 0; border: none; border-radius: 8px;
  cursor: pointer;
  background: var(--nf-disc); color: var(--nf-purple-solid);
}
.vol-pop .vol-mute::before { content: "\1F50A"; font-size: 15px; line-height: 1; }  /* 🔊 */
.vol-pop.is-off .vol-mute::before { content: "\1F507"; }                            /* 🔇 */
.vol-pop.is-off .vol-mute { color: rgba(86, 44, 195, 0.5); }
.vol-pop.is-off .vol-range { accent-color: rgba(86, 44, 195, 0.4); }
/* the score: a mini Scrabble tile, counting in Graduate */
.hud-chip {
  display: inline-flex;
  align-items: baseline;
  gap: 5px;
  padding: 6px 11px 5px;
  border-radius: 8px;
  border: none;
  background: linear-gradient(150deg, #fdf3da 0%, #f5e2b6 62%, #ecd29a 100%);
  box-shadow:
    inset 0 1px 0 rgba(255, 255, 255, 0.75),
    inset 0 -2px 3px rgba(140, 96, 40, 0.35),
    0 2px 8px rgba(60, 30, 100, 0.18);
  font-family: 'Graduate', Georgia, serif;
  font-size: 1.02rem;
  line-height: 1;
  color: var(--tile-ink);
  font-variant-numeric: tabular-nums;
}
.hud-chip::after {
  content: 'PTS';
  font-family: 'Graduate', Georgia, serif;
  font-size: 0.48rem;
  font-weight: 700;
  letter-spacing: 0.09em;
  color: rgba(74, 52, 24, 0.6);
}
.hud-chip.is-bump { animation: scoreBump 0.38s ease; }
@keyframes scoreBump {
  30% { transform: scale(1.22) rotate(-2deg); }
  60% { transform: scale(0.96); }
}

/* arcade points that rise off a cleared plank */
.score-float {
  position: absolute;
  z-index: 6;
  font-family: 'Graduate', Georgia, serif;
  font-size: 1.55rem;
  line-height: 1;
  white-space: nowrap;
  background: linear-gradient(100deg, var(--bubble-blue) 10%, var(--nf-purple-solid) 85%);
  -webkit-background-clip: text;
  background-clip: text;
  -webkit-text-fill-color: transparent;
  filter: drop-shadow(0 2px 8px rgba(84, 44, 194, 0.35));
  pointer-events: none;
  animation: scoreFloat 1.05s ease-out forwards;
}
@keyframes scoreFloat {
  0%   { opacity: 0; transform: translate(-50%, 10px) scale(0.7); }
  18%  { opacity: 1; transform: translate(-50%, 0) scale(1.18); }
  100% { opacity: 0; transform: translate(-50%, -52px) scale(1); }
}

/* ---- screens ---- */
.screen {
  display: none;
  flex: 1 1 auto;
  min-height: 0;
  flex-direction: column;
  animation: screenIn 0.3s ease;
}
.screen.is-active { display: flex; }
@keyframes screenIn {
  from { opacity: 0; transform: translateY(8px); }
  to   { opacity: 1; transform: translateY(0); }
}

/* ================================================================
   HOME
   ================================================================ */
/* hero banner — FotoLekti spec: 92% / 128px / 24px */
.home-hero {
  margin-top: 4px;
  flex-shrink: 0;
  display: flex;
  justify-content: center;
}
.home-hero img {
  width: 92%;
  max-width: 430px;
  height: 128px;
  object-fit: cover;
  border-radius: 24px;
  box-shadow:
    0 0.8rem 1.8rem rgba(85, 70, 150, 0.22),
    0 0.3rem 0.7rem rgba(86, 44, 195, 0.12);
}

#screen-home .page-title {
  margin-top: 14px;
  flex-shrink: 0;
}

.mode-cards {
  margin-top: 20px;
  display: flex;
  flex-direction: column;
  gap: var(--breather);
}

/* white frost cards, lavender stroke, pale disc badge (mesaj canon) */
.mode-card {
  position: relative;
  display: flex;
  align-items: center;
  gap: 14px;
  width: 100%;
  padding: 16px 14px;
  background: var(--nf-card);
  border: 1px solid var(--nf-frame-stroke);
  border-radius: 18px;
  cursor: pointer;
  text-align: left;
  font-family: inherit;
  color: var(--nf-blue);
  box-shadow: 0 2px 10px rgba(60, 30, 100, 0.06);
  transition: transform 0.2s ease, box-shadow 0.2s ease, border-color 0.2s ease;
}
.mode-card:hover {
  transform: translateY(-2px);
  border-color: var(--nf-stroke-50);
  box-shadow: 0 6px 18px rgba(60, 30, 100, 0.12);
}
.mode-card:active { transform: scale(0.98); }

.mode-card__glyph {
  width: 46px; height: 46px;
  flex-shrink: 0;
  display: flex; align-items: center; justify-content: center;
  border-radius: 50%;
  background: var(--nf-disc);
  font-size: 1.35rem;
}
.mode-card__body { flex: 1 1 auto; min-width: 0; }
.mode-card__name {
  display: block;
  font-family: 'Graduate', Georgia, serif;
  font-style: italic;
  font-weight: 600;
  font-size: 1.2rem;
  color: var(--nf-purple-solid);
}
.mode-card__desc {
  display: block;
  margin-top: 3px;
  font-size: 0.82rem;
  line-height: 1.4;
  color: rgba(49, 86, 142, 0.72);
}
.mode-card__chev {
  width: 18px; height: 18px;
  flex-shrink: 0;
  color: var(--nf-stroke-50);
}

.klasman-link {
  margin: 30px auto 0;
  padding: 8px 18px;
  border: 1px solid var(--nf-frame-stroke);
  border-radius: 999px;
  background: linear-gradient(160deg, #ffffff 0%, #f1edfb 100%);
  color: var(--nf-purple-solid);
  font-family: inherit;
  font-size: 0.85rem;
  font-weight: 700;
  cursor: pointer;
  box-shadow: 0 2px 10px rgba(60, 30, 100, 0.06);
  transition: background 0.2s ease, transform 0.15s ease, border-color 0.2s ease;
}
.klasman-link:hover { background: var(--nf-disc); border-color: var(--nf-stroke-50); }
.klasman-link:active { transform: scale(0.96); }

.home-best {
  margin-top: auto;
  padding-top: 14px;
  text-align: center;
  font-size: 0.8rem;
  font-weight: 600;
  color: rgba(86, 44, 195, 0.55);
}

/* ---- Palefò wordmark footer ---- */

/* ================================================================
   LEVEL SELECT — Anvayisè Franse difficulty ladder
   ================================================================ */
.level-intro {
  margin-top: 30px;
  text-align: center;
  font-family: 'Graduate', Georgia, serif;
  font-style: italic;
  font-weight: 600;
  font-size: 1.35rem;
  color: var(--nf-purple-solid);
}

/* ================================================================
   MODE 1 — ANVAYISÈ FRANSE: the plank falls through the frost
   ================================================================ */
.round-meta {
  margin-top: var(--breather);
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 10px;
  flex-shrink: 0;
}
.round-chip {
  padding: 4px 14px;
  border-radius: 999px;
  background: var(--nf-disc);
  border: 1px solid var(--nf-frame-stroke);
  font-size: 0.8rem;
  font-weight: 700;
  color: var(--nf-purple-solid);
  font-variant-numeric: tabular-nums;
}
.streak-chip {
  padding: 4px 12px;
  border-radius: 999px;
  background: var(--nf-grad);
  color: #fff;
  font-size: 0.8rem;
  font-weight: 700;
  animation: bubbleIn 0.25s ease;
}
/* keeper wrongly struck */
.letter-tile.is-wrong {
  animation: wrongPulse 0.4s ease;
  color: var(--ember-deep);
}
@keyframes wrongPulse {
  0%, 100% { transform: scale(1); }
  40% { transform: scale(1.15); }
}
.burn-fx .flash {
  left: -28px; top: -28px;
  width: 56px; height: 56px;
  border-radius: 50%;
  background: radial-gradient(circle,
    rgba(255, 244, 180, 0.95) 0%, rgba(255, 150, 60, 0.6) 40%,
    rgba(224, 79, 31, 0) 72%);
  animation: burnFlash 0.32s ease-out forwards;
}
@keyframes burnFlash {
  0%   { opacity: 1; transform: scale(0.3); }
  100% { opacity: 0; transform: scale(1.8); }
}
@keyframes emberFly {
  0%   { opacity: 1; transform: translate(0, 0) scale(1); }
  70%  { opacity: 1; }
  100% { opacity: 0; transform: translate(var(--dx, 0px), var(--dy, -40px)) scale(0.25); }
}
.play-feedback {
  flex-shrink: 0;
  min-height: 2.4em;
  padding: 8px 0 4px;
  text-align: center;
  font-size: 0.92rem;
  font-weight: 700;
  color: rgba(49, 86, 142, 0.6);
  transition: color 0.2s ease;
}
.play-feedback.is-hit { color: var(--hit-green); }
.play-feedback.is-miss { color: var(--ember-deep); }
/* ================================================================
   MODE — GLISE MO: words rain down, swipe them home
   ================================================================ */

/* correct-swipe confetti burst (rides the emberFly flight path) */
.pop-fx {
  position: absolute;
  width: 0; height: 0;
  pointer-events: none;
  z-index: 5;
}
.pop-fx i {
  position: absolute;
  left: 0; top: 0;
  display: block;
  width: 7px; height: 7px;
  border-radius: 2px;
  opacity: 0;
  animation: emberFly var(--dur, 0.7s) ease-out var(--delay, 0s) forwards;
}
/* ================================================================
   MODE 2 — TANDE • EKRI
   ================================================================ */

.dikte-verdict.is-wrong { background: var(--ember-deep); }

@keyframes bubbleIn {
  from { opacity: 0; transform: translateY(6px); }
  to   { opacity: 1; transform: translateY(0); }
}
/* ================================================================
   MODE 3 — DI L (ASR)
   ================================================================ */


/* frost blue inside, brand violet outside — the mesaj ring cascade */
/* ================================================================
   LEADERBOARD
   ================================================================ */
.lb-card {
  position: relative;
  margin-top: var(--breather);
  padding: 20px 16px 14px;
  background: var(--nf-card);
  border: 1px solid var(--nf-frame-stroke);
  border-radius: 16px;
  box-shadow: 0 8px 26px rgba(60, 30, 100, 0.10);
  overflow: hidden;
}
.lb-title {
  font-family: 'Graduate', Georgia, serif;
  font-style: italic;
  font-weight: 600;
  font-size: 1.15rem;
  color: var(--nf-purple-solid);
  text-align: center;
  margin-bottom: 10px;
}
.lb-form {
  display: flex;
  gap: 8px;
  margin-bottom: 12px;
}
.lb-name {
  flex: 1 1 auto;
  min-width: 0;
  padding: 9px 12px;
  border-radius: 10px;
  border: 1px solid var(--nf-frame-stroke);
  background: var(--nf-frost);
  font-family: 'Graduate', Georgia, serif;
  font-size: 0.95rem;
  color: var(--nf-blue);
  text-transform: uppercase;
}
.lb-name:focus {
  outline: none;
  border-color: var(--nf-purple);
  box-shadow: 0 0 0 3px rgba(86, 44, 195, 0.12);
}
.lb-save {
  flex-shrink: 0;
  padding: 0 16px;
  border: none;
  border-radius: 10px;
  background: var(--nf-grad);
  box-shadow: 0 4px 14px rgba(167, 78, 222, 0.3);
  color: #fff;
  font-family: inherit;
  font-size: 0.88rem;
  font-weight: 700;
  cursor: pointer;
  transition: transform 0.15s ease, background 0.2s ease;
}
.lb-save:hover { filter: brightness(1.08); }
.lb-save:active { transform: scale(0.95); }

.lb-rows {
  list-style: none;
  display: flex;
  flex-direction: column;
}
.lb-rows li {
  display: grid;
  grid-template-columns: 34px 1fr auto;
  align-items: center;
  gap: 8px;
  padding: 7px 8px;
  border-radius: 9px;
  font-size: 0.92rem;
  color: var(--nf-blue);
}
.lb-rows li + li { border-top: 1px solid var(--nf-hairline-25); }
.lb-rank {
  font-family: 'Graduate', Georgia, serif;
  font-size: 0.85rem;
  text-align: center;
  color: rgba(86, 44, 195, 0.55);
}
.lb-player {
  font-weight: 700;
  letter-spacing: 0.03em;
  text-transform: uppercase;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.lb-pts {
  font-family: 'Graduate', Georgia, serif;
  font-size: 0.95rem;
  font-variant-numeric: tabular-nums;
  color: var(--nf-purple-solid);
}
.lb-rows li.lb-me {
  background: var(--nf-disc);
  box-shadow: inset 0 0 0 1px var(--nf-frame-stroke);
  animation: bubbleIn 0.3s ease;
}
.lb-empty {
  padding: 12px 4px;
  text-align: center;
  font-size: 0.85rem;
  color: rgba(49, 86, 142, 0.55);
}

.lb-tabs {
  margin-top: var(--breather);
  display: flex;
  justify-content: center;
  gap: 8px;
}
.lb-tab {
  padding: 6px 14px;
  border-radius: 999px;
  border: 1px solid var(--nf-frame-stroke);
  background: var(--nf-card);
  color: var(--nf-purple-solid);
  font-family: inherit;
  font-size: 0.8rem;
  font-weight: 700;
  cursor: pointer;
  transition: background 0.2s ease, color 0.2s ease;
}
.lb-tab.is-active {
  background: var(--nf-grad);
  border-color: transparent;
  color: #fff;
}

/* ================================================================
   END SCREEN + shared buttons
   ================================================================ */
.end-card {
  position: relative;
  margin-top: 34px;
  padding: 28px 20px;
  background: var(--nf-card);
  border: 1px solid var(--nf-frame-stroke);
  border-radius: 20px;
  box-shadow: 0 8px 26px rgba(60, 30, 100, 0.10);
  text-align: center;
  overflow: hidden;
}
.end-emblem { font-size: 2.6rem; }
.end-title {
  margin-top: 8px;
  font-family: 'Graduate', Georgia, serif;
  font-style: italic;
  font-weight: 600;
  font-size: 1.6rem;
  color: var(--nf-purple-solid);
}
.end-stats {
  margin-top: 16px;
  display: flex;
  justify-content: center;
  gap: 22px;
}
.end-stat { text-align: center; }
.end-stat b {
  display: block;
  font-family: 'Graduate', Georgia, serif;
  font-size: 1.5rem;
  font-weight: 400;
  color: var(--nf-blue);
  font-variant-numeric: tabular-nums;
}
.end-stat span {
  font-size: 0.72rem;
  font-weight: 700;
  letter-spacing: 0.06em;
  text-transform: uppercase;
  color: rgba(49, 86, 142, 0.55);
}
.end-note {
  margin-top: 14px;
  font-size: 0.88rem;
  line-height: 1.5;
  color: rgba(49, 86, 142, 0.75);
}

.primary-btn {
  margin-top: 22px;
  width: 100%;
  padding: 14px;
  border: none;
  border-radius: 16px;
  background: var(--nf-grad);
  box-shadow: 0 6px 18px rgba(167, 78, 222, 0.32);
  color: #fff;
  font-family: inherit;
  font-size: 1rem;
  font-weight: 700;
  cursor: pointer;
  transition: transform 0.15s ease, background 0.2s ease;
}
.primary-btn:hover { filter: brightness(1.08); }
.primary-btn:active { transform: scale(0.98); }

.ghost-btn {
  margin-top: 10px;
  width: 100%;
  padding: 12px;
  border: 1px solid var(--nf-frame-stroke);
  border-radius: 16px;
  background: linear-gradient(160deg, #ffffff 0%, #f1edfb 100%);
  color: var(--nf-purple-solid);
  font-family: inherit;
  font-size: 0.92rem;
  font-weight: 700;
  cursor: pointer;
  transition: background 0.2s ease, transform 0.15s ease;
}
.ghost-btn:hover { background: var(--nf-disc); }
.ghost-btn:active { transform: scale(0.98); }

/* ---- quality floor: calm the decoration for reduced-motion users.
   The fall itself is gameplay (driven by JS), so it stays. ---- */
@media (prefers-reduced-motion: reduce) {
  .word-sign, .fall-wrap.is-danger .word-sign { animation: none; }
  .street-line { animation: none; }
  .burn-fx { display: none; }
  .dom-table::before { animation: none; }
  .dom-slot .dom-tile { animation: none; }
  .dom-chain.is-rainbow .dom-tile { animation: none; }
  .mic-btn.is-recording,
  .mic-btn.is-recording::before,
  .mic-btn.is-recording::after,
  .speak-btn.is-speaking,
  .speak-btn.is-speaking::before { animation: none; }
  .screen { animation: none; }
}

/* narrow phones: tighter tiles so long words hold one line —
   8 letters at full size and 11 at is-long both fit a 360px screen.
   Lives at the END of the sheet so it wins the cascade over base rules. */
@media (max-width: 479px) {
  .word-sign { padding: 22px 8px 20px; }
}

/* ================================================================
   GAME-DESIGN PASS: collection, sivivan, boss, gold, stamps, recap
   (appended — wins the cascade over base rules)
   ================================================================ */

/* home: two link pills side by side; known-words line above records */
.home-links {
  margin-top: 30px;
  display: flex;
  justify-content: center;
  gap: 10px;
  flex-shrink: 0;
}
.home-links .klasman-link { margin: 0; }
.home-known {
  margin-top: auto;
  padding-top: 14px;
  text-align: center;
  font-size: 0.8rem;
  font-weight: 700;
  color: rgba(86, 44, 195, 0.7);
}
.home-known:not([hidden]) + .home-best { margin-top: 4px; padding-top: 0; }
.home-known[hidden] + .home-best { margin-top: auto; }

/* san-èd toggle on the level screen */
.level-toggle {
  margin-top: 16px;
  display: flex;
  align-items: center;
  gap: 10px;
  width: 100%;
  padding: 12px 14px;
  background: var(--nf-card);
  border: 1px solid var(--nf-frame-stroke);
  border-radius: 14px;
  font-family: inherit;
  font-size: 0.85rem;
  font-weight: 600;
  color: var(--nf-blue);
  cursor: pointer;
  text-align: left;
}
.level-toggle b { color: var(--nf-purple-solid); }
.level-toggle__box {
  flex-shrink: 0;
  width: 20px; height: 20px;
  border-radius: 6px;
  border: 2px solid var(--nf-stroke-50);
  background: #fff;
  transition: background 0.15s ease, border-color 0.15s ease;
}
.level-toggle.is-on .level-toggle__box {
  background: var(--nf-grad);
  border-color: transparent;
  box-shadow: inset 0 0 0 3px #fff;
}
/* stamps: PAFÈ! / Sove! slam onto the plank */
.stamp {
  position: absolute;
  z-index: 7;
  transform: translate(-50%, -50%) rotate(-12deg);
  padding: 4px 14px;
  border: 3px solid currentColor;
  border-radius: 10px;
  background: rgba(255, 255, 255, 0.88);
  font-family: 'Graduate', Georgia, serif;
  font-size: 1.7rem;
  letter-spacing: 0.04em;
  pointer-events: none;
  animation: stampIn 0.4s cubic-bezier(0.2, 1.4, 0.4, 1), stampOut 0.3s ease 1s forwards;
}
@keyframes stampIn {
  0%   { opacity: 0; transform: translate(-50%, -50%) rotate(-12deg) scale(2.2); }
  100% { opacity: 1; transform: translate(-50%, -50%) rotate(-12deg) scale(1); }
}
@keyframes stampOut { to { opacity: 0; } }

/* end-of-round recap: the words, fr → ht */
.recap-bonus {
  text-align: center;
  font-size: 0.85rem;
  font-weight: 700;
  color: var(--nf-purple-solid);
  margin-bottom: 8px;
  min-height: 1em;
}
.recap-rows {
  list-style: none;
  display: flex;
  flex-direction: column;
}
.recap-row {
  display: grid;
  grid-template-columns: 1fr 16px 1fr auto;
  align-items: baseline;
  gap: 7px;
  padding: 6px 8px;
  border-radius: 8px;
  font-size: 0.88rem;
  color: rgba(49, 86, 142, 0.8);
}
.recap-row + .recap-row { border-top: 1px solid var(--nf-hairline-25); }
.recap-fr { text-align: right; }
.recap-arrow { color: var(--nf-stroke-50); }
.recap-ht { color: var(--nf-purple-solid); font-weight: 700; }
.recap-gloss { font-size: 0.72rem; color: rgba(49, 86, 142, 0.5); }
.recap-mark { font-size: 0.8rem; font-weight: 700; }
.recap-row.is-clean .recap-mark { color: #f0a93a; }
.recap-row:not(.is-clean):not(.is-escaped) .recap-mark { color: var(--hit-green); }
.recap-row.is-escaped { background: rgba(224, 79, 31, 0.07); }
.recap-row.is-escaped .recap-mark { color: var(--ember-deep); text-transform: uppercase; font-size: 0.66rem; }
.share-btn { margin-top: 12px; }

/* koleksyon book */
.kol-stats {
  margin-top: var(--breather);
  display: flex;
  justify-content: center;
  gap: 8px;
  flex-wrap: wrap;
  flex-shrink: 0;
}
.kol-chip {
  padding: 5px 13px;
  border-radius: 999px;
  background: var(--nf-grad);
  color: #fff;
  font-size: 0.8rem;
  font-weight: 700;
}
.kol-chip--rev { background: linear-gradient(100deg, #ffb547, #ff7a45); }
.kol-chip--dim { background: var(--nf-disc); color: var(--nf-purple-solid); border: 1px solid var(--nf-frame-stroke); }
.kol-list {
  list-style: none;
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: 2px 12px;
}
.kol-list li {
  display: flex;
  align-items: baseline;
  gap: 6px;
  padding: 4px 2px;
  font-size: 0.85rem;
  overflow: hidden;
}
.kol-list b { color: var(--nf-purple-solid); }
.kol-list span {
  font-size: 0.7rem;
  color: rgba(49, 86, 142, 0.5);
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

/* tall content screens scroll at every size */
#screen-end, #screen-koleksyon {
  overflow-y: auto;
  scrollbar-width: none;
}
#screen-end::-webkit-scrollbar, #screen-koleksyon::-webkit-scrollbar { display: none; }

/* ================================================================
   Taller Okap backdrop + scoreboard merged onto the image
   ================================================================ */
/* the image rises up behind the round chips — chips float on the scene */
#screen-play .round-meta { position: relative; z-index: 3; margin-top: 10px; }
#screen-play .sign-stage { margin: -38px -8px 0; }
#screen-glise .glise-lanes { position: relative; z-index: 3; margin-top: 8px; }
#screen-glise .glise-stage { margin: -30px -8px 0; }

/* the points counter: bigger, hanging over the artwork like a plaque */
.hud { top: 175%; z-index: 20; }
.hud-chip {
  font-size: 1.45rem;
  padding: 10px 15px 8px;
  border-radius: 12px;
  box-shadow:
    inset 0 1px 0 rgba(255, 255, 255, 0.75),
    inset 0 -2px 3px rgba(140, 96, 40, 0.35),
    0 6px 16px rgba(60, 30, 100, 0.35);
}
.hud-chip::after { font-size: 0.6rem; }

/* score plaque v2: 30% bigger, lowered to the cloud line, high-contrast */
.hud { top: 120px; }
.hud-chip {
  font-size: 1.9rem;
  padding: 12px 18px 10px;
  border-radius: 14px;
  background: var(--nf-grad);
  color: #fff;
  box-shadow:
    inset 0 1px 0 rgba(255, 255, 255, 0.35),
    0 8px 20px rgba(167, 78, 222, 0.45);
}
.hud-chip::after { font-size: 0.72rem; color: rgba(255, 255, 255, 0.85); }

/* music toggle sits beside the sound toggle */
.music-btn { right: 44px; }
.music-btn .music-off { display: none; }
.music-btn.is-off .music-on { display: none; }
.music-btn.is-off .music-off { display: block; }
.music-btn.is-off { color: rgba(86, 44, 195, 0.45); }
/* ================================================================
   Background luminosity +20% — the image moves to its own ::before
   layer so brightness() can hit it without touching the game pieces.
   Wash rides ::after; content sits above both.
   ================================================================ */


/* WhatsApp share — brand green (mesaj canon --wa-green) */
.wa-btn {
  margin-top: 10px;
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 8px;
  width: 100%;
  padding: 12px;
  border-radius: 16px;
  background: #25D366;
  color: #fff;
  font-family: inherit;
  font-size: 0.92rem;
  font-weight: 700;
  text-decoration: none;
  box-shadow: 0 4px 14px rgba(37, 211, 102, 0.35);
  transition: filter 0.2s ease, transform 0.15s ease;
}
.wa-btn svg { width: 20px; height: 20px; }
.wa-btn:hover { filter: brightness(1.06); }
.wa-btn:active { transform: scale(0.98); }

/* ================================================================
   MODOMINO — the table every Haitian household knows
   ================================================================ */
.dom-prompt {
  margin-top: 12px;
  display: flex;
  align-items: baseline;
  justify-content: center;
  gap: 10px;
  flex-shrink: 0;
}
.dom-listen {
  align-self: center;
  display: flex; align-items: center; justify-content: center;
  width: 34px; height: 34px;
  border: 1px solid var(--nf-frame-stroke);
  border-radius: 50%;
  background: var(--nf-disc);
  color: var(--nf-purple-solid);
  cursor: pointer;
  transition: background 0.2s ease, transform 0.15s ease;
}
.dom-listen svg { width: 17px; height: 17px; }
.dom-listen:hover { background: rgba(84, 44, 194, 0.14); }
.dom-listen:active { transform: scale(0.92); }

/* the table: green felt in a wooden rim */
.dom-table {
  position: relative;
  flex: 1 1 auto;
  min-height: 0;
  margin-top: 12px;
  border: 7px solid #8b5a2b;
  border-radius: 18px;
  background:
    radial-gradient(ellipse at 50% 35%, #2e8b52 0%, #1f6e3d 55%, #175a31 100%);
  box-shadow:
    inset 0 0 34px rgba(0, 0, 0, 0.35),
    0 10px 28px rgba(23, 90, 49, 0.35);
  display: flex;
  align-items: center;
  justify-content: center;
  padding: 14px;
  overflow: hidden;
}
.dom-table.is-slam { animation: tableSlam 0.28s ease; }
@keyframes tableSlam {
  0%   { transform: translate(0, 0); }
  25%  { transform: translate(-3px, 2px) rotate(-0.5deg); }
  50%  { transform: translate(3px, -2px) rotate(0.4deg); }
  75%  { transform: translate(-2px, 1px); }
  100% { transform: translate(0, 0); }
}

.dom-chain {
  display: flex;
  flex-wrap: wrap;
  justify-content: center;
  gap: 7px;
}
.dom-slot {
  min-width: 58px;
  height: 68px;
  border: 2px dashed rgba(255, 253, 240, 0.45);
  border-radius: 9px;
  display: flex;
  align-items: center;
  justify-content: center;
  position: relative;
}
.dom-slot.is-next { border-color: rgba(255, 209, 102, 0.85); box-shadow: 0 0 12px rgba(255, 209, 102, 0.35); }

/* The syllable already on the table that the new word grows out of. It wears
   a static golden edge, which reads fine on an empty board and disappears on
   a crowded one — so when the word is dealt it pulses three times to say
   "this is where you are". z-index lifts it over its neighbours: the halo
   spreads well past the slot and would otherwise slide under the tiles
   sitting next to it on the chain. */
.dom-slot.is-carry-cell.is-flash {
  z-index: 4;
  animation: domCarryFlash 0.78s ease-in-out 3;
}
@keyframes domCarryFlash {
  0%, 100% { box-shadow: 0 0 0 0 rgba(255, 209, 102, 0); }
  45% {
    box-shadow:
      0 0 0 5px rgba(255, 209, 102, 0.92),
      0 0 22px 9px rgba(255, 209, 102, 0.55);
  }
}
/* the alert still has to land without motion: one steady ring that fades out
   on its own, no pulsing. (A transition cannot do this — nothing changes the
   value after the class lands, so the ring would simply stay lit forever.) */
@keyframes domCarryHold {
  0%, 72% { box-shadow: 0 0 0 5px rgba(255, 209, 102, 0.92); }
  100%    { box-shadow: 0 0 0 5px rgba(255, 209, 102, 0); }
}
@media (prefers-reduced-motion: reduce) {
  .dom-slot.is-carry-cell.is-flash { animation: domCarryHold 2.6s ease-out 1; }
}

/* the tile: heavy ivory, divider + pips like the real thing */
.dom-tile {
  min-width: 58px;
  height: 68px;
  padding: 0 10px;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: 4px;
  background: linear-gradient(160deg, #fffef8 0%, #f2eede 60%, #ddd6c2 100%);
  border: none;
  border-radius: 9px;
  box-shadow:
    inset 0 2px 0 rgba(255, 255, 255, 0.9),
    inset 0 -3px 5px rgba(120, 100, 60, 0.35),
    0 6px 10px rgba(0, 0, 0, 0.35);
  font-family: 'Graduate', Georgia, serif;
  font-size: 1.05rem;
  color: #2a2418;
  cursor: pointer;
  user-select: none;
  -webkit-user-select: none;
  -webkit-tap-highlight-color: transparent;
  transition: transform 0.15s ease, box-shadow 0.2s ease, opacity 0.2s ease;
}
/* full letter card: the divider + pips are retired — the syllable
   owns the whole ivory face */
.dom-hand .dom-tile:hover { transform: translateY(-6px) scale(1.06); box-shadow:
  inset 0 2px 0 rgba(255,255,255,.9), inset 0 -3px 5px rgba(120,100,60,.35), 0 12px 18px rgba(0,0,0,.4); }
.dom-hand .dom-tile:active { transform: translateY(0) scale(0.95); }
/* A played tile FOLDS out of the hand instead of leaving a hole behind:
   width/padding collapse to nothing and the surviving tiles close ranks.
   The negative margin is the subtle part — a zero-width flex item still
   sits between two `gap`s, so without it the hand keeps 16px where 8px
   belongs and the seam stays visible. Width is driven from JS (measured
   px -> 0) because you cannot transition away from `width: auto`. */
.dom-tile.is-used {
  opacity: 0;
  pointer-events: none;
  min-width: 0;
  padding-left: 0;
  padding-right: 0;
  margin-left: calc(var(--dom-gap, 8px) * -1);
  transform: scale(0.72);
  overflow: hidden;
  white-space: nowrap;
  transition: width 0.19s ease, padding 0.19s ease, margin 0.19s ease,
              transform 0.19s ease, opacity 0.13s ease;
}
/* the gap must still close for reduced-motion users — only the glide goes.
   Placed after the rule above on purpose: @media adds no specificity. */
@media (prefers-reduced-motion: reduce) {
  .dom-tile.is-used { transition: none; }
}
.dom-tile.is-wrong { animation: wrongPulse 0.4s ease; color: var(--ember-deep); }

/* placed tile slams down: big → overshoot → seat */
.dom-slot .dom-tile { cursor: default; }
.dom-slot .dom-tile.is-slammed { animation: domSlam 0.3s cubic-bezier(0.2, 1.5, 0.4, 1); }
@keyframes domSlam {
  0%   { transform: scale(1.6) rotate(-6deg); opacity: 0.6; }
  55%  { transform: scale(0.92) rotate(1.5deg); opacity: 1; }
  100% { transform: scale(1) rotate(0); }
}
.slam-ring {
  position: absolute;
  inset: -8px;
  border: 3px solid rgba(255, 253, 240, 0.7);
  border-radius: 12px;
  pointer-events: none;
  animation: slamRing 0.4s ease-out forwards;
}
@keyframes slamRing {
  0%   { opacity: 0.9; transform: scale(0.7); }
  100% { opacity: 0; transform: scale(1.5); }
}

.dom-hand {
  --dom-gap: 8px;          /* .dom-tile.is-used cancels exactly one of these */
  flex-shrink: 0;
  margin-top: 12px;
  display: flex;
  flex-wrap: wrap;
  justify-content: center;
  gap: var(--dom-gap);
  min-height: 76px;
}
.lb-tabs { flex-wrap: wrap; }

/* MoDomino v2: the clue replaces the word — meaning on screen, French in the ear */
.dom-prompt__text {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 2px;
  min-width: 0;
}
.dom-clue {
  font-family: 'Graduate', Georgia, serif;
  font-style: italic;
  font-weight: 600;
  font-size: 1.35rem;
  color: var(--nf-purple-solid);
  text-align: center;
}
.dom-sub {
  font-size: 0.72rem;
  font-weight: 600;
  color: rgba(49, 86, 142, 0.55);
}

/* MoDomino v3: the clue is a full Kreyòl definition — give it room */
.dom-clue { font-size: 1.02rem; line-height: 1.4; max-width: 300px; }

/* score plaque v3: pinned to the very top-right corner */
.hud { top: 0; transform: none; }

/* ================================================================
   MODOMINO v3: kase locks, hints, Djo, chains, daily skins
   ================================================================ */
.dom-hand.is-locked { pointer-events: none; opacity: 0.55; transition: opacity 0.2s ease; }
.dom-hint { font-size: 1rem; }
.dom-hint.is-used { opacity: 0.35; pointer-events: none; }
.djo-chip { background: rgba(42, 36, 24, 0.85); border-color: transparent; color: #ffd166; }
/* the carried chain tile arrives pre-seated with a golden edge */
.dom-slot .dom-tile.is-carry {
  box-shadow:
    inset 0 2px 0 rgba(255, 255, 255, 0.9),
    inset 0 -3px 5px rgba(120, 100, 60, 0.35),
    0 0 0 2px rgba(255, 209, 102, 0.8),
    0 6px 10px rgba(0, 0, 0, 0.35);
}
/* daily-streak table skins */
.dom-table.skin-ble {
  background: radial-gradient(ellipse at 50% 35%, #2e6a8b 0%, #1f4e6e 55%, #17405a 100%);
}
.dom-table.skin-drapo {
  background:
    linear-gradient(180deg, rgba(0, 32, 159, 0.55) 0%, rgba(0, 32, 159, 0.55) 50%,
      rgba(210, 16, 52, 0.5) 50%, rgba(210, 16, 52, 0.5) 100%),
    radial-gradient(ellipse at 50% 35%, #2e8b52 0%, #1f6e3d 55%, #175a31 100%);
}

/* ================================================================
   MODOMINO COLOR PASS: party lights, ringed tiles, rainbow waves
   ================================================================ */
/* drifting colored lights on the felt */
.dom-table::before {
  content: '';
  position: absolute;
  inset: 0;
  border-radius: 11px;
  pointer-events: none;
  background:
    radial-gradient(140px 140px at 20% 30%, rgba(95, 165, 250, 0.30), transparent 70%),
    radial-gradient(170px 170px at 80% 70%, rgba(167, 78, 222, 0.26), transparent 70%),
    radial-gradient(120px 120px at 60% 15%, rgba(255, 209, 102, 0.22), transparent 70%);
  mix-blend-mode: screen;
  animation: feltLights 9s ease-in-out infinite alternate;
}
@keyframes feltLights {
  0%   { transform: translate(0, 0) scale(1); opacity: 0.8; }
  50%  { transform: translate(-14px, 10px) scale(1.06); opacity: 1; }
  100% { transform: translate(12px, -8px) scale(1.02); opacity: 0.85; }
}
.dom-chain { position: relative; z-index: 1; }

/* the chain wears the palette: each position gets its ring color */
.dom-slot:nth-child(4n + 1) { --ring: rgba(95, 165, 250, 0.8); }
.dom-slot:nth-child(4n + 2) { --ring: rgba(167, 78, 222, 0.8); }
.dom-slot:nth-child(4n + 3) { --ring: rgba(255, 209, 102, 0.85); }
.dom-slot:nth-child(4n + 4) { --ring: rgba(61, 220, 132, 0.8); }
.dom-slot .dom-tile {
  box-shadow:
    inset 0 2px 0 rgba(255, 255, 255, 0.9),
    inset 0 -3px 5px rgba(120, 100, 60, 0.35),
    0 0 0 2px var(--ring, transparent),
    0 0 14px var(--ring, transparent),
    0 6px 10px rgba(0, 0, 0, 0.35);
  animation: ringPulse 2.4s ease-in-out infinite;
}
@keyframes ringPulse {
  50% { filter: brightness(1.08); }
}
.dom-slot .slam-ring { border-color: var(--ring, rgba(255, 253, 240, 0.7)); }

/* word complete: a rainbow wave ripples down the chain */
.dom-chain.is-rainbow .dom-tile { animation: tileParty 1.1s ease; }
.dom-chain.is-rainbow .dom-slot:nth-child(1) .dom-tile { animation-delay: 0s; }
.dom-chain.is-rainbow .dom-slot:nth-child(2) .dom-tile { animation-delay: 0.1s; }
.dom-chain.is-rainbow .dom-slot:nth-child(3) .dom-tile { animation-delay: 0.2s; }
.dom-chain.is-rainbow .dom-slot:nth-child(4) .dom-tile { animation-delay: 0.3s; }
.dom-chain.is-rainbow .dom-slot:nth-child(5) .dom-tile { animation-delay: 0.4s; }
.dom-chain.is-rainbow .dom-slot:nth-child(6) .dom-tile { animation-delay: 0.5s; }
@keyframes tileParty {
  0%, 100% { filter: none; transform: none; }
  25% { filter: hue-rotate(120deg) saturate(2.6) brightness(1.15); transform: translateY(-7px) scale(1.09) rotate(-2deg); }
  55% { filter: hue-rotate(230deg) saturate(2.4); transform: translateY(0) scale(1); }
  80% { filter: hue-rotate(330deg) saturate(2); }
}

/* points float on the FELT: white→gold with a dark halo — the blue/purple
   gradient disappeared against the green table */
.score-float--felt {
  background: linear-gradient(100deg, #ffffff 15%, #ffd166 60%, #ffb547 95%);
  -webkit-background-clip: text;
  background-clip: text;
  font-size: 1.8rem;
  filter:
    drop-shadow(0 2px 3px rgba(0, 0, 0, 0.85))
    drop-shadow(0 0 10px rgba(0, 0, 0, 0.6));
}

/* ================================================================
   MODOMINO TABLE → OKAP SKY: green felt and wooden rim retired.
   The chain now plays over the same brightened Cap-Haïtien backdrop
   as KreyolDefandè, under the identical frost wash.
   ================================================================ */
.dom-table {
  border: none;
  border-radius: 16px;
  background: none;
  box-shadow: none;
}
.dom-table.skin-ble, .dom-table.skin-drapo { background: none; }
.dom-table::before {
  content: '';
  position: absolute;
  inset: 0;
  z-index: 0;
  border-radius: 0;
  pointer-events: none;
  background: url('/images/okap.jpg') center bottom / cover no-repeat;
  filter: brightness(1.2);
  mix-blend-mode: normal;
  animation: none;
}
.dom-table::after {
  content: '';
  position: absolute;
  inset: 0;
  z-index: 0;
  pointer-events: none;
  background: linear-gradient(180deg,
    rgba(250, 251, 254, 0.50) 0%,
    rgba(244, 245, 252, 0.42) 45%,
    rgba(238, 233, 250, 0.30) 100%);
}

/* The Okap film: it opens a round and then gets out of the way. It has to be
   a real element — a ::before cannot hold a <video> — which is why the still
   on ::before stays underneath rather than being replaced. That one fact
   buys two things: the dissolve at the end of the film is just this layer
   going transparent over the photo already sitting there, and a browser that
   cannot decode the video shows the photo with no script involved.
   At z-index 0 as a CHILD it takes exactly the still's place in the paint
   order — ::before, then children, then ::after — so the frost wash and both
   daily skins tint the film the way they tinted the photo.
   brightness(1.2) is copied from ::before so the two layers read the same. */
.dom-bg {
  position: absolute;
  inset: 0;
  z-index: 0;
  width: 100%;
  height: 100%;
  object-fit: cover;
  object-position: center bottom;
  pointer-events: none;
  filter: brightness(1.2);
}
/* the film is over — dissolve into the photo underneath. Transition lives on
   this rule alone, so the fade animates but the next round's reset snaps
   back to frame one (same shape as .jeo-video.is-fading). */
.dom-bg.is-fading { opacity: 0; transition: opacity 1.1s ease; }
/* an opening film behind a timed word game is exactly what this asks about
   — skip straight to the still, which is already sitting underneath */
@media (prefers-reduced-motion: reduce) {
  .dom-bg { display: none; }
}

/* daily-streak skins now tint the wash instead of the felt */
.dom-table.skin-ble::after {
  background: linear-gradient(180deg,
    rgba(199, 226, 254, 0.58) 0%,
    rgba(186, 216, 252, 0.46) 45%,
    rgba(172, 205, 250, 0.36) 100%);
}
.dom-table.skin-drapo::after {
  background: linear-gradient(180deg,
    rgba(0, 32, 159, 0.28) 0%, rgba(0, 32, 159, 0.22) 50%,
    rgba(210, 16, 52, 0.22) 50%, rgba(210, 16, 52, 0.28) 100%);
}
/* empty slots: purple dashes and a frost fill read on the bright sky
   where the old ivory dashes vanish */
.dom-slot {
  border-color: rgba(84, 44, 194, 0.40);
  background: rgba(250, 251, 254, 0.32);
}
.dom-slot.is-next {
  border-color: rgba(236, 130, 20, 0.9);
  box-shadow: 0 0 12px rgba(255, 170, 60, 0.5);
}

/* slots must read over the busy street art: frosted-glass chips */
.dom-slot {
  background: rgba(252, 252, 255, 0.58);
  border-color: rgba(84, 44, 194, 0.55);
  backdrop-filter: blur(3px);
  -webkit-backdrop-filter: blur(3px);
}

/* ================================================================
   MODOMINO CROSSWORD: the chain breaks out of single file. Words lie
   across the table like a real domino game — each new word turns
   perpendicular to the last, and a chained word CROSSES it on the
   shared syllable. Seated words persist; the board grows all session.
   ================================================================ */
.dom-chain {
  --dom-cw: 68px;                /* roomy enough for full-size lettering */
  --dom-ch: 68px;
  display: grid;
  gap: 7px;
  justify-content: center;
  align-content: center;
  transform-origin: center;
}
/* cells are fixed to the grid track so crossings line up exactly */
.dom-slot { width: var(--dom-cw); min-width: 0; height: var(--dom-ch); }
/* letters NEVER shrink — the Scrabble-style Graduate face stays at its
   full size; when the board outgrows the table, the camera pans */
.dom-chain .dom-tile {
  width: 100%;
  min-width: 0;
  height: 100%;
  padding: 0 4px;
}
/* a filled cell is a seated tile, not an open slot: no dashes, no frost */
.dom-slot.is-filled {
  border-color: transparent;
  background: none;
  backdrop-filter: none;
  -webkit-backdrop-filter: none;
}
/* words already on the table settle down — calmer than the live word */
.dom-slot .dom-tile.is-set {
  animation: none;
  box-shadow:
    inset 0 2px 0 rgba(255, 255, 255, 0.9),
    inset 0 -3px 5px rgba(120, 100, 60, 0.35),
    0 4px 8px rgba(0, 0, 0, 0.3);
  filter: saturate(0.85);
}

/* ================================================================
   MODOMINO CAMERA: fit-or-pan. The board shrinks to fit until tiles
   would stop being readable (0.65×); past that the table becomes a
   pannable viewport and the camera follows the word being played.
   ================================================================ */
.dom-viewport {
  /* absolute: detached from layout, so a huge board can NEVER inflate
     the table and push the page off screen — it only pans inside it */
  position: absolute;
  inset: 10px;
  z-index: 1;                 /* above the Okap backdrop + frost wash */
  overflow: auto;
  display: flex;
  overscroll-behavior: contain;   /* pans must not rubber-band the page */
  scrollbar-width: none;
  touch-action: pan-x pan-y;      /* fingers pan the letters layer */
  cursor: grab;
}
.dom-viewport.is-grabbing { cursor: grabbing; }
.dom-viewport::-webkit-scrollbar { display: none; }
/* margin:auto centers a small board AND stays reachable when it
   overflows — flex centering would push edges beyond scroll reach */
.dom-sizer { margin: auto; flex: none; }

/* ================================================================
   END-SCREEN SCROLL: with .dash-panel now min-height:0, the scroll
   screens are properly constrained — but their flex children must
   refuse to shrink, or the cards squash to fit instead of scrolling.
   ================================================================ */
#screen-end > *, #screen-koleksyon > * { flex-shrink: 0; }
@media (max-width: 479px) {
  #screen-home > *, #screen-level > *, #screen-klasman > * { flex-shrink: 0; }
}

/* ================================================================
   KLASMAN DB: the auto-saved score line + rename affordance
   ================================================================ */
.lb-saved {
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 8px;
  font-size: 0.82rem;
  font-weight: 600;
  color: var(--nf-blue);
  margin-bottom: 6px;
}
.lb-saved b { color: var(--nf-purple-solid); }
.lb-change {
  border: none;
  background: none;
  font: inherit;
  font-size: 0.75rem;
  color: rgba(84, 44, 194, 0.75);
  text-decoration: underline;
  cursor: pointer;
  padding: 2px 4px;
}

/* ================================================================
   SETTINGS + PROGRESSION: the cog owns the old level screen; modes
   launch instantly at the earned level; stars track the climb.
   ================================================================ */
.settings-sub {
  font-size: 0.78rem;
  font-weight: 700;
  letter-spacing: 0.06em;
  text-transform: uppercase;
  color: rgba(49, 86, 142, 0.6);
  margin: 10px 0 6px;
  text-align: center;
}
/* the chosen setting wears a ring */
.level-card.is-selected {
  outline: 3px solid rgba(84, 44, 194, 0.75);
  outline-offset: -1px;
  box-shadow: 0 0 14px rgba(84, 44, 194, 0.3);
}
/* home cards wear the level you'll get when you tap */
.mode-card__lvl {
  display: block;
  margin-top: 3px;
  font-size: 0.68rem;
  font-weight: 800;
  color: var(--nf-purple-solid);
}
/* end-screen star track — the loop that pulls the next game */
.prog-card {
  background: linear-gradient(180deg, #fafbfe 0%, #f0edfb 100%);
  border-radius: 18px;
  padding: 12px 16px;
  margin-top: 10px;
  text-align: center;
}
.prog-card__level {
  font-weight: 800;
  font-size: 0.92rem;
  color: var(--nf-blue);
}
.prog-card__stars {
  font-size: 1.5rem;
  letter-spacing: 6px;
  color: #d99a00;
  margin: 2px 0;
}
.prog-card__msg {
  font-size: 0.8rem;
  font-weight: 600;
  color: rgba(49, 86, 142, 0.75);
}
/* NIVO MOUTE — the bell rings */
.prog-card.is-promoted {
  background: linear-gradient(120deg, #ffe9b0 0%, #ffd166 45%, #f8b64c 100%);
  animation: promoPop 0.7s cubic-bezier(0.2, 1.4, 0.4, 1);
}
.prog-card.is-promoted .prog-card__level { color: #7a4a00; }
.prog-card.is-promoted .prog-card__msg { color: rgba(122, 74, 0, 0.85); }
@keyframes promoPop {
  0%   { transform: scale(0.7) rotate(-2deg); opacity: 0; }
  60%  { transform: scale(1.06) rotate(1deg); }
  100% { transform: scale(1) rotate(0); opacity: 1; }
}

/* three icons, three slots, real air between them: cog · sound · music */
#settings-btn { right: 0; }
#sound-btn { right: 52px; }
.music-btn { right: 104px; }

/* ================================================================
   PAUSE + PROMPT LIFT: 🔊/💡 ride the chip row now; ⏸ lives beside
   the back arrow during play and freezes the whole table.
   ================================================================ */
/* the clue buttons sit UP in the meta row, right-aligned */
.dom-listen--meta { margin-left: auto; }
#screen-domino .round-meta { justify-content: flex-start; }
/* pause: left side, next to the back arrow, games only */
#pause-btn { right: auto; left: 44px; }
.pause-overlay {
  position: absolute;
  inset: 0;
  z-index: 40;
  display: flex;
  align-items: center;
  justify-content: center;
  background: rgba(26, 20, 60, 0.45);
  backdrop-filter: blur(6px);
  -webkit-backdrop-filter: blur(6px);
}
.pause-overlay[hidden] { display: none; }
.pause-card {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 14px;
  background: linear-gradient(180deg, #fafbfe 0%, #eee9fa 100%);
  border-radius: 22px;
  padding: 26px 34px;
  box-shadow: 0 18px 44px rgba(26, 20, 60, 0.4);
}
.pause-title {
  font-family: 'Graduate', Georgia, serif;
  font-style: italic;
  font-weight: 600;
  font-size: 1.4rem;
  color: var(--nf-purple-solid);
}
.pause-quit {
  border: none;
  background: none;
  font: inherit;
  font-size: 0.85rem;
  font-weight: 600;
  color: rgba(49, 86, 142, 0.65);
  text-decoration: underline;
  cursor: pointer;
}
/* ================================================================
   ZÒRÈY FEN: two big spellings a hair apart, one draining bar, and
   your ear on the clock. Tiles wear the same ivory as the domino.
   ================================================================ */

.zy-tile.is-wrong {
  animation: wrongPulse 0.4s ease;
  box-shadow:
    inset 0 2px 0 rgba(255, 255, 255, 0.9),
    0 0 0 3px rgba(229, 72, 77, 0.85),
    0 8px 16px rgba(0, 0, 0, 0.25);
}
/* ================================================================
   ZÒRÈY FEN → OKAP SKY: the listening stage plays over the same
   brightened Cap-Haïtien backdrop as MoDomino, under the frost wash.
   ================================================================ */


/* ================================================================
   HOME FITS THE PHONE: six cards now — the menu compresses so every
   card is on screen without scrolling; the hero yields on short
   viewports and scroll remains only as a last-resort safety net.
   ================================================================ */
.home-hero { height: 84px; }
.home-hero img { height: 100%; width: 100%; object-fit: cover; }
.page-title { font-size: 22px; margin: 4px 0 2px; }
.mode-cards { gap: 8px; }
.mode-card { padding: 9px 12px; }
.mode-card__glyph { font-size: 1.4rem; }
.mode-card__name { font-size: 0.95rem; }
.mode-card__desc {
  font-size: 0.67rem;
  line-height: 1.25;
  display: -webkit-box;
  -webkit-line-clamp: 2;
  -webkit-box-orient: vertical;
  overflow: hidden;
}
.mode-card__lvl { margin-top: 2px; font-size: 0.64rem; }
.home-links { margin-top: 8px; }

/* short phones: the hero steps aside, the cards ARE the home */
@media (max-height: 760px) {
  .home-hero { display: none; }
  .page-title { margin-top: 0; }
}

/* the brand mark tucks in: it was the last 40px hanging off the frame */
.mode-cards { gap: 7px; }

/* final squeeze: everything sits inside the frame with room to breathe */
.home-hero { height: 64px; }
.mode-card { padding: 8px 12px; }
.mode-cards { gap: 6px; }
.home-links { margin-top: 6px; }

/* SE-class phones (≤700px tall): one-line descriptions, tightest chrome —
   six cards AND the links row on screen; only the brand mark may scroll */
@media (max-height: 700px) {
  .page-title { font-size: 19px; margin: 0 0 2px; }
  .mode-card { padding: 7px 12px; }
  .mode-cards { gap: 5px; }
  .mode-card__desc { -webkit-line-clamp: 1; }
  .mode-card__glyph { font-size: 1.2rem; }
  .home-links { margin-top: 5px; }
}

/* ================================================================
   HOME POLISH: the level rides inline after the title, the Palefò
   mark breathes again, and the first card hugs the app title.
   ================================================================ */
.mode-card__lvl {
  display: inline;
  margin: 0 0 0 8px;
  font-size: 0.62rem;
  font-weight: 800;
  color: var(--nf-purple-solid);
  vertical-align: 1px;
  white-space: nowrap;
}
/* the brand mark un-squished: badges left, so the room came back */
/* the first card sits right under the title */
.page-title { margin: 2px 0 4px; }
.mode-cards { margin-top: 0; }

/* ================================================================
   HOME BALANCE: card titles grow, the six cards spread to drink the
   dead space, and the bottom pills stack icon-over-one-line-label.
   ================================================================ */
.mode-card__name { font-size: 1.08rem; }
/* the cards column flexes: leftover height becomes even breathing
   room between cards instead of a void under the links */
.mode-cards {
  flex: 1 1 auto;
  min-height: 0;
  display: flex;
  flex-direction: column;
  justify-content: space-evenly;
}
.mode-card { flex-shrink: 0; }
/* pills: icon on top, label on ONE line */
.klasman-link {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 2px;
}
.klasman-link__ico { font-size: 1.1rem; line-height: 1; }
.klasman-link__txt { white-space: nowrap; }
/* stats + brand hug the bottom edge */
.home-known { margin-top: auto; }

/* ================================================================
   HERO RESTORED: the two stat lines under the pills are remmed out
   (display:none — their JS keeps updating them silently) and the
   height flows back into the hero, Citadelle and the girl in frame.
   ================================================================ */
#home-known, #home-best { display: none; }
.home-hero { height: 128px; }   /* back to the FotoLekti spec */

/* ================================================================
   CARD RHYTHM: the six cards pack tighter and the description text
   steps up — the space freed by the gaps feeds the words. Hero and
   Palefò mark untouched; the brand stays pinned to the bottom edge.
   ================================================================ */
.mode-cards { justify-content: flex-start; gap: 6px; flex: 0 0 auto; }
.mode-card__desc { font-size: 0.8rem; line-height: 1.3; }
.home-links { margin-top: 10px; }

/* ================================================================
   MODOMINO TILES: lighter yellow faces, bigger letters. Cells widen
   to 72px so long syllables keep their room at the new size.
   ================================================================ */
.dom-tile {
  background: linear-gradient(160deg, #fffdf0 0%, #fff6c8 55%, #ffe9a4 100%);
  font-size: 1.22rem;
}
.dom-chain { --dom-cw: 72px; }

/* ================================================================
   JEOKAT: Ayiti as a table of cards. Ten department cards hold their
   city chips; Bòs Sonson's taptap wears the destination on its plak;
   three itineraries wait below. The globe zooms down to open a game.
   And the home menu becomes scrollable — seven cards now.
   ================================================================ */
.mode-cards {
  flex: 1 1 auto;
  min-height: 0;
  overflow-y: auto;
  scrollbar-width: none;
}
.mode-cards::-webkit-scrollbar { display: none; }
.jeo-route.is-wrong {
  animation: wrongPulse 0.4s ease;
  box-shadow: 0 0 0 2px rgba(229, 72, 77, 0.85), 0 4px 10px rgba(60, 30, 100, 0.18);
}
#screen-jeokat { position: relative; }
#screen-jeokat.is-landing .jeo-map {
  animation: jeoLand 1.4s 1.2s cubic-bezier(0.2, 1.25, 0.4, 1) both;
}
@keyframes jeoLand {
  0%   { transform: scale(0.12); opacity: 0; }
  70%  { transform: scale(1.05); opacity: 1; }
  100% { transform: scale(1); }
}
#screen-jeokat.is-landing::after {
  content: 'AYITI 🇭🇹';
  position: absolute;
  left: 50%; top: 38%;
  transform: translate(-50%, -50%);
  z-index: 31;
  font-family: 'Graduate', serif;
  font-size: 2.2rem;
  letter-spacing: 0.12em;
  color: #fff;
  text-shadow: 0 3px 10px rgba(0, 0, 0, 0.6), 0 0 24px rgba(255, 209, 102, 0.8);
  animation: jeoStamp 1.2s 1.5s cubic-bezier(0.2, 1.5, 0.4, 1) both;
  pointer-events: none;
}
@keyframes jeoStamp {
  0%   { transform: translate(-50%, -50%) scale(3); opacity: 0; }
  35%  { transform: translate(-50%, -50%) scale(1); opacity: 1; }
  75%  { opacity: 1; }
  100% { transform: translate(-50%, -50%) scale(0.92); opacity: 0; }
}

/* ================================================================
   JEOKAT ON THE REAL MAP: map.png fills the stage, city markers sit
   at true positions, the camera dives onto the origin (name shown),
   and Bòs Sonson fades in with street sign + destination bus header.
   ================================================================ */
.jeo-stage {
  flex: 1 1 auto;
  min-height: 0;
  position: relative;
  overflow: hidden;
  margin: 8px 4px 0;
  border-radius: 14px;
  background: #2e9db3;              /* the sea behind the letterbox */
}
/* the landing animation now targets the stage */
#screen-jeokat.is-landing .jeo-stage {
  animation: jeoLand 1.4s 1.2s cubic-bezier(0.2, 1.25, 0.4, 1) both;
}
/* ================================================================
   JEOKAT SCENE: Bòs Sonson stands center between the ORIGIN signpost
   and the taptap whose destination board rides its roof.
   ================================================================ */

/* ================================================================
   JEOKAT ONE-TAKE OPENING: the overlay hands off to the map early so
   globe → Caribbean → Ayiti → city reads as one uncut zoom; then the
   itineraries rise only after Sonson's image is on stage.
   ================================================================ */

/* the world itself is the camera now — no second stage animation */
#screen-jeokat.is-landing .jeo-stage { animation: none; }
#screen-jeokat.is-landing::after { animation-delay: 1.8s; }

/* build stamp at the foot of Paramèt */
.settings-build {
  margin-top: 10px;
  text-align: center;
  font-size: 0.62rem;
  font-weight: 600;
  color: rgba(49, 86, 142, 0.45);
}
/* ================================================================
   JEOKAT SCENE TAKEOVER: at the end of the zoom Bòs Sonson's picture
   REPLACES the map — full-frame in the stage, origin sign bottom-left,
   destination bus board bottom-right — until the answer brings the
   map back to show the ride.
   ================================================================ */


/* the AYITI stamp retires from the opening animation */
#screen-jeokat.is-landing::after { content: none; animation: none; }
/* ================================================================
   JEOKAT ITINERARIES wear MoDomino's face: butter-yellow ivory and
   the Graduate letterface — one tile language across the suite.
   ================================================================ */

.jeo-route.is-wrong {
  box-shadow:
    inset 0 2px 0 rgba(255, 255, 255, 0.9),
    inset 0 -3px 5px rgba(120, 100, 60, 0.35),
    0 0 0 3px rgba(229, 72, 77, 0.85),
    0 5px 10px rgba(0, 0, 0, 0.25);
}
/* ================================================================
   THE CARIBBEAN LAYER: between globe and Ayiti the camera plunges
   through the islands — Cuba, Jamaica, Hispaniola glowing warm —
   zooming toward Hispaniola as it dissolves into the Haiti map.
   ================================================================ */

/* the globe hands off earlier so the Caribbean gets its moment */
/* ================================================================
   ONE-CAMERA OPENING, rebuilt: the drawn globe and the Caribbean are
   sibling layers of the map, all pushed by a single zoom value in JS.
   The is-on class shows a layer; JS owns transform and opacity.
   ================================================================ */

.jeo-glob.is-on { display: block; }
.jeo-karayib.is-on { display: block; }

/* ================================================================
   THE FILM: zoom_in_on_Haiti.mp4 owns the descent, then dissolves
   into the living map. A tap skips it.
   ================================================================ */
.jeo-video {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  object-fit: cover;
  z-index: 6;
  display: none;
  background: #0b1230;
  cursor: pointer;
}
.jeo-video.is-on { display: block; }
.jeo-video.is-fading { opacity: 0; transition: opacity 1s ease; }
/* ================================================================
   MÒD RANVÈSE: on clear, the letters French ADDS are born into the
   sign with a pop — the word completes before your eyes.
   ================================================================ */

.ghost-chip.is-wrong {
  background: linear-gradient(150deg, #ffb4a6 0%, #f4796a 100%);
  animation: wrongPulse 0.4s ease;
}

/* ================================================================
   PARAMÈT — the per-game level board: each game gets its own row
   with its earned climb and its own difficulty selector
   ================================================================ */
.game-levels {
  margin-top: 14px;
  display: flex;
  flex-direction: column;
  gap: 10px;
  width: min(560px, 100%);
  align-self: center;
}
.glv-row {
  background: var(--nf-card);
  border: 1px solid var(--nf-frame-stroke);
  border-radius: 14px;
  padding: 10px 12px;
  box-shadow: 0 2px 10px rgba(60, 30, 100, 0.06);
}
.glv-head {
  display: flex;
  justify-content: space-between;
  align-items: baseline;
  gap: 10px;
}
.glv-head b {
  font-family: 'Graduate', Georgia, serif;
  font-style: italic;
  font-weight: 600;
  font-size: 1.02rem;
  color: var(--nf-purple-solid);
}
.glv-earned {
  font-size: 0.78rem;
  color: rgba(49, 86, 142, 0.72);
  white-space: nowrap;
}
.glv-earned i { font-style: normal; color: #d99a00; letter-spacing: 1px; }
.glv-seg {
  display: flex;
  flex-wrap: wrap;
  gap: 6px;
  margin-top: 8px;
}
.glv-btn {
  flex: 1 1 auto;
  padding: 7px 8px;
  border: 1px solid var(--nf-frame-stroke);
  border-radius: 999px;
  background: rgba(252, 252, 255, 0.7);
  font-family: inherit;
  font-size: 0.78rem;
  font-weight: 700;
  color: var(--nf-blue);
  cursor: pointer;
  transition: transform 0.15s ease, background 0.15s ease, color 0.15s ease;
}
.glv-btn:active { transform: scale(0.95); }
.glv-btn.is-selected {
  background: var(--nf-purple-solid);
  border-color: var(--nf-purple-solid);
  color: #fff;
  box-shadow: 0 3px 10px rgba(84, 44, 194, 0.35);
}

/* ================================================================
   KOUTKAT — Twa Sèt against Tonton Chal
   A porch table, not a casino: worn felt, wooden rails, ivory cards
   with rank glyphs oversized well past what looks right on a desktop
   mockup, because this is read at arm's length in the sun.
   ================================================================ */
/* NOTE: never set `display` on the bare id — an id beats `.screen{display:none}`
   on specificity and the table renders under the home menu forever. Layout
   goes on .is-active, the same hook every other screen shows itself with. */
#screen-koutkat {
  position: relative;                 /* the declare + win overlays anchor here */
  padding: 6px 10px 10px;
  overflow: hidden;
}
#screen-koutkat.is-active {
  display: flex;
  flex-direction: column;
  gap: 8px;
  height: 100%;
}
#screen-koutkat .round-meta { position: relative; }
#screen-koutkat.is-hot .kk-heat__fill { animation: kkHeatPulse 0.7s ease-in-out infinite; }
@keyframes kkHeatPulse {
  0%, 100% { filter: brightness(1); }
  50%      { filter: brightness(1.4); }
}
#screen-koutkat.is-hot .kk-table {
  border-color: var(--ember);
  box-shadow: inset 0 3px 14px rgba(0, 0, 0, 0.42), 0 0 18px rgba(255, 122, 69, 0.55);
}
.kk-btn {
  padding: 9px 14px;
  border: 1px solid var(--nf-frame-stroke);
  border-radius: 12px;
  background: var(--nf-card);
  color: var(--nf-purple-solid);
  font-size: 0.82rem;
  font-weight: 700;
  cursor: pointer;
}
.kk-btn:disabled { opacity: 0.4; cursor: default; }
.kk-hands.is-on { background: var(--nf-purple-solid); color: #fff; border-color: transparent; }
.kk-share {
  display: block;
  width: 100%;
  margin: 12px 0 6px;
  padding: 13px;
  border: none;
  border-radius: 14px;
  background: #25d366;                /* WhatsApp green: say where it goes */
  color: #06331a;
  font-size: 0.95rem;
  font-weight: 800;
  cursor: pointer;
  box-shadow: 0 6px 16px rgba(37, 211, 102, 0.4);
}
.kk-share:disabled { opacity: 0.6; }

.kk-ghost {
  width: 100%;
  padding: 9px;
  border: 1px solid var(--nf-frame-stroke);
  border-radius: 12px;
  background: transparent;
  color: var(--nf-blue);
  font-size: 0.82rem;
  font-weight: 700;
  cursor: pointer;
}

/* ================================================================
   KATKAT — nine cards, five seconds, three names
   Same porch table as KoutKat: worn felt, wooden rail, ivory faces.
   The rank glyph is deliberately oversized — this is read at arm's
   length, and for five seconds only.
   ================================================================ */
#screen-katkat {
  position: relative;                 /* the win overlay anchors here */
  padding: 6px 10px 10px;
  overflow: hidden;
}
#screen-katkat.is-active {
  display: flex;
  flex-direction: column;
  gap: 4px;                           /* every px here is a px off the cards */
  height: 100%;
}
.kat-slot.is-next { border-color: var(--nf-purple-solid); background: rgba(84, 44, 194, 0.16); }
#screen-katkat.is-hot .kat-board {
  border-color: var(--ember);
  box-shadow: inset 0 3px 14px rgba(0, 0, 0, 0.42), 0 0 18px rgba(255, 122, 69, 0.55);
}
.kat-card.is-wrong .kat-card__front { box-shadow: inset 0 0 0 2px var(--ember-deep), 0 2px 5px rgba(0, 0, 0, 0.35); }
.kat-scorefx__pips i.is-miss { background: rgba(255, 255, 255, 0.22); }
@keyframes katPipIn {
  from { opacity: 0; transform: scale(0); }
  to   { opacity: 1; transform: scale(1); }
}
/* KatKat runs tight: the feedback line and the bubble give the cards room */
#screen-katkat .play-feedback { margin: 0; font-size: 0.78rem; line-height: 1.25; }
#screen-katkat .kk-bubble { font-size: 0.74rem; }

/* KatKat's controls run a quarter smaller than KoutKat's: every pixel the
   buttons give back becomes card, since the board is sized off the height
   that is left over. */
#screen-katkat .kk-controls { gap: 11px; }
#screen-katkat .kk-btn {
  padding: 7px 11px;
  border-radius: 10px;
  font-size: 0.62rem;
}
#screen-katkat .kk-mic { width: 51px; height: 51px; }
#screen-katkat .kk-mic .mic-disc svg { width: 24px; height: 24px; }
#screen-katkat .kk-hands { padding: 3px 7px; font-size: 0.6rem; }

/* opponent: 'none' — the porch is empty and the cards get his two rows */
#screen-katkat.no-chal #kat-chal,
#screen-katkat.no-chal #kat-bubble { display: none; }
#screen-katkat .kat-lives { font-size: 0.78rem; letter-spacing: 0; }

/* ---- Paramèt tabs ---- */
.set-tabs {
  display: flex;
  gap: 6px;
  width: min(560px, 100%);
  align-self: center;
  margin-bottom: 10px;
}
.set-tab {
  flex: 1;
  padding: 8px 6px;
  border: 1px solid var(--nf-frame-stroke);
  border-radius: 11px;
  background: transparent;
  color: var(--nf-blue);
  font-size: 0.8rem;
  font-weight: 700;
  cursor: pointer;
  transition: background 0.18s ease, color 0.18s ease;
}
.set-tab.is-on {
  background: var(--nf-purple-solid);
  border-color: transparent;
  color: #fff;
  box-shadow: 0 4px 12px rgba(84, 44, 194, 0.28);
}
.set-pane { display: none; width: 100%; }
.set-pane.is-on { display: flex; flex-direction: column; align-items: center; }
.set-note {
  margin: 6px 0 0;
  font-size: 0.72rem;
  color: var(--nf-blue);
  opacity: 0.75;
  text-align: center;
}
/* the retry: biggest, first, no menu in the way */
.kat-again {
  display: block;
  width: 100%;
  margin-bottom: 8px;
  padding: 13px;
  border: none;
  border-radius: 14px;
  background: var(--nf-grad);
  color: #fff;
  font-size: 0.98rem;
  font-weight: 800;
  cursor: pointer;
  box-shadow: 0 6px 16px rgba(84, 44, 194, 0.4);
}
/* KatKat's feedback line holds a fixed two lines: a message that grows or
   shrinks would change how much room the board thinks it has */
#screen-katkat .play-feedback {
  min-height: 2.2em;
  display: flex;
  align-items: center;
  justify-content: center;
}

/* ================================================================
   JWÈTKONÈK — the lobby, the race rail, the taunts
   ================================================================ */
#screen-konek { align-items: center; gap: 10px; padding: 0 12px 12px; overflow-y: auto; }
.konek-you, .konek-actions, .konek-room {
  width: min(430px, 100%);
  display: flex;
  flex-direction: column;
  gap: 8px;
}
.konek-field { display: flex; flex-direction: column; gap: 4px; }
.konek-field span { font-size: 0.7rem; font-weight: 700; color: var(--nf-blue); }
#konek-name, #konek-code {
  padding: 10px 12px;
  border: 1px solid var(--nf-frame-stroke);
  border-radius: 12px;
  background: var(--nf-card);
  font-family: inherit;
  font-size: 0.95rem;
  color: var(--nf-blue);
}
#konek-code { text-transform: uppercase; letter-spacing: 0.18em; text-align: center; }
.konek-teams { display: flex; gap: 6px; }
.konek-team {
  flex: 1;
  padding: 8px 4px;
  border: 1px solid var(--nf-frame-stroke);
  border-radius: 11px;
  background: transparent;
  color: var(--nf-blue);
  font-size: 0.72rem;
  font-weight: 700;
  cursor: pointer;
}
.konek-team.is-on { background: var(--nf-purple-solid); border-color: transparent; color: #fff; }
.konek-team.is-a.is-on { background: #2f7fb4; }
.konek-team.is-b.is-on { background: #b4562f; }
.konek-joinrow { display: flex; gap: 6px; }
.konek-joinrow input { flex: 1; }

.konek-room {
  padding: 12px;
  border-radius: 16px;
  background: var(--nf-card);
  border: 1px solid var(--nf-frame-stroke);
}
.konek-codeline { margin: 0 0 8px; font-size: 0.82rem; color: var(--nf-blue); text-align: center; }
.konek-codeline b {
  font-family: 'Graduate', Georgia, serif;
  font-size: 1.3rem;
  letter-spacing: 0.16em;
  color: var(--nf-purple-solid);
}
/* the connection, honestly reported */
.konek-dot {
  display: inline-block;
  width: 9px; height: 9px;
  border-radius: 50%;
  margin-left: 6px;
  background: #bbb;
}
.konek-dot.is-on { background: var(--hit-green); box-shadow: 0 0 7px rgba(47, 180, 124, 0.9); }
.konek-dot.is-connecting { background: #ffc46b; animation: katPipIn 0.8s ease-in-out infinite alternate; }
.konek-dot.is-error, .konek-dot.is-off { background: var(--ember-deep); }

.konek-roster { list-style: none; margin: 0 0 10px; padding: 0; display: flex; flex-direction: column; gap: 5px; }
.konek-row {
  display: flex; align-items: center; gap: 7px;
  padding: 6px 9px;
  border-radius: 10px;
  background: var(--nf-disc);
  font-size: 0.82rem;
  color: var(--nf-blue);
}
.konek-row.is-me { box-shadow: inset 0 0 0 1.5px var(--nf-stroke-50); }
.konek-chip {
  min-width: 22px;
  padding: 1px 5px;
  border-radius: 6px;
  background: rgba(84, 44, 194, 0.15);
  font-size: 0.66rem;
  font-weight: 800;
  text-align: center;
}
.konek-chip.is-team-a { background: #2f7fb4; color: #fff; }
.konek-chip.is-team-b { background: #b4562f; color: #fff; }

/* ---- the race, live on the table ---- */
.konek-rail {
  width: 100%;
  display: flex;
  flex-direction: column;
  gap: 4px;
  margin-bottom: 4px;
}
.konek-lane { display: flex; align-items: center; gap: 6px; font-size: 0.68rem; color: var(--nf-blue); }
.konek-lane__name { width: 68px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.konek-lane.is-me .konek-lane__name { font-weight: 800; color: var(--nf-purple-solid); }
.konek-lane__track {
  position: relative;
  flex: 1;
  height: 10px;
  border-radius: 99px;
  background: rgba(84, 44, 194, 0.12);
  overflow: visible;
}
.konek-lane__track i {
  display: block;
  height: 100%;
  border-radius: 99px;
  background: var(--nf-grad);
  transition: width 0.4s ease;
}
.konek-lane.is-team-a .konek-lane__track i { background: linear-gradient(90deg, #6fb3e0, #2f7fb4); }
.konek-lane.is-team-b .konek-lane__track i { background: linear-gradient(90deg, #e0a06f, #b4562f); }
.konek-lane__tile {
  position: absolute;
  top: -5px;
  transform: translateX(-50%);
  font-size: 0.85rem;
  transition: left 0.4s ease;
}
.konek-lane__score { width: 34px; text-align: right; font-weight: 800; }

/* ---- taunts: your voice, out of their phone ---- */
.konek-taunts { display: flex; justify-content: center; gap: 6px; margin-top: 2px; }
.konek-taunt {
  width: 34px; height: 34px;
  border: 1px solid var(--nf-frame-stroke);
  border-radius: 50%;
  background: var(--nf-card);
  font-size: 1rem;
  cursor: pointer;
  transition: transform 0.15s ease;
}
.konek-taunt:hover { transform: scale(1.12); }
.konek-taunt.is-sent { background: var(--nf-purple-solid); transform: scale(0.92); }

/* ---- Yon Sèl Tab: one chain, two live tips ------------------------------
   The tips are the whole game — a player must be able to find, at a glance
   and at arm's length, which end of the chain is theirs. Mine glows gold
   like the carry tile it will become; theirs sits blue and quiet. */
.dom-slot.is-end::after {
  content: attr(data-end);
  position: absolute;
  top: -9px;
  left: 50%;
  transform: translateX(-50%);
  font: 700 0.56rem/1 "Barlow Condensed", system-ui, sans-serif;
  letter-spacing: 0.08em;
  padding: 2px 6px;
  border-radius: 999px;
  white-space: nowrap;
  pointer-events: none;
  z-index: 3;
}
.dom-slot.is-end-mine::after {
  content: attr(data-end) " ← OU";
  background: #ffd166;
  color: #2a2418;
  box-shadow: 0 2px 6px rgba(0, 0, 0, 0.35);
}
.dom-slot.is-end-theirs::after {
  background: rgba(70, 130, 190, 0.95);
  color: #f7fbff;
  box-shadow: 0 2px 6px rgba(0, 0, 0, 0.3);
}
.dom-slot.is-end-mine .dom-tile { box-shadow:
  inset 0 2px 0 rgba(255, 255, 255, 0.9),
  0 0 0 3px rgba(255, 209, 102, 0.95),
  0 6px 12px rgba(0, 0, 0, 0.35); }
.dom-slot.is-end-mine { animation: tabTip 1.5s ease-in-out infinite; }
@keyframes tabTip {
  0%, 100% { transform: scale(1); }
  50% { transform: scale(1.05); }
}
/* somebody else's tile: cooler, so the chain reads as two hands building it */
.dom-slot .dom-tile.is-peer {
  background: linear-gradient(180deg, #eaf2fb 0%, #cfe0f2 100%);
  color: #1d3350;
}
.konek-tab { margin-top: 8px; }
@media (prefers-reduced-motion: reduce) {
  .dom-slot.is-end-mine { animation: none; }
}


/* ================================================================
   MODOMINO v4: the hand you keep — pyoche, pase, hold-to-hear,
   and a syllable silo that arrives whole with the definition.
   ================================================================ */

/* ---- the two verbs: draw, or give the word away ---- */
.dom-actions {
  flex-shrink: 0;
  display: flex;
  justify-content: center;
  gap: 10px;
  margin-top: 8px;
}
.dom-act {
  display: inline-flex;
  align-items: center;
  gap: 7px;
  padding: 8px 15px;
  border: none;
  border-radius: 999px;
  font: 700 0.82rem/1 'Graduate', Georgia, serif;
  letter-spacing: 0.01em;
  color: #2a2418;
  background: linear-gradient(160deg, #fffdf0 0%, #f4ecd2 60%, #ddd2b0 100%);
  box-shadow:
    inset 0 2px 0 rgba(255, 255, 255, 0.9),
    inset 0 -2px 4px rgba(120, 100, 60, 0.3),
    0 4px 9px rgba(0, 0, 0, 0.3);
  cursor: pointer;
  -webkit-tap-highlight-color: transparent;
  transition: transform 0.14s ease, opacity 0.2s ease, box-shadow 0.2s ease;
}
.dom-act:hover:not(:disabled) { transform: translateY(-2px); }
.dom-act:active:not(:disabled) { transform: translateY(1px) scale(0.97); }
.dom-act:disabled { opacity: 0.38; cursor: default; }
.dom-act__ico { font-size: 1.05rem; line-height: 1; }
.dom-act__n {
  min-width: 20px;
  padding: 2px 6px;
  border-radius: 999px;
  background: rgba(42, 36, 24, 0.85);
  color: #ffd166;
  font-size: 0.74rem;
  text-align: center;
}
.dom-act--pass {
  background: linear-gradient(160deg, #4a4033 0%, #322a20 100%);
  color: #f4ecd2;
  box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.16), 0 4px 9px rgba(0, 0, 0, 0.35);
}

/* the tile you just drew announces itself once, then joins the hand */
.dom-tile.is-drawn { animation: tileDrawn 1.1s ease-out; }
@keyframes tileDrawn {
  0%   { transform: translateY(14px) scale(0.8); opacity: 0; }
  40%  { transform: translateY(-5px) scale(1.06); opacity: 1;
         box-shadow: 0 0 0 3px rgba(255, 209, 102, 0.9), 0 10px 16px rgba(0, 0, 0, 0.4); }
  100% { transform: none; }
}

/* ---- press-and-hold: weigh the bone, hear the syllable ---- */
.dom-tile { -webkit-touch-callout: none; touch-action: manipulation; }
.dom-hand .dom-tile.is-held { transform: translateY(-8px) scale(1.08); }
.dom-hand .dom-tile.is-preview {
  box-shadow:
    inset 0 2px 0 rgba(255, 255, 255, 0.9),
    0 0 0 3px rgba(95, 165, 250, 0.95),
    0 14px 20px rgba(0, 0, 0, 0.42);
}
.dom-hand .dom-tile.is-preview::after {
  content: '\1F50A';
  position: absolute;
  top: -9px;
  right: -6px;
  font-size: 0.7rem;
}
.dom-hand .dom-tile { position: relative; }
/* focus has to be loud here — the hand is now fully keyboard-playable */
.dom-tile:focus-visible {
  outline: none;
  box-shadow:
    inset 0 2px 0 rgba(255, 255, 255, 0.9),
    0 0 0 3px #ffd166,
    0 0 0 6px rgba(42, 36, 24, 0.8);
}

/* ---- the silo: every empty slot of the word arrives while Keke reads
   the definition, cascading left to right. You see the SHAPE of the word
   before you hear a thing about it. ---- */
.dom-slot.is-silo {
  animation: siloDrop 0.42s cubic-bezier(0.2, 1.35, 0.4, 1) backwards;
  animation-delay: calc(var(--i, 0) * 70ms + 120ms);
}
@keyframes siloDrop {
  0%   { opacity: 0; transform: translateY(-16px) scale(0.72); }
  70%  { opacity: 1; transform: translateY(2px) scale(1.04); }
  100% { opacity: 1; transform: none; }
}
/* letters ride the cell size down when a long word has to fit whole */
.dom-chain .dom-tile { font-size: var(--dom-fs, 1.22rem); }

@media (prefers-reduced-motion: reduce) {
  .dom-slot.is-silo { animation: none; }
  .dom-tile.is-drawn { animation: none; }
  .dom-hand .dom-tile.is-held { transform: none; }
}

/* Seven tiles, not three-plus-chaff: the hand is compacted so a full rack
   sits on one or two rows and the felt keeps the height it had. */
.dom-hand { max-height: 148px; overflow-y: auto; align-content: flex-start; }
.dom-hand .dom-tile {
  height: 58px;                /* unscoped: a folding tile must not resize the row */
  font-size: 1.06rem;
}
/* :not(.is-used) rather than a higher-specificity override — the fold has to
   be able to take min-width and padding to zero, and a plain `.dom-hand
   .dom-tile` rule beats `.dom-tile.is-used` on source order alone. */
.dom-hand .dom-tile:not(.is-used) {
  min-width: 46px;
  padding: 0 7px;
}

/* the pile is shut and the tile is not yours: pase is the only move left */
.dom-act--pass.is-urgent {
  background: linear-gradient(160deg, #b3341f 0%, #7d2214 100%);
  color: #fff6e2;
  animation: pasePulse 1.5s ease-in-out infinite;
}
@keyframes pasePulse {
  0%, 100% { box-shadow: 0 0 0 0 rgba(210, 16, 52, 0.55), 0 4px 9px rgba(0,0,0,.35); }
  50%      { box-shadow: 0 0 0 7px rgba(210, 16, 52, 0), 0 4px 9px rgba(0,0,0,.35); }
}
@media (prefers-reduced-motion: reduce) { .dom-act--pass.is-urgent { animation: none; } }

/* A hand grown by draws steps its tiles down instead of hiding the last
   row: the tile you just paid a pyòch for must never be below the fold. */
.dom-hand { max-height: 172px; }
.dom-hand.is-tight .dom-tile {
  height: 48px;
  font-size: 0.94rem;
}
.dom-hand.is-tight .dom-tile:not(.is-used) {
  min-width: 38px;
  padding: 0 5px;
}
/* …and it announces itself long enough to actually be found */
.dom-tile.is-drawn { animation: tileDrawn 1.9s ease-out; z-index: 2; }
@keyframes tileDrawn {
  0%   { transform: translateY(16px) scale(0.78); opacity: 0; }
  18%  { transform: translateY(-6px) scale(1.08); opacity: 1;
         box-shadow: 0 0 0 4px rgba(255, 209, 102, 1), 0 12px 18px rgba(0, 0, 0, .45); }
  72%  { transform: none;
         box-shadow: 0 0 0 4px rgba(255, 209, 102, .95), 0 8px 14px rgba(0, 0, 0, .4); }
  100% { transform: none; }
}
@media (prefers-reduced-motion: reduce) {
  .dom-tile.is-drawn { animation: none; box-shadow: 0 0 0 4px rgba(255, 209, 102, .95); }
}

/* ================================================================
   BOS DJO ON YOUR TABLE: he lays on the same chain you do, so his
   bones have to be legible as HIS at a glance — old, dark, worn
   smooth, against your butter-yellow ivory.
   ================================================================ */
.dom-slot .dom-tile.is-djo {
  background: linear-gradient(160deg, #e8503f 0%, #c62828 55%, #8e1512 100%);
  color: #fff6e2;
  text-shadow: 0 1px 1px rgba(0, 0, 0, 0.35);
  box-shadow:
    inset 0 2px 0 rgba(255, 255, 255, 0.3),
    inset 0 -3px 5px rgba(0, 0, 0, 0.4),
    0 6px 10px rgba(0, 0, 0, 0.42);
}
/* while he plays, the felt leans his way and your empty sockets rest */
.dom-chain.is-djo-turn .dom-slot:not(.is-filled) {
  border-color: rgba(255, 209, 102, 0.22);
  box-shadow: none;
  opacity: 0.55;
}
.djo-chip { transition: transform 0.18s ease; }
.dom-chain.is-djo-turn ~ * .djo-chip,
.djo-chip.is-live { transform: scale(1.08); }

/* Build stamp, bottom-right of the phone canvas. Sits on .device-frame
   rather than inside .dash-panel so the panel's overflow: clip cannot eat
   it, and stays out of the tab order — it is a label, not a control. */
.app-ver {
  position: absolute;
  right: 12px;
  bottom: 10px;
  z-index: 30;
  pointer-events: none;
  user-select: none;
  font: 600 0.66rem/1 'Graduate', Georgia, serif;
  letter-spacing: 0.04em;
  color: rgba(49, 86, 142, 0.42);
}
.app-ver[hidden] { display: none; }

/* Hover-to-hear reaches the felt too. The hand's preview ring is scoped to
   .dom-hand, and a laid tile needs its own — same blue, no 🔊 badge, because
   a board cell is often scaled down and would clip it. */
.dom-chain .dom-tile.is-preview {
  box-shadow:
    inset 0 2px 0 rgba(255, 255, 255, 0.9),
    0 0 0 3px rgba(95, 165, 250, 0.95),
    0 8px 14px rgba(0, 0, 0, 0.4);
}

/* ================================================================
   THE DAILY, ON THE FLAGSHIP CARD
   Mo Jodi a is the reason to come back tomorrow and it was a link in a
   row of three at the bottom of the home screen. It now rides directly
   under the MoDomino card, tucked up against it so the two read as one
   block rather than a card and an afterthought.
   ================================================================ */
.mode-card--daily { border-bottom-left-radius: 6px; border-bottom-right-radius: 6px; }
.daily-strip {
  display: flex;
  align-items: center;
  gap: 8px;
  width: 100%;
  margin: -2px 0 10px;
  padding: 8px 14px;
  border: none;
  border-radius: 6px 6px 16px 16px;
  background: linear-gradient(180deg, rgba(84, 44, 194, 0.13), rgba(84, 44, 194, 0.07));
  color: var(--nf-purple-solid, #542cc2);
  font: 700 0.78rem/1.2 'Graduate', Georgia, serif;
  text-align: left;
  cursor: pointer;
  -webkit-tap-highlight-color: transparent;
  transition: background 0.18s ease, transform 0.14s ease;
}
.daily-strip:hover { background: linear-gradient(180deg, rgba(84,44,194,.2), rgba(84,44,194,.11)); }
.daily-strip:active { transform: scale(0.985); }
.daily-strip__ico { font-size: 0.95rem; }
.daily-strip__txt { flex: 1 1 auto; }
.daily-strip__state {
  flex: 0 0 auto;
  padding: 3px 9px;
  border-radius: 999px;
  background: rgba(84, 44, 194, 0.16);
  font-size: 0.72rem;
  letter-spacing: 0.01em;
}
/* played today: the strip goes quiet rather than nagging */
.daily-strip.is-done { opacity: 0.72; }
.daily-strip.is-done .daily-strip__state {
  background: rgba(61, 220, 132, 0.25);
  color: #17703f;
}

/* the clip button sits with the other share actions */
.son-btn { margin-top: 8px; }
.son-btn[disabled] { opacity: 0.6; cursor: default; }


/* ================================================================
   THE MARK: Techtra Labs, pinned to the bottom edge. Replaces the
   Palefò mark and the ten scattered overrides it had accumulated —
   one rule now, since this app owns its own chrome.

   techtralabs-logo.png carries a real alpha channel. Do NOT reach for
   mix-blend-mode to knock out a background: spoapp learned that the
   hard way, where a backdrop-filter ancestor created an isolation
   context, silently killed the blend and left a white box.
   ================================================================ */
.brand-mark {
  margin-top: auto;                 /* holds the bottom edge */
  padding-bottom: 6px;
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 8px;
  flex: 0 0 auto;
}
.brand-mark__by {
  font-size: 0.62rem;
  font-weight: 600;
  letter-spacing: 0.04em;
  text-transform: lowercase;
  color: rgba(49, 86, 142, 0.5);
}
.brand-mark img {
  height: 30px;
  width: auto;
  display: block;
}

/* 3. HERO: taller now that the JwètKreyòl title is gone — the picture
   takes the room the wordmark was using, plus the rest of the slack. */
.home-hero { height: 196px; }

/* ================================================================
   4. HERO + WORDMARK: the picture takes a quarter more height
   (196 → 245) and the name lands under it. The wordmark wears
   Graduate — the same letterface as the score chip and the bones —
   so the home screen introduces the game in the game's own alphabet
   rather than in a separate chrome face. (The whole app wears Graduate
   now; this rule predates that and is kept because the size and tracking
   here are the wordmark's, not the body's.)

   Appended, not edited in place: .home-hero is declared six times up
   the file and only the LAST one is live. Change the height here.
   ================================================================ */
.home-hero { height: 245px; }

.home-title {
  flex-shrink: 0;
  /* at 3rem the wordmark needs air under it — 2px let the first card
     crowd the descender line. The slack is free: the cards column is
     flex: 0 0 auto and the home screen has room to spare below. */
  margin: 6px 0 12px;
  text-align: center;
  font-family: 'Graduate', Georgia, serif;
  font-weight: 400;
  font-size: 3rem;
  letter-spacing: 0.04em;
  line-height: 1.05;
  color: var(--nf-blue);
}

/* short phones: the hero already steps aside up the file — the name
   stays, tightened, so the home screen never loses its title */
@media (max-height: 760px) {
  .home-title { font-size: 2.2rem; margin: 2px 0; }
}
@media (max-height: 700px) {
  .home-title { font-size: 1.9rem; margin: 0 0 2px; }
}

/* ================================================================
   5. THE BRAND MARK, PARKED: "yon jwèt" and the Techtra Labs logo are
   both off for now. The whole footer goes rather than just the <img> —
   the byline was already down, so what remained would have been a 36px
   flex row holding nothing but its own padding.

   Nothing else had to move. The mark carried `margin-top: auto`, but
   .mode-cards is `flex: 1 1 auto` and simply absorbs the vacated
   height; since it packs its cards with `justify-content: flex-start`,
   the leftover space renders in the same place either way and the
   links row still lands 46px off the bottom of the frame. Measured
   both ways — identical. To bring the mark back, delete this rule.
   ================================================================ */
.brand-mark { display: none; }

/* ================================================================
   6. CREDITS: the people behind the table, under the build stamp.
   Role muted and small, name in the panel blue — the line reads as
   one sentence but the NAME is what the eye lands on. list-style
   is killed explicitly: .set-pane centres its children, and a
   marker would hang the text off-axis.
   ================================================================ */
.set-credits {
  list-style: none;
  margin: 2px 0 0;
  padding: 0;
  text-align: center;
}
.set-credits li {
  font-size: 0.74rem;
  line-height: 1.75;
  color: var(--nf-blue);
}
.set-credits span {
  opacity: 0.55;
  font-weight: 500;
}
.set-credits b {
  font-weight: 700;
}

/* ================================================================
   7. THE NAME MID-GAME: MoDoMiNo sits in the top bar during a round,
   between the pause glyph on the left and the score plaque on the right.

   Absolute, NOT the bar's flex centre. Every other child of .top-bar is
   absolutely positioned, so an in-flow span would be the only flex item
   and justify-content:center would place it at 180px — which is the true
   middle of the gap only while the score is a single digit. The plaque is
   anchored right and grows leftward: at 1250 points, an ordinary score, it
   ran 17px into the name. So this spans the gap and centres INSIDE it,
   with the right inset holding back the plaque at the widest a reachable
   score makes it (4 digits ≈ 146px, measured; the ceiling is ~5700).

   A fixed inset rather than the live gap because a title that re-centres
   on every score is motion in the chrome, pulling the eye off the board.

   pointer-events:none because it overlays a bar whose whole job is
   buttons; the name should never eat a tap meant for pause.
   ================================================================ */
.play-title {
  position: absolute;
  left: 88px;                  /* the pause glyph ends here: 44 + 44 */
  right: 150px;                /* reserved for the plaque at full width */
  text-align: center;
  font-family: 'Graduate', Georgia, serif;
  font-weight: 400;
  font-size: 1rem;
  letter-spacing: 0.05em;
  line-height: 1;
  color: var(--nf-blue);
  opacity: 0.7;
  white-space: nowrap;
  pointer-events: none;
}

/* ================================================================
   8. THE HERO COMES BACK ON SHORT PHONES.

   Up the file (the "short phones" block) the hero is display:none
   below 760px tall. That was written when this screen carried SIX
   mode cards plus a brand mark — the picture genuinely had nowhere
   to go. It carries two cards and a daily strip now, and the mark
   is parked, so the room exists; the rule outlived its reason and
   the hero simply vanished on any phone under 760px.

   Measured, by shrinking the frame and reading back the height the
   cards column keeps (its content needs 177px; below that the cards
   themselves start to squash):

       viewport   largest hero that keeps the cards whole
        568px      ~110
        640px      ~200
        667px      ~200
        780px       245  (full)

   So it scales rather than disappears. Each step is set below the
   measured ceiling for the range, leaving the cards headroom.
   ================================================================ */
@media (max-height: 760px) {
  .home-hero { display: flex; height: 190px; }
}
@media (max-height: 639px) {
  .home-hero { height: 120px; }
}
/* Under ~520px tall there is genuinely no room for both: the cards are
   the game and they win. This is the only place the picture still steps
   aside, and it is a range almost no phone reports in portrait. */
@media (max-height: 519px) {
  .home-hero { display: none; }
}

/* ================================================================
   9. GLOBAL KLASMAN: a row on the worldwide board says where it was
   played. Dim and small — the name and the score are the row; the
   origin is a footnote that answers "who is this person" without
   competing with either.
   ================================================================ */
.lb-origin {
  margin-left: 7px;
  font-size: 0.62rem;
  font-weight: 600;
  letter-spacing: 0.03em;
  color: rgba(49, 86, 142, 0.45);
}

/* ================================================================
   10. THE NAME AS BONES. MoDoMiNo is four syllable tiles now, cut
   from the same material as the ones you slam on the table: the
   lighter yellow face, the Graduate letterface, the inset highlight
   and the drop shadow that give a tile its thickness.

   The word is also literally how the game works — a word arrives in
   syllables and you build it — so the title demonstrates the rule
   before the player has read a word of Kreyòl.

   One knob, --ht: the tile's width. Everything else is derived from
   it, so the short-viewport steps below set a single number instead
   of re-tuning four properties each.
   ================================================================ */
.home-title {
  --ht: 64px;
  display: flex;
  justify-content: center;
  align-items: flex-end;
  gap: 8px;
  letter-spacing: normal;        /* tracking was for text; these are boxes */
}
.home-tile {
  width: var(--ht);
  height: calc(var(--ht) * 1.12);
  display: flex;
  align-items: center;
  justify-content: center;
  border-radius: calc(var(--ht) * 0.14);
  background: linear-gradient(160deg, #fffdf0 0%, #fff6c8 55%, #ffe9a4 100%);
  box-shadow:
    inset 0 2px 0 rgba(255, 255, 255, 0.9),
    inset 0 -3px 5px rgba(120, 100, 60, 0.35),
    0 6px 10px rgba(0, 0, 0, 0.28);
  font-family: 'Graduate', Georgia, serif;
  font-size: calc(var(--ht) * 0.40);
  line-height: 1;
  color: var(--tile-ink);
  user-select: none;
  -webkit-user-select: none;
}

/* the tiles shrink with the screen, same ladder as the hero above them */
@media (max-height: 760px) { .home-title { --ht: 52px; gap: 7px; } }
@media (max-height: 639px) { .home-title { --ht: 44px; gap: 6px; } }

/* ================================================================
   11. A MODE THAT IS COMING BACK, NOT GONE. Ansanm is switched off
   (KONEK_ENABLED in game.js) and the card is greyed rather than
   removed: a card that disappears reads as something broken, and
   leaving it visible says the mode exists and is on its way.

   The `disabled` attribute is what actually stops the tap and takes
   it out of the tab order — everything here is appearance only, so
   the look can never drift from the behaviour.
   ================================================================ */
.mode-card.is-soon {
  opacity: 0.5;
  cursor: default;
  box-shadow: none;
  background: rgba(255, 255, 255, 0.55);
}
.mode-card.is-soon .mode-card__glyph {
  filter: grayscale(1);
}
.mode-card.is-soon .mode-card__chev {
  visibility: hidden;      /* a chevron promises somewhere to go */
}
.mode-card__soon {
  margin-left: 8px;
  padding: 2px 7px;
  border-radius: 999px;
  background: rgba(49, 86, 142, 0.12);
  color: rgba(49, 86, 142, 0.75);
  font-family: 'Graduate', Georgia, serif;
  font-size: 0.58rem;
  font-weight: 700;
  letter-spacing: 0.04em;
  text-transform: uppercase;
  vertical-align: middle;
  white-space: nowrap;
}

/* ================================================================
   12. CASE IS THE HIERARCHY NOW.

   Graduate ships a single weight, so the bold that used to separate a
   card's title from its description is synthesised and barely reads.
   Case does that work instead: titles in full capitals, descriptions
   in the lowercase form — which in this face is a smaller, quieter
   capital. Two sizes of the same letter, which is exactly the
   distinction the typeface is built to make.

   Applied with text-transform rather than by rewriting the markup, so
   the HTML keeps ordinary sentence case: it is what a screen reader
   announces, what the share text reuses, and what stays legible if
   this face is ever swapped out.
   ================================================================ */
.mode-card__name {
  text-transform: uppercase;
  letter-spacing: 0.02em;   /* capitals need the extra air */
}
.mode-card__desc {
  text-transform: lowercase;
}
/* The level chip rides inside the title but is a status, not a name —
   left in its own case so "⭐ Difisil" still reads as a badge. */
.mode-card__lvl {
  text-transform: none;
}

/* ================================================================
   13. THE NAME IS SPELLED MoDoMiNo, ALWAYS.

   Rule 12 puts card titles in capitals, and that turned the app's own
   name into MODOMINO — the alternating caps are the wordmark, not a
   typing accident, and they are what the tiles under the hero spell
   out. So the name opts OUT of the transform and only the words around
   it take capitals: "MoDoMiNo — SOLO".

   TITLES ONLY. A card's subtitle is lowercase all the way through,
   the name included — the two rules are deliberately different, so the
   subtitle does not carry .wordmark and reads "modomino" there.
   ================================================================ */
.wordmark {
  text-transform: none;
}

/* ================================================================
   14. MO JODI A IS A CARD NOW, not a strip.

   It was a thin bar because it used to ride ON the flagship card. As
   the last item in a list of three cards, a bar read as a footnote to
   MoDoMiNo Ansanm rather than as its own thing.

   It reuses .mode-card wholesale, so it inherits the case rules, the
   glyph column and the chevron for free. Only two things differ: the
   #20 ✓ · 🔥 state chip, which rides in the title the way the level
   badge does on the others, and the softened look once today is done.
   ================================================================ */
.daily-strip__state {
  margin-left: 8px;
  font-size: 0.62rem;
  font-weight: 800;
  letter-spacing: 0.02em;
  color: var(--nf-purple-solid);
  vertical-align: 1px;
  white-space: nowrap;
  /* digits, a tick and a flame — uppercasing them changes nothing and
     lowercasing would shrink the tick, so it opts out of both. */
  text-transform: none;
}
/* Played today: the card steps back rather than disappearing, so the
   streak stays readable and tomorrow's is obviously still coming. */
.mode-card--jodi.is-done { opacity: 0.72; }
.mode-card--jodi.is-done .daily-strip__state { color: var(--hit-green); }

/* ================================================================
   15. UPRIGHT TITLES, ITALIC SUBTITLES.

   The titles were italic from the old chrome face and kept it through
   the switch to Graduate. Upright reads as the label; the slant moves
   down to the subtitle, where it does the softening the old bold used
   to do — the same trick as the case rule above, using the other axis.

   GRADUATE HAS NO ITALIC. It ships one upright face, so this slant is
   synthesised by the browser: a shear applied to the upright letters,
   not drawn shapes. That is fine at a subtitle's size and it is what
   the whole app already does for bold — worth knowing rather than
   discovering, if the subtitles ever look mechanically slanted.
   ================================================================ */
.mode-card__name {
  font-style: normal;
}
.mode-card__desc {
  font-style: italic;
}

/* ================================================================
   16. THE WORDMARK IN PARAMÈT. The Enfo pane opened with a label that
   said "about the application"; it opens with the application's name
   instead, in the bones the hero spells it with.

   Same component, one number different: .home-title derives tile
   size, corner radius and type size from --ht, so a settings-pane
   version is that knob and nothing else. It also inherits the
   flex-shrink and centring already on .home-title.
   ================================================================ */
.set-wordmark {
  --ht: 44px;
  margin: 4px 0 10px;
}

/* ================================================================
   17. A WORD THAT EXPLAINS ITSELF. Koleksyon listed the English gloss
   and nothing more — "pen · bread" tells you the translation but never
   what the thing IS. Rest on a row for a second (press and hold on a
   phone) and the Kreyòl description arrives here and is read aloud.

   STICKY, not a tooltip. .lb-card is `overflow: hidden`, so a bubble
   positioned inside it would be clipped at the card edge; and over a
   two-column grid in a 430px frame a bubble covers the neighbours you
   are reading. A fixed place to look costs neither.
   ================================================================ */
.kol-def {
  position: sticky;
  top: 0;
  z-index: 3;
  margin-top: 10px;
  padding: 7px 12px;
  border-radius: 12px;
  /* OPAQUE, both states. This is sticky and the lists scroll UNDERNEATH it,
     so a translucent fill (--nf-disc is 8% purple) would let rows read
     straight through the sentence. These are that same lavender already
     flattened onto the panel background, which looks identical over the
     page and survives a long collection scrolling past. */
  background: #ebe7f8;
  border: 1px solid var(--nf-frame-stroke);
  box-shadow: 0 6px 12px -8px rgba(60, 30, 100, 0.35);
  transition: background 0.2s ease, border-color 0.2s ease;

  /* A FIXED BAND, WHATEVER IS IN IT.

     A panel that grows when it answers reflows the list beneath it, and the
     row you are resting on walks out from under the pointer at the exact
     moment it speaks — firing pointerout and cancelling the very highlight
     it just set. Measured at 43px of shift before this was reserved: more
     than a row is tall, so the cursor landed on a DIFFERENT word.

     So the height is reserved for the worst case rather than fitted to the
     content, and the idle state is padded to the same band. The reserve
     differs by layout mode because the wrap point does: on the 430px canvas
     the longest description (69 characters) sits beside the word or on one
     line below it; at phone width it needs two. Verified by measuring all
     4500 descriptions against the longest word in the bank at both widths —
     one height, one row position, in each mode. */
  min-height: var(--kol-bar);
  display: flex;
  align-items: center;     /* the reserve reads as padding, not a blank line */
  flex-wrap: wrap;
  gap: 0 6px;
}

/* nothing hovered yet: quieter, but still OPAQUE — the rows scroll under
   the invitation exactly as they scroll under an answer. */
.kol-def.is-idle {
  background: #f7f7fc;
  border-color: transparent;
  box-shadow: none;
}
.kol-def.is-idle .kol-def__d {
  color: rgba(49, 86, 142, 0.45);
  font-size: 0.72rem;
}
.kol-def__w {
  color: var(--nf-purple-solid);
  font-size: 0.9rem;
}
.kol-def__w:empty { display: none; }   /* idle: no stray gap before the hint */
.kol-def__d {
  font-size: 0.78rem;
  font-style: italic;              /* descriptions read as description */
  color: rgba(49, 86, 142, 0.85);
  line-height: 1.35;
}

/* the lit row is what ties the sentence above to the word under the
   cursor — same .is-preview the tiles use when they speak */
.kol-list li {
  border-radius: 7px;
  transition: background 0.15s ease;
  /* a long press must not start selecting text or raise the iOS callout */
  -webkit-touch-callout: none;
  -webkit-user-select: none;
  user-select: none;
}
.kol-list li[data-fr] { cursor: help; }
.kol-list li.is-preview {
  /* background ONLY. Widening the padding here nudged the text 4px sideways
     under the resting cursor — the same reflow-under-the-pointer mistake the
     panel above reserves its height to avoid, just on the other axis. */
  background: var(--nf-disc);
}

/* ================================================================
   18. THE COUNT JUMPS TO WHAT IT COUNTS. "Pou revize" sits below the
   words you already know, so on a full collection the number was on
   screen and the list it referred to was not. Clicking the chip now
   scrolls to that section.

   --kol-bar is declared here, on the scroller, because two things need
   the same number: the description bar reserves it as height, and the
   jump target has to clear it. As separate literals they drift apart
   the first time either is retuned, and the symptom is a heading that
   lands underneath a sticky bar.
   ================================================================ */
#screen-koleksyon { --kol-bar: 4.3rem; }
/* Phone width: the frame is the device's own width, a 69-character sentence
   takes a second line, and the reserve has to grow with it.
   This MUST sit after the base declaration. It first lived up in §17, where
   it lost every time — same specificity, and this stylesheet is append-only,
   so the later rule wins. The bar quietly went back to one line on phones,
   which is exactly the reflow §17 exists to prevent. */
@media (max-width: 479px) {
  #screen-koleksyon { --kol-bar: 5.4rem; }
}

/* scroll-margin-top is what stops `block: 'start'` parking the card
   under the sticky bar — no offset arithmetic, and immune to the
   frame's transform in a way manual scrollTop math is not. */
#kol-card-review { scroll-margin-top: calc(var(--kol-bar) + 12px); }

/* the interactive chip has to be indistinguishable from its siblings:
   a <button> arrives with its own border, font and background */
button.kol-chip {
  border: 0;
  font: inherit;
  font-size: 0.8rem;
  font-weight: 700;
  line-height: inherit;
  cursor: pointer;
  -webkit-appearance: none;
  appearance: none;
}
button.kol-chip:hover { filter: brightness(1.06); }
button.kol-chip:active { transform: translateY(1px); }
button.kol-chip:focus-visible {
  outline: 2px solid var(--nf-purple-solid);
  outline-offset: 2px;
}

/* ================================================================
   19. SIGNING IN. The gate card and the Kont row, ported from
   FotoLekti's shared-cookie SSO and dressed in this app's language:
   Graduate, the purple, the same rounded card as everything else.

   The modal sits OUTSIDE .device-frame on purpose. The frame is a
   430px canvas under a JS scale transform; a dialog inherited into
   that gets shrunk with the phone and reads as a toy. Full viewport,
   unscaled, is what a sign-in card should be — and it sidesteps the
   scaled-vs-layout-pixel confusion that transform brings with it.
   ================================================================ */
.gate-modal {
  position: fixed;
  inset: 0;
  z-index: 9000;
  display: flex;
  align-items: center;
  justify-content: center;
  padding: 20px;
  opacity: 0;
  pointer-events: none;
  transition: opacity 0.18s ease;
}
.gate-modal.open { opacity: 1; pointer-events: auto; }
.gate-backdrop {
  position: absolute;
  inset: 0;
  background: rgba(30, 16, 66, 0.55);
  backdrop-filter: blur(3px);
}
.gate-card {
  position: relative;
  width: min(400px, 100%);
  max-height: 92vh;
  overflow-y: auto;
  scrollbar-width: none;
  padding: 26px 22px 20px;
  border-radius: 24px;
  background: var(--nf-card);
  border: 1px solid var(--nf-frame-stroke);
  box-shadow: 0 24px 60px rgba(40, 20, 90, 0.35);
  text-align: center;
  animation: gateIn 0.22s ease;
}
.gate-card::-webkit-scrollbar { display: none; }
@keyframes gateIn {
  from { opacity: 0; transform: translateY(10px) scale(0.985); }
  to   { opacity: 1; transform: none; }
}
/* the wordmark stands in for a title — same four bones as the hero */
.gate-card .set-wordmark { justify-content: center; margin-bottom: 12px; }
.gate-subtitle {
  margin: 0 0 16px;
  font-size: 0.82rem;
  font-style: italic;
  line-height: 1.45;
  color: rgba(49, 86, 142, 0.8);
}
.gate-error {
  display: none;
  margin: 0 0 12px;
  padding: 8px 12px;
  border-radius: 10px;
  font-size: 0.76rem;
  background: rgba(255, 92, 92, 0.12);
  color: #b3261e;
}
.gate-error.show { display: block; }
.gate-error.info { background: var(--nf-disc); color: var(--nf-purple-solid); }

/* provider buttons: each keeps its own brand colour, because a Google
   button that is not Google-coloured is a button people do not trust */
.signin-btn {
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 10px;
  width: 100%;
  margin-bottom: 9px;
  padding: 11px 14px;
  border: 0;
  border-radius: 999px;
  font: inherit;
  font-size: 0.85rem;
  font-weight: 700;
  color: #fff;
  cursor: pointer;
  transition: filter 0.15s ease, transform 0.08s ease;
}
.signin-btn:hover { filter: brightness(1.07); }
.signin-btn:active { transform: translateY(1px); }
.signin-ico { width: 18px; height: 18px; flex: 0 0 18px; }
.signin-btn-google   { background: #4285F4; }
.signin-btn-facebook { background: #1877F2; }
.signin-btn-apple    { background: #111; }
.signin-btn-google .signin-ico {
  background: #fff; border-radius: 50%; padding: 2px;
  width: 20px; height: 20px; flex-basis: 20px;
}

.signin-alt-pill {
  display: inline-flex;
  align-items: center;
  gap: 8px;
  margin: 6px 0 0;
  padding: 8px 14px;
  border-radius: 999px;
  border: 1px solid var(--nf-frame-stroke);
  background: var(--nf-disc);
  color: var(--nf-purple-solid);
  font: inherit;
  font-size: 0.78rem;
  font-weight: 700;
  cursor: pointer;
}
.signin-alt-pill:hover { filter: brightness(0.98); }
.signin-alt-pill-icon { width: 15px; height: 15px; }

.gate-hint {
  margin: 14px 0 10px;
  font-size: 0.72rem;
  font-style: italic;
  line-height: 1.4;
  color: rgba(49, 86, 142, 0.6);
}
/* the way out. MANDATORY in modomino-auth.js removes this affordance;
   the game working without an account is why it is here at all */
.gate-skip {
  border: 0;
  background: none;
  font: inherit;
  font-size: 0.78rem;
  font-weight: 700;
  color: rgba(49, 86, 142, 0.75);
  text-decoration: underline;
  cursor: pointer;
  padding: 6px 10px;
}
.gate-skip:hover { color: var(--nf-purple-solid); }

/* ---- Paramèt → Kont ---- */
.account-row {
  /* .set-pane.is-on centres its children, so without this the row shrinks to
     fit its text and a short name makes a stubby little card */
  width: 100%;
  box-sizing: border-box;
  display: flex;
  align-items: center;
  gap: 10px;
  padding: 12px 14px;
  border-radius: 14px;
  background: var(--nf-disc);
  border: 1px solid var(--nf-frame-stroke);
}
.account-who { flex: 1 1 auto; min-width: 0; text-align: left; }
.account-name {
  display: block;
  font-size: 0.85rem;
  color: var(--nf-purple-solid);
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.account-email {
  display: block;
  font-size: 0.7rem;
  font-style: italic;
  color: rgba(49, 86, 142, 0.65);
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.account-btn {
  flex: 0 0 auto;
  padding: 7px 15px;
  border: 0;
  border-radius: 999px;
  background: var(--nf-grad);
  color: #fff;
  font: inherit;
  font-size: 0.78rem;
  font-weight: 700;
  cursor: pointer;
}
.account-btn:hover { filter: brightness(1.06); }
.account-btn:active { transform: translateY(1px); }
.account-danger {
  margin-top: 14px;
  padding: 7px 14px;
  border-radius: 999px;
  border: 1px solid rgba(179, 38, 30, 0.35);
  background: none;
  color: #b3261e;
  font: inherit;
  font-size: 0.74rem;
  font-weight: 700;
  cursor: pointer;
}
.account-danger:hover { background: rgba(179, 38, 30, 0.07); }

@media (prefers-reduced-motion: reduce) {
  .gate-card { animation: none; }
  .gate-modal { transition: none; }
}

/* ================================================================
   20. PASS IT ON. A line under the result inviting the player to
   send it to a friend. It sits in the end card; the WhatsApp button
   it points at sits in the recap card below, which is why the arrow
   points down and why game.js hides this whenever that card is
   hidden — an invitation to press a button that is not on screen is
   worse than no invitation.
   ================================================================ */
.end-share-cta {
  margin: 8px 0 0;
  font-size: 0.78rem;
  font-style: italic;
  color: var(--nf-purple-solid);
  opacity: 0.9;
}

/* ================================================================
   21. THE THREE WAYS TO PASS IT ON, lifted above the level card so
   they sit directly under the invitation in the end card instead of
   at the bottom of the word list where nobody scrolled to them.

   Their own row now, not a stack inside the recap card, so three
   buttons of different widths read as one group.
   ================================================================ */
.end-share {
  display: flex;
  flex-wrap: wrap;
  justify-content: center;
  align-items: center;
  gap: 8px;
  margin-top: var(--breather);
}
.end-share .ghost-btn,
.end-share .wa-btn { margin: 0; }

/* ================================================================
   22. THE HAND IS ONE LINE, ALWAYS.
   It was `flex-wrap: wrap` with max-height 172px and a scrollbar, so
   a rack grown by draws spilled onto a second and third row. Two
   costs, and the second is the expensive one:

   1. the suggestions stopped reading as one rack;
   2. the hand is a flex sibling of the felt, so every extra row TOOK
      HEIGHT FROM #dom-viewport. The board's camera had framed the
      word against the old height, and the sockets ended up outside
      the shorter viewport — the silo "not visible" while the clue
      was being read.

   So: never wraps, and a FIXED height, which is what keeps the felt's
   height stable from deal to deal. Tile width is computed in JS
   (domSizeHand) from the rack size, the same way the board's cells
   are — down to a readable floor, past which the row pans sideways
   rather than shrinking into confetti.

   These selectors carry `.is-tight` too: that class sizes tiles at a
   higher specificity than a plain `.dom-hand .dom-tile`, so without
   naming it here it would keep winning and the computed size would
   be ignored on a big rack — exactly when this matters most.
   ================================================================ */
.dom-hand {
  flex-wrap: nowrap;
  overflow-x: auto;
  overflow-y: hidden;
  max-height: none;
  height: 76px;
  align-items: center;
  scrollbar-width: none;
  scroll-behavior: smooth;
}
.dom-hand::-webkit-scrollbar { display: none; }

.dom-hand .dom-tile,
.dom-hand.is-tight .dom-tile {
  flex: 0 0 auto;
  height: var(--hand-ch, 58px);
  font-size: var(--hand-fs, 1.06rem);
}
.dom-hand .dom-tile:not(.is-used),
.dom-hand.is-tight .dom-tile:not(.is-used) {
  width: var(--hand-cw, 50px);
  min-width: 0;
  padding: 0;
}
/* the fold still folds: a played tile collapses to nothing rather than
   holding its computed width open as a gap in the row */
.dom-hand .dom-tile.is-used { width: 0; }

/* a Pataje button with nothing to send: present, so it is not mistaken for
   missing, but plainly not pressable */
.end-share .ghost-btn:disabled {
  opacity: 0.45;
  cursor: default;
  filter: grayscale(0.5);
}

/* ================================================================
   23. THE HEAD TO HEAD. A Defi link now carries the challenger's
   score, so a round played from one ends against a person. The card
   only appears when there is somebody on the other side of it.

   Deliberately louder than the cards around it: this is the one
   thing on the end screen that asks for an answer.
   ================================================================ */
.vs-card {
  margin-top: var(--breather);
  padding: 16px 18px 18px;
  border-radius: 18px;
  background: var(--nf-card);
  border: 1px solid var(--nf-frame-stroke);
  box-shadow: 0 10px 28px rgba(60, 30, 100, 0.12);
  text-align: center;
}
.vs-card.is-win  { border-color: rgba(46, 160, 90, 0.55); }
.vs-card.is-loss { border-color: rgba(210, 16, 52, 0.40); }
.vs-card__label {
  margin: 0 0 10px;
  font-size: 0.7rem;
  letter-spacing: 0.16em;
  color: var(--nf-purple-solid);
  opacity: 0.75;
}
.vs-card__row {
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 14px;
}
.vs-side {
  flex: 1 1 0;
  min-width: 0;
  padding: 10px 6px;
  border-radius: 12px;
  background: var(--nf-disc);
  opacity: 0.55;                      /* the one ahead is the one lit */
  transition: opacity 0.2s ease, transform 0.2s ease;
}
.vs-side.is-ahead { opacity: 1; transform: scale(1.04); }
.vs-side b {
  display: block;
  font-size: 1.6rem;
  line-height: 1.1;
  color: var(--nf-purple-solid);
  font-variant-numeric: tabular-nums;
}
.vs-side span {
  display: block;
  margin-top: 2px;
  font-size: 0.7rem;
  font-style: italic;
  color: rgba(49, 86, 142, 0.7);
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.vs-card__x {
  flex: 0 0 auto;
  font-size: 0.9rem;
  color: rgba(49, 86, 142, 0.45);
}
.vs-card__verdict {
  margin: 12px 0 0;
  font-size: 0.92rem;
  color: var(--nf-blue);
}
.vs-card__btn { margin-top: 14px; width: 100%; }

/* ================================================================
   24. KOLEKSYON BECOMES A DICTIONARY. Search and an unseen filter
   reach the whole 4,500-word bank, not just the words a player has
   already met — which was four thousand entries, each with a
   definition and a recording, that nothing could open.
   ================================================================ */
.kol-tools {
  display: flex;
  gap: 8px;
  margin-top: 10px;
  align-items: stretch;
}
.kol-search {
  flex: 1 1 auto;
  min-width: 0;
  padding: 8px 12px;
  border-radius: 999px;
  border: 1px solid var(--nf-frame-stroke);
  background: var(--nf-card);
  color: var(--nf-purple-solid);
  font: inherit;
  font-size: 0.82rem;
}
.kol-search::placeholder { color: rgba(49, 86, 142, 0.45); font-style: italic; }
.kol-search:focus-visible {
  outline: 2px solid var(--nf-purple-solid);
  outline-offset: 1px;
}
.kol-filter {
  flex: 0 0 auto;
  padding: 8px 13px;
  border-radius: 999px;
  border: 1px solid var(--nf-frame-stroke);
  background: var(--nf-disc);
  color: var(--nf-purple-solid);
  font: inherit;
  font-size: 0.72rem;
  font-weight: 700;
  cursor: pointer;
  white-space: nowrap;
}
.kol-filter.is-on {
  background: var(--nf-grad);
  color: #fff;
  border-color: transparent;
}
.kol-filter:active { transform: translateY(1px); }

/* the kanè button sits with the other Koleksyon tools, narrower because it
   carries a glyph rather than a phrase */
.kol-filter--share { padding: 8px 11px; font-size: 0.9rem; line-height: 1; }

/* ================================================================
   25. WHAT TO DO NEXT, UNDER THE SHARE ROW. "Jwe ankò" and "Tounen
   lakay" sat at the very bottom of the end screen — below the level
   card, the word list and the leaderboard — so on a full round the
   two things a player actually reaches for were several scrolls past
   everything they had already read.

   Their own group, so the pair reads as one decision rather than two
   loose buttons: the margin-top the buttons carry for standing alone
   at the foot of a screen is cancelled, and the gap does the spacing.
   ================================================================ */
.end-next {
  display: grid;
  gap: 9px;
  margin-top: 16px;
}
.end-next .primary-btn,
.end-next .ghost-btn { margin-top: 0; }

/* ================================================================
   26. THE TABLE'S RESULT. Finishing an Ansanm race first means
   landing on the end screen while everyone else is still playing,
   so this card fills in as their results arrive rather than being
   drawn once and going stale.
   ================================================================ */
.ans-card {
  margin-top: var(--breather);
  padding: 16px 16px 14px;
  border-radius: 18px;
  background: var(--nf-card);
  border: 1px solid var(--nf-frame-stroke);
  box-shadow: 0 10px 28px rgba(60, 30, 100, 0.12);
  text-align: center;
}
.ans-card__crown { margin: 0; font-size: 1.9rem; line-height: 1; }
.ans-card__title {
  margin: 6px 0 12px;
  font-size: 1.05rem;
  color: var(--nf-purple-solid);
}
.ans-rows { list-style: none; margin: 0; padding: 0; display: grid; gap: 2px; }
.ans-row {
  display: grid;
  grid-template-columns: 2.1rem 1fr auto;
  align-items: baseline;
  gap: 8px;
  padding: 7px 9px;
  border-radius: 9px;
  font-size: 0.9rem;
  text-align: left;
}
/* your own row is the one you look for first */
.ans-row.is-me { background: var(--nf-disc); }
.ans-row__pos {
  font-size: 0.78rem;
  color: rgba(49, 86, 142, 0.55);
  font-variant-numeric: tabular-nums;
}
.ans-row__name {
  color: var(--nf-purple-solid);
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.ans-row__pts {
  font-weight: 700;
  color: var(--nf-blue);
  font-variant-numeric: tabular-nums;
}
.ans-card__note {
  margin: 10px 0 0;
  font-size: 0.72rem;
  font-style: italic;
  color: rgba(49, 86, 142, 0.6);
}

/* ================================================================
   27. THE NAME MID-GAME IS THE NAME FROM THE FRONT PAGE. It was
   plain Graduate text while the home screen wore the four bones —
   the same product with two different signatures, and the one you
   look at for a whole round was the lesser of them.

   Same component, one knob: .home-tile derives everything from an
   inherited --ht, so this is a size, not a second design. 30px was
   measured, not guessed — the strip between the pause glyph and the
   score plaque is 192px wide and the bar is 44px tall, and four
   tiles at 30 with a 4px gap come to 132 × 34, which centres in
   both with room to spare as the plaque grows leftward.

   Two things are toned down for a bar rather than a hero: the drop
   shadow, which at full strength smears out of a 44px bar and onto
   the felt, and the whole mark to 0.85 — the original text sat at
   0.7 precisely so the chrome would not pull the eye off the board,
   and bones read louder than letters.
   ================================================================ */
.play-title {
  --ht: 30px;
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 4px;
  opacity: 0.85;
  letter-spacing: normal;      /* tracking was for text; these are boxes */
}
.play-title .home-tile {
  box-shadow:
    inset 0 1px 0 rgba(255, 255, 255, 0.85),
    inset 0 -2px 3px rgba(120, 100, 60, 0.30),
    0 2px 4px rgba(0, 0, 0, 0.22);
}
/* short phones lose bar height before anything else does */
@media (max-height: 639px) {
  .play-title { --ht: 26px; gap: 3px; }
}

/* ================================================================
   28. THE EAR. Say the word instead of building it — the mic sits
   with the other two round controls (replay the clue, hint) rather
   than the top bar, which mid-game is already pause, wordmark and
   score edge to edge with nothing to spare.

   Three states worth telling apart: off (the resting control), on
   (armed, purple), and live (actually hearing, a slow pulse). The
   pulse matters — a microphone that is listening and gives no sign
   of it is the kind of thing people close the app over.
   ================================================================ */
.dom-mic.is-on {
  background: var(--nf-grad);
  color: #fff;
  border-color: transparent;
}
.dom-mic.is-live { animation: micPulse 1.6s ease-in-out infinite; }
@keyframes micPulse {
  0%, 100% { box-shadow: 0 0 0 0 rgba(210, 16, 52, 0.45); }
  50%      { box-shadow: 0 0 0 6px rgba(210, 16, 52, 0); }
}
/* a word taken by voice: the control confirms it was the ear that did it */
.dom-mic.is-hit { animation: micHit 0.5s ease-out; }
@keyframes micHit {
  0%   { transform: scale(1); }
  40%  { transform: scale(1.18); }
  100% { transform: scale(1); }
}
.dom-mic:disabled { opacity: 0.4; cursor: default; }
@media (prefers-reduced-motion: reduce) {
  .dom-mic.is-live, .dom-mic.is-hit { animation: none; }
}

/* ── 29. The mic that can still be tapped ────────────────────────────────
   It is no longer `disabled` — a disabled button swallows the click, and
   with it the explanation of why the mic is unavailable. It now carries
   aria-disabled instead, so it looks exactly as inert as before and still
   answers a tap. */
.dom-mic[aria-disabled="true"] { opacity: 0.4; }
.dom-mic[aria-disabled="true"]:active { transform: none; }

/* ── 30. Red means speak now ─────────────────────────────────────────────
   Three states the player reads without being taught them:

     off        the ordinary white disc
     on         blue — the ear is armed
     LISTENING  flashing red — say the word

   The blue "on" state only says the microphone is available. It does not
   say the app is recording, and the recorder runs in windows rather than
   continuously — so without a third state a player speaks into the gap
   between two windows and nothing comes back. Red is the cue that the
   window is open.

   Declared after .is-on at equal specificity, so it wins while both classes
   are present: the append-only convention this file already runs on.

   About 1.2 Hz — well under the 3 Hz photosensitivity threshold — and it
   alternates between two reds rather than red and nothing, so the control
   never reads as having vanished. */
.dom-mic.is-live {
  background: #D21034;
  border-color: transparent;
  color: #fff;
  animation: micListen 0.84s steps(1, end) infinite;
}
@keyframes micListen {
  0%,   49%  { background: #D21034; box-shadow: 0 0 0 0 rgba(210, 16, 52, 0.55); }
  50%, 100%  { background: #F03A55; box-shadow: 0 0 0 7px rgba(210, 16, 52, 0); }
}
/* Motion off, meaning kept: still unmistakably red, just not blinking. */
@media (prefers-reduced-motion: reduce) {
  .dom-mic.is-live {
    animation: none;
    background: #D21034;
    box-shadow: 0 0 0 3px rgba(210, 16, 52, 0.30);
  }
}

/* ── 31. The wordmark, at the size it was always meant to be ─────────────
   Measured, and the measurement corrected an assumption in section 27.

   .play-title is a flex row and .home-tile shrinks by default, so the mark
   was never rendering at its --ht: the strip between the pause glyph and
   the score plaque is 122px on a 360px bar, the four tiles want 132, and
   the browser quietly squeezed each one to 27.5px. Raising --ht alone only
   made the tiles taller — the width stayed pinned, so the bones stretched.

   The room comes first. `right` reserved 150px for a plaque that actually
   measures 85, leaving 65px of dead bar. Reserving 118 still gives the
   plaque a third more than it uses and widens the strip to 154px, which is
   enough for four 34px tiles and a 4px gap (148) without any shrink.

   34px is the ceiling the BAR sets, not the strip: tiles are 1:1.12, so 34
   renders 38px tall inside a 44px bar and keeps ~3px of air top and bottom.
   38px tiles would fit the width and foul the bar.

   Net: 27.5 → 34 wide, 24% larger, and the bones are correctly proportioned
   again rather than squashed. Shrink is deliberately left enabled, so a
   narrower phone still degrades by squeezing instead of overflowing. */
.play-title {
  --ht: 34px;
  right: 118px;
}

/* ── 32. The ownership line under the credits ────────────────────────────
   Set apart from the role lines above it: it names who the app belongs
   to, not who did what. */
.set-credits__own { margin-top: 6px; opacity: 0.8; }

/* ── 33. Apwopo MoDoMiNo ─────────────────────────────────────────────────
   The credits, at a size that can be read: they were three lines of small
   type at the bottom of a settings pane. Roles first, names bold, one
   note under the line that needs one. Three languages, one shown. */
.about-open { margin-top: 14px; width: 100%; }
.about-lang { display: flex; gap: 6px; justify-content: center; margin: 4px 0 16px; }
.about-lang__btn {
  border: 1px solid var(--nf-frame-stroke); background: var(--nf-disc);
  color: var(--nf-purple-solid); border-radius: 999px; padding: 6px 14px;
  font: 600 13px/1 inherit; font-family: inherit; cursor: pointer;
}
.about-lang__btn.is-on { background: var(--nf-grad); color: #fff; border-color: transparent; }
.about { padding: 0 18px 8px; max-width: 40ch; margin: 0 auto; }
.about p { margin: 0 0 12px; font-size: 15px; line-height: 1.55; }
.about__tag { font-family: 'Graduate', Georgia, serif; font-size: 17px; color: var(--nf-blue); }
.about__h {
  margin: 18px 0 8px; font-family: 'Graduate', Georgia, serif; font-weight: 400;
  font-size: 15px; letter-spacing: .04em; color: var(--nf-blue);
}
.about__credits { list-style: none; margin: 0; padding: 0; display: grid; gap: 10px; }
.about__credits li { display: grid; gap: 2px; }
.about__credits span { font-size: 12px; letter-spacing: .08em; text-transform: uppercase; opacity: .7; }
.about__credits b { font-size: 19px; line-height: 1.2; }
.about__credits em { font-size: 13px; font-style: normal; opacity: .75; }
.about__own { margin-top: 18px; font-size: 16px; }
.about__own b { font-size: 18px; }
.about__own i { font-size: 14px; opacity: .8; }
.about__ver { font-size: 12px; opacity: .6; letter-spacing: .06em; }
.about-back { margin: 10px auto 0; display: block; }

/* ── 34. "Mo Jodi a" on one line ─────────────────────────────────────────
   The card's name span also carries the daily's live state — the puzzle
   number and the streak-to-reward hint, e.g. "#22 · 3 jou pou tab ble a"
   — inline after the name. That is 34 characters in a 242px column, so
   the name wrapped. The other cards only append "⭐ Fasil", which fits.
   The state now takes its own smaller line under the name; the name
   itself never breaks. Nothing is truncated: the hint is the reason the
   state exists. */
.mode-card--jodi .mode-card__name { white-space: nowrap; }
.mode-card--jodi .mode-card__name .daily-strip__state {
  display: block;
  white-space: normal;
  margin: 2px 0 0;
  font-size: 0.78em;
  font-weight: 600;
  letter-spacing: 0.01em;
}

/* ── 35. How to play ─────────────────────────────────────────────────────
   Same column and type as Apwopo; the steps are a real sequence, so they
   are numbered, and the lists are not. */
.howto__steps { margin: 0 0 6px; padding-left: 22px; display: grid; gap: 8px; font-size: 15px; line-height: 1.5; }
.howto__steps li::marker { font-family: 'Graduate', Georgia, serif; color: var(--nf-blue); }
.howto__list { list-style: none; margin: 0 0 6px; padding: 0; display: grid; gap: 7px; font-size: 15px; line-height: 1.5; }
.howto__list li { padding-left: 14px; position: relative; }
.howto__list li::before { content: "🁢"; position: absolute; left: -4px; top: 0; font-size: 11px; opacity: .55; }
.about i { font-style: italic; }

/* ── 36. Long reference screens scroll ───────────────────────────────────
   A .screen is a flex column inside the fixed 430×932 frame; it clips
   whatever runs past the bottom unless it says otherwise. Paramèt,
   Klasman and Koleksyon already do. How-to in three languages runs to
   several screens, and Apwopo is one tap from it — same rule for both,
   with the scrollbar hidden the way the others hide it. */
#screen-howto, #screen-about { overflow-y: auto; -webkit-overflow-scrolling: touch; padding-bottom: 24px; }
#screen-howto::-webkit-scrollbar, #screen-about::-webkit-scrollbar { display: none; }

/* ── 37. Open tables in the lobby ────────────────────────────────────────
   The Prive / Louvri choice borrows the team chips; the list of open
   tables borrows the roster rows, as buttons. */
.konek-visib { margin-bottom: 8px; }
.konek-team.is-open.is-on { background: #1f8a5b; }
.konek-dir { width: 100%; max-width: 360px; margin-top: 6px; }
.konek-dirlist { list-style: none; margin: 0 0 6px; padding: 0; display: flex; flex-direction: column; gap: 6px; }
.konek-dirrow {
  width: 100%; display: grid; grid-template-columns: 1fr auto; gap: 2px 10px; align-items: center;
  text-align: left; padding: 9px 12px; border-radius: 12px; cursor: pointer;
  background: var(--nf-disc); border: 1px solid var(--nf-frame-stroke); color: inherit; font: inherit;
}
.konek-dirrow:active { transform: scale(0.99); }
.konek-dirrow b { font-size: 15px; }
.konek-dircode { font-family: ui-monospace, monospace; font-size: 12px; letter-spacing: .08em; opacity: .75; grid-column: 2; grid-row: 1 / span 2; align-self: center; }
.konek-dirmeta { font-size: 12px; opacity: .7; }
.konek-visline { margin: 0 0 8px; font-size: 13px; opacity: .8; text-align: center; }
.konek-flip { margin-bottom: 8px; }

/* ── 38. The table code, prominently ────────────────────────────────────
   The code is the thing the host has to get to other people — read out,
   typed, or shown to a camera. It was a bold word in a sentence. Now it
   is the biggest thing on the panel: mono, spaced, tappable to copy. */
.konek-codecard { width: 100%; max-width: 360px; text-align: center; margin: 2px 0 10px; }
.konek-codelabel { margin: 0 0 4px; font-size: 12px; letter-spacing: .08em; text-transform: uppercase; opacity: .7; display: flex; justify-content: center; align-items: center; gap: 8px; }
.konek-bigcode {
  display: block; width: 100%; padding: 12px 10px; border-radius: 14px; cursor: pointer;
  background: var(--nf-disc); border: 1px solid var(--nf-frame-stroke); color: var(--nf-blue);
  font-family: ui-monospace, 'JetBrains Mono', Menlo, monospace; font-weight: 700;
  font-size: clamp(26px, 9vw, 34px); letter-spacing: .18em; line-height: 1.15;
  text-indent: .18em;                 /* letter-spacing trails the last glyph; this recentres */
  white-space: nowrap; word-break: keep-all; overflow: hidden;   /* one word, never FANMIDIMAN / CH */
}
.konek-bigcode.is-long { font-size: clamp(20px, 7.2vw, 28px); letter-spacing: .1em; text-indent: .1em; }
.konek-bigcode:active { transform: scale(0.99); }
.konek-codehint { margin: 6px 0 0; font-size: 12px; opacity: .7; }

/* ── 39. Ansanm, simplified ──────────────────────────────────────────────
   Before a table: your name, then two doors — join one that exists, or
   make one. In a table: the table is the whole screen. Small uppercase
   labels do the structuring; there is one primary action per state. */
.konek-h { margin: 0 0 6px; font-size: 11px; font-weight: 700; letter-spacing: .1em; text-transform: uppercase; color: var(--nf-blue); opacity: .85; }
.konek-h small { font-weight: 400; text-transform: none; letter-spacing: 0; opacity: .8; }
.konek-h--inline { margin: 0; white-space: nowrap; }
.konek-door { width: 100%; max-width: 360px; display: flex; flex-direction: column; gap: 8px; }
.konek-or { width: 100%; max-width: 360px; display: flex; align-items: center; gap: 10px; margin: 2px 0; font-size: 12px; opacity: .55; }
.konek-or::before, .konek-or::after { content: ""; flex: 1; height: 1px; background: var(--nf-frame-stroke); }
.konek-dir { margin: 0; }
.konek-dirhead { display: flex; align-items: center; justify-content: space-between; font-size: 12px; font-weight: 700; letter-spacing: .06em; text-transform: uppercase; opacity: .8; margin-bottom: 6px; }
.konek-refresh {
  width: 30px; height: 30px; border-radius: 50%; border: 1px solid var(--nf-frame-stroke);
  background: var(--nf-disc); color: var(--nf-purple-solid); font-size: 17px; line-height: 1; cursor: pointer;
}
.konek-refresh.is-spinning { animation: konekSpin 1s linear infinite; }
@keyframes konekSpin { to { transform: rotate(360deg); } }
.konek-dirnote { margin: 0; }
.konek-create { width: 100%; }
.konek-switch {
  display: flex; align-items: center; gap: 10px; width: 100%; padding: 8px 10px;
  border-radius: 12px; border: 1px solid var(--nf-frame-stroke); background: var(--nf-disc);
  color: inherit; font: inherit; text-align: left; cursor: pointer;
}
.konek-switch__knob { flex: 0 0 auto; width: 38px; height: 22px; border-radius: 999px; background: #c9c3dd; position: relative; transition: background .2s; }
.konek-switch__knob::after { content: ""; position: absolute; top: 3px; left: 3px; width: 16px; height: 16px; border-radius: 50%; background: #fff; transition: left .2s; }
.konek-switch.is-on .konek-switch__knob { background: #1f8a5b; }
.konek-switch.is-on .konek-switch__knob::after { left: 19px; }
.konek-switch__text { font-size: 14px; line-height: 1.25; }
.konek-switch__text small { display: block; font-size: 11px; opacity: .65; }
.konek-teamrow { display: flex; align-items: center; gap: 8px; margin: 4px 0 10px; }
.konek-teamrow .konek-teams { flex: 1; }
.konek-startbox { width: 100%; display: flex; flex-direction: column; gap: 6px; margin-top: 2px; }
.konek-mode .konek-team { flex: 1; }
.konek-modedesc { margin: 0 0 2px; font-size: 12px; opacity: .7; text-align: center; }
@media (prefers-reduced-motion: reduce) { .konek-refresh.is-spinning { animation: none; } }

/* ── 40. Prive / Louvri as two radios ───────────────────────────────────
   A switch reads as a setting; two named choices read as a decision. */
.konek-visib { width: 100%; margin: 0; padding: 0; border: 0; display: grid; gap: 6px; }
.konek-visib legend { padding: 0; margin-bottom: 2px; }
.konek-radio {
  display: flex; align-items: center; gap: 10px; padding: 8px 10px; cursor: pointer;
  border-radius: 12px; border: 1px solid var(--nf-frame-stroke); background: var(--nf-disc);
}
.konek-radio input { width: 18px; height: 18px; margin: 0; accent-color: var(--nf-purple-solid); flex: 0 0 auto; }
.konek-radio span { display: grid; line-height: 1.2; }
.konek-radio b { font-size: 14px; }
.konek-radio small { font-size: 11px; opacity: .65; }
.konek-radio:has(input:checked) { border-color: var(--nf-purple-solid); box-shadow: inset 0 0 0 1px var(--nf-purple-solid); }

/* ── 41. Tables have names, and the list shows them ─────────────────────
   The name is the code — the thing you say out loud — so it leads the
   row in the code's own face; the host and the head-count follow. */
.konek-dirrow { grid-template-columns: 1fr; gap: 1px; }
.konek-dirname { font-family: ui-monospace, 'JetBrains Mono', Menlo, monospace; font-size: 17px; letter-spacing: .12em; color: var(--nf-blue); }
#konek-tabname { text-transform: uppercase; letter-spacing: .12em; }

/* ── 42. Whose table it is ──────────────────────────────────────────────
   The owner's row is crowned and tinted so the lobby reads "whose table"
   at a glance, on every phone at it. */
.konek-row.is-owner { background: rgba(255, 196, 107, 0.22); box-shadow: inset 0 0 0 1.5px rgba(214, 150, 40, 0.6); }
.konek-row.is-owner.is-me { box-shadow: inset 0 0 0 1.5px rgba(214, 150, 40, 0.6), 0 0 0 1.5px var(--nf-stroke-50); }
.konek-owner { margin-right: 4px; font-size: 14px; }

/* ── 43. Ran (x) · Max (y) ──────────────────────────────────────────────── */
.lb-me-line { margin: 0 0 8px; text-align: center; font-weight: 700; color: var(--nf-blue); font-size: 15px; }

/* ── 44. Eight at a table; the UI's language ─────────────────────────────── */
.konek-dirrow.is-full { opacity: .55; cursor: default; }
.ui-lang-row { display: flex; align-items: center; justify-content: space-between; gap: 10px; margin: 2px 0 6px; font-size: 14px; font-weight: 700; color: var(--nf-blue); }
.ui-lang { font: inherit; font-size: 15px; padding: 6px 10px; border-radius: 10px; border: 1px solid var(--nf-frame-stroke); background: var(--nf-disc); color: inherit; }

/* ── 45. Five tables showing, the rest by scrolling ─────────────────────
   A row is two lines in a 12px-padded card, about 58px with its gap;
   five of them is the window. Sixteen tables should not push the code
   field and Create off the screen. */
.konek-dirlist { max-height: 296px; overflow-y: auto; padding-right: 2px; scrollbar-width: thin; }

/* ── 46. The administrator's line on the home screen ────────────────────
   A banner is one sentence above the cards. Maintenance is the same line
   in the game's red, with the cards greyed and untappable; the links below
   them stay live — reading the board is not playing. */
.home-notice { margin: 0 16px 10px; padding: 10px 14px; border-radius: 12px; background: rgba(59, 124, 255, .12); color: #1c2430; font-size: 14px; line-height: 1.35; text-align: center; }
.home-notice--maint { background: rgba(210, 16, 52, .12); color: #A80D2A; font-weight: 600; }
.mode-cards.is-maint .mode-card { opacity: .45; pointer-events: none; }

/* ── 47. The loop: the table as a place worth coming back to ─────────────
   Ask the name at the table, not before it; a ready check that starts the
   table itself; a countdown every phone shows together; lanes as bones;
   a banner when someone comes home; the standings first on the end
   screen, then the week's ledger. */
.konek-askname { width: 100%; max-width: 360px; margin: 0 0 10px; padding: 12px; border-radius: 14px; background: var(--nf-disc); border: 1.5px solid var(--nf-purple-solid); }
.konek-askname label { display: block; font-weight: 800; font-size: 15px; margin-bottom: 8px; color: var(--nf-blue); }
.konek-watchnote { margin: 0 0 8px; font-size: 13px; opacity: .8; text-align: center; }
.konek-watchlink { margin: 8px 0 0; font-size: 13px; }   /* last on the table screen, under Kite tab la */
.konek-ready { width: 100%; max-width: 360px; margin: 6px 0 2px; }
.konek-ready.is-on { background: var(--nf-purple-solid); color: #fff; border-color: transparent; }
.konek-row .konek-ok { margin-left: auto; font-size: 13px; }
.konek-row .konek-h2h { margin-left: 6px; font-size: 11px; opacity: .7; white-space: nowrap; }
.konek-teamnames { display: flex; gap: 6px; width: 100%; max-width: 360px; margin: -2px 0 6px; }
.konek-teamnames input { flex: 1; min-width: 0; padding: 7px 9px; border-radius: 10px; border: 1px solid var(--nf-frame-stroke); font: inherit; font-size: 13px; }
.konek-ledgerh { margin-top: 8px; }
.konek-ledger { margin: 0 0 6px; font-size: 13px; text-align: center; opacity: .85; }
.konek-mode { flex-wrap: wrap; }
.konek-mode .konek-team { flex: 1 1 45%; }

/* bones on the lane: one per word, face-up as they land; the lane flashes on a landing */
.konek-lane__bones { flex: 1; display: flex; gap: 2px; align-items: center; }
.konek-lane__bones i { flex: 1; height: 9px; border-radius: 3px; background: rgba(84, 44, 194, 0.12); transition: background .25s; }
.konek-lane__bones i.is-on { background: linear-gradient(90deg, var(--nf-purple-solid), var(--nf-blue)); }
.konek-lane__bones i.is-miss { background: linear-gradient(90deg, #e8896e, #c8341a); }   /* played, not cleared: a pass or a collapse — a clear red bar */
.konek-lane.is-team-a .konek-lane__bones i.is-on { background: linear-gradient(90deg, #6fb3e0, #2f7fb4); }
.konek-lane.is-team-b .konek-lane__bones i.is-on { background: linear-gradient(90deg, #e0a06f, #b4562f); }
.konek-lane.is-hit { animation: laneHit .5s ease-out; }
@keyframes laneHit { 0% { transform: translateX(0); background: rgba(255, 209, 102, .45); } 100% { transform: translateX(0); background: transparent; } }
.konek-lane.is-lead .konek-lane__name::after { content: ' 🥇'; }

.konek-clock { text-align: center; font-weight: 800; font-size: 15px; color: var(--nf-blue); margin: -2px 0 2px; font-variant-numeric: tabular-nums; }
.konek-clock.is-low { color: #D21034; animation: clockLow 1s steps(2) infinite; }
@keyframes clockLow { 50% { opacity: .45; } }
.konek-banner { position: absolute; left: 50%; top: 34%; transform: translateX(-50%); z-index: 6; padding: 10px 16px; border-radius: 14px; background: rgba(28, 20, 60, .92); color: #fff; font-weight: 800; font-size: 16px; white-space: nowrap; box-shadow: 0 8px 26px rgba(0,0,0,.35); animation: bannerIn .35s ease-out; }
@keyframes bannerIn { from { opacity: 0; transform: translate(-50%, 8px) scale(.96); } to { opacity: 1; transform: translate(-50%, 0) scale(1); } }
.konek-count { position: fixed; inset: 0; z-index: 40; display: grid; place-content: center; text-align: center; background: rgba(238, 240, 251, .86); backdrop-filter: blur(3px); }
.konek-count b { display: block; font: 800 96px/1 Graduate, Georgia, serif; color: var(--nf-purple-solid); animation: countPop .9s ease-out infinite; }
.konek-count span { display: block; margin-top: 10px; font-weight: 700; color: var(--nf-blue); }
@keyframes countPop { 0% { transform: scale(1.25); opacity: .4; } 30% { transform: scale(1); opacity: 1; } 100% { transform: scale(1); opacity: 1; } }
@media (prefers-reduced-motion: reduce) { .konek-count b, .konek-lane.is-hit, .konek-clock.is-low { animation: none; } }

.ans-card__teams { margin: 4px 0 6px; font-weight: 800; text-align: center; color: var(--nf-blue); }
.ans-card__ledger { margin: 8px 0 0; font-size: 12.5px; text-align: center; opacity: .8; }
.ans-card.is-waiting .ans-card__crown { animation: countPop 1.6s ease-in-out infinite; }
#ans-slot-top .ans-card { margin-bottom: 10px; }


/* ================================================================
   v1.87 — appended, wins the cascade
   ================================================================ */

/* the table's name wore the browser's default box next to a styled "Non ou" */
#konek-tabname {
  padding: 10px 12px;
  border: 1px solid var(--nf-frame-stroke);
  border-radius: 12px;
  background: var(--nf-card);
  font-family: inherit;
  font-size: 0.95rem;
  color: var(--nf-blue);
}

/* home: the links follow the cards instead of hanging at the bottom of a
   932px canvas with 150px of nothing above them; the brand mark alone
   holds the bottom edge */
.home-links { margin-top: 14px; }
.home-known { margin-top: 12px; }
.home-known[hidden] + .home-best { margin-top: 12px; }

/* the open slot breathes when a word is dealt: three slow pulses say
   "here", on a felt that is mostly painting on the first word */
.dom-slot.is-next { animation: domNextPulse 1.1s ease-in-out 3; }
@keyframes domNextPulse {
  0%, 100% { box-shadow: 0 0 12px rgba(255, 209, 102, 0.35); }
  50% { box-shadow: 0 0 0 4px rgba(255, 209, 102, 0.7), 0 0 20px 6px rgba(255, 209, 102, 0.45); }
}
@media (prefers-reduced-motion: reduce) { .dom-slot.is-next { animation: none; } }

/* THE SPARE HEIGHT GOES TO THE PICTURE, NOT UNDER THE CARDS. .mode-cards
   carried flex-grow, so on a full-height canvas the list stretched and left
   its own bottom empty — 150px of nothing between Ansanm and the links. The
   cards are content-sized now and the hero absorbs what is left, within
   bounds, so the links sit right under the last card at every height. */
.mode-cards { flex: 0 0 auto; }
.home-hero { height: auto; flex: 1 1 245px; min-height: 160px; max-height: 320px; }
.home-hero img { height: 100%; }

/* the recap's definition panel: Koleksyon's, but in the flow of the end
   screen rather than stuck to a scroller's top; the lit row is the word.
   The reserved height (--kol-bar) stays: without it the panel grows when it
   answers, the rows shift under the resting pointer, and the row that just
   spoke loses its light to the pointerout the shift itself raises. */
#screen-end { --kol-bar: 4.3rem; }          /* the reserve is declared per screen; see #screen-koleksyon */
@media (max-width: 479px) { #screen-end { --kol-bar: 5.4rem; } }
.recap-def { position: static; margin: 4px 0 8px; }
.recap-row.is-preview { background: rgba(84, 44, 194, 0.09); cursor: default; }

/* Apwopo: the button that reads the Kreyòl text; lit while Kekè speaks */
.about-play { display: block; margin: 6px 0 12px; }
.about-play.is-on { background: var(--nf-purple-solid); color: #fff; border-color: var(--nf-purple-solid); }

/* ================================================================
   appended, wins the cascade

   THE PALEFÒ MARK RETURNS, AND THE HERO MAKES ROOM FOR IT.

   Section 5 up the file parked the brand mark (display:none) when it
   was Techtra Labs; the app is Palefò's now and its wordmark closes
   the home screen the way it closes FotoLekti. palefo-logo.png is
   fotolekti's Palefo_Text_Vector.png resized to 640px: the original
   is 4340px and 680KB for a 58px-tall footer. Real alpha channel —
   same warning as the Techtra note: no mix-blend-mode tricks.

   The hero gives up the height: its flex basis drops 245 → 170 with
   the ceiling at 190. The new hero.jpg is 3150×1344 (2.34:1), which at
   the 92%/430px width renders ~169px tall — so 170 shows the whole
   picture under object-fit: cover instead of cropping a strip out of
   the middle of a taller box. Only the LAST .home-hero rule is live;
   change the height here.
   ================================================================ */
.home-hero { flex: 1 1 170px; min-height: 96px; max-height: 190px; }
/* #screen-home > * pins flex-shrink:0 on every child up the file (id +
   universal outranks the class), so the hero never actually gave height
   back on a short phone — it sat at its basis and pushed the footer off
   the frame. id + class wins it back: the picture shrinks to min-height
   before anything else moves. */
#screen-home > .home-hero { flex-shrink: 1; }
@media (max-height: 639px) { .home-hero { min-height: 72px; max-height: 120px; } }

.brand-mark { display: flex; margin-top: auto; padding: 10px 0 8px; }
.brand-mark img { height: 58px; width: auto; opacity: 0.92; }
@media (max-height: 639px) { .brand-mark { padding: 6px 0 4px; } .brand-mark img { height: 47px; } }

/* ================================================================
   PATAJE SON AN + FÈ YON KLIP, PARKED. Both end-screen buttons are off
   for now. The game.js logic that lights them (konekCrowned, canClip,
   clipSupported) is untouched and still flips their hidden attribute;
   an id rule outranks the UA's [hidden] display:none, so they stay off
   whatever it decides. To bring them back, delete this rule.
   ================================================================ */
#son-btn, #klip-btn { display: none; }
