  :root {
    --bg-top: #4A0080;
    --bg-bottom: #0066FF;
    --gold: #FFD700;
    --gold-deep: #FFA800;
    /* Completed-tile colours: hot pink marks the answer found, vibrant
       purple marks the ones ruled out. */
    --win: #FF44AA;
    --win-hi: #FF85C8;
    --win-lo: #CC2280;
    --spent: #8833CC;
    --spent-hi: #B370E8;
    --spent-lo: #661F9E;
    --danger: #FF2D2D;
    --go: #44CC44;
    --go-dark: #2E9E2E;
    --ink-on-colour: #FFFFFF;
  }

  * { box-sizing: border-box; -webkit-tap-highlight-color: transparent; }

  html, body {
    margin: 0;
    padding: 0;
    color: var(--ink-on-colour);
  }

  body {
    font-family: 'Nunito', system-ui, sans-serif;
    font-weight: 700;
    min-height: 100vh;
    display: flex;
    justify-content: center;
    /* Fixed rich gradient — content scrolls over it. */
    background: linear-gradient(180deg, var(--bg-top) 0%, #6A00C0 35%, #1A4FE0 75%, var(--bg-bottom) 100%);
    background-attachment: fixed;
    overflow-x: hidden;
  }

  /* ---------- Drifting background blobs ---------- */
  .blobs {
    position: fixed;
    inset: 0;
    z-index: 0;
    overflow: hidden;
    pointer-events: none;
  }

  .blob {
    position: absolute;
    border-radius: 50%;
    filter: blur(28px);
    opacity: 0.22;
    animation: drift 34s ease-in-out infinite alternate;
  }

  .blob:nth-child(1) { width: 260px; height: 260px; background: #FF44AA; top: 6%;  left: -70px; animation-duration: 38s; }
  .blob:nth-child(2) { width: 200px; height: 200px; background: #00CCBB; top: 34%; right: -60px; animation-duration: 46s; animation-delay: -8s; }
  .blob:nth-child(3) { width: 300px; height: 300px; background: #FFD700; top: 62%; left: -90px; animation-duration: 52s; animation-delay: -18s; opacity: 0.16; }
  .blob:nth-child(4) { width: 180px; height: 180px; background: #8844FF; top: 86%; right: -40px; animation-duration: 42s; animation-delay: -26s; }

  @keyframes drift {
    from { transform: translate3d(0, 0, 0) scale(1); }
    to   { transform: translate3d(46px, -60px, 0) scale(1.18); }
  }

  .game-container {
    position: relative;
    z-index: 1;
    width: 100%;
    max-width: 480px;
    min-height: 100vh;
    padding: 0 20px 70px;
    display: flex;
    flex-direction: column;
    align-items: center;
  }

  .game-container.shake-screen { animation: screenShake 0.5s ease; }

  @keyframes screenShake {
    0%, 100% { transform: translate(0, 0); }
    15% { transform: translate(-9px, 3px); }
    30% { transform: translate(8px, -4px); }
    45% { transform: translate(-7px, 2px); }
    60% { transform: translate(6px, -3px); }
    80% { transform: translate(-4px, 1px); }
  }

  /* ---------- Header ---------- */
  .game-header {
    width: 100%;
    text-align: center;
    padding: 26px 0 18px;
    flex-shrink: 0;
  }

  .title {
    font-family: 'Fredoka One', 'Nunito', sans-serif;
    font-weight: 400;
    font-size: 52px;
    letter-spacing: 0.02em;
    margin: 0;
    line-height: 1;
    background: linear-gradient(90deg, #FFD700 0%, #FFB300 45%, #FF6B00 100%);
    -webkit-background-clip: text;
    background-clip: text;
    color: transparent;
    -webkit-text-fill-color: transparent;
    /* Shadow sits on a wrapper because the text itself is clipped to gradient. */
    filter: drop-shadow(0 4px 0 rgba(0, 0, 0, 0.28)) drop-shadow(0 0 18px rgba(255, 200, 0, 0.35));
  }

  .category-name {
    font-family: 'Nunito', sans-serif;
    font-size: 15px;
    font-weight: 900;
    letter-spacing: 0.1em;
    text-transform: uppercase;
    color: #FFFFFF;
    margin: 14px 0 0;
    min-height: 18px;
    text-shadow: 0 2px 6px rgba(0, 0, 0, 0.35);
  }

  .category-description {
    font-family: 'Nunito', sans-serif;
    font-size: 14px;
    font-weight: 600;
    line-height: 1.4;
    color: rgba(255, 255, 255, 0.78);
    margin: 6px 0 0;
    min-height: 18px;
  }

  .meta-line {
    font-family: 'Nunito', sans-serif;
    font-size: 13px;
    font-weight: 800;
    letter-spacing: 0.06em;
    text-transform: uppercase;
    color: #FFFFFF;
    margin: 14px 0 0;
  }

  .meta-line span {
    display: inline-block;
    background: rgba(0, 0, 0, 0.22);
    border-radius: 999px;
    padding: 5px 14px;
    margin: 0 3px;
  }

  /* ---------- Tower ---------- */
  .tower-wrap {
    position: relative;
    width: 100%;
  }

  /* Gold spine threading the rows, glowing softly. */
  .progress-line {
    position: absolute;
    top: 6px;
    bottom: 6px;
    left: 50%;
    width: 5px;
    margin-left: -2.5px;
    border-radius: 999px;
    background: linear-gradient(180deg, rgba(255, 215, 0, 0.15), var(--gold) 18%, var(--gold-deep));
    animation: spineGlow 2.4s ease-in-out infinite;
    z-index: 0;
  }

  .progress-line[hidden] { display: none; }

  @keyframes spineGlow {
    0%, 100% { box-shadow: 0 0 8px rgba(255, 215, 0, 0.45); opacity: 0.75; }
    50%      { box-shadow: 0 0 20px rgba(255, 215, 0, 0.95); opacity: 1; }
  }

  .tower {
    position: relative;
    z-index: 1;
    width: 100%;
    display: flex;
    flex-direction: column;
    gap: 16px;
  }

  .row {
    width: 100%;
    display: flex;
    justify-content: center;
    gap: 8px;
    opacity: 1;
    transform: translateY(0);
  }

  .row.entering {
    animation: rowDrop 0.5s cubic-bezier(0.34, 1.56, 0.64, 1);
  }

  /* Solution reveal: each row fades up in turn, climbing from the bottom of
     the tower so it reads as an unveiling rather than a data dump. */
  .row.reveal {
    opacity: 0;
    animation: revealRow 0.45s ease forwards;
  }

  @keyframes revealRow {
    from { opacity: 0; transform: translateY(16px) scale(0.97); }
    to   { opacity: 1; transform: translateY(0) scale(1); }
  }

  @keyframes rowDrop {
    0%   { opacity: 0; transform: translateY(-38px) scale(0.94); }
    70%  { opacity: 1; transform: translateY(4px) scale(1.02); }
    100% { opacity: 1; transform: translateY(0) scale(1); }
  }

  /* Unplayed tile colours: vivid blue, green and orange, cycling down the
     tower. White text throughout, lifted by a shadow on every tile.
     White-text contrast against the base colour:
       blue   #1a1aff -> 7.97:1
       green  #00aa00 -> 3.11:1
       orange #e65c00 -> 3.56:1
     Green and orange sit below the 4.5:1 that 13px bold nominally wants;
     the shadow is what carries them in practice. The gloss highlights are
     kept close to the base rather than markedly lighter — a bright top to
     the gradient is where white text actually fails, and text sits over the
     middle of the tile where the base colour dominates.
     Only unplayed tiles use these; correct and spent colours override them
     once a tile has been answered. */
  .row[data-row="1"],
  .row[data-row="4"],
  .row[data-row="7"] { --c: #1a1aff; --c-lo: #0000cc; --c-hi: #4d4dff; }

  .row[data-row="2"],
  .row[data-row="5"],
  .row[data-row="8"] { --c: #00aa00; --c-lo: #007a00; --c-hi: #00c400; }

  .row[data-row="3"],
  .row[data-row="6"],
  .row[data-row="9"] { --c: #e65c00; --c-lo: #a84300; --c-hi: #ff7014; }

  /* Every tile is exactly 78 x 72px in every row and every state. The
     2/3/4/3/2 wave comes purely from how many tiles a row holds — rows are
     centred, so a 2-tile row sits narrow with space either side.
     flex-shrink stays on as a safety valve: a 4-tile row needs 336px, so on
     a viewport under ~356px four 78px tiles cannot physically fit, and
     shrinking together beats overflowing the page. */
  .tile {
    flex: 0 1 78px;
    width: 78px;
    max-width: 78px;
    min-width: 0;
    box-sizing: border-box;
    height: 72px;
    padding: 6px;
    display: flex;
    flex-direction: column;
    align-items: center;
    justify-content: center;
    text-align: center;
    position: relative;
    border-radius: 16px;
    /* Glossy candy: lighter at the top, solid colour below. */
    background: linear-gradient(180deg, var(--c-hi) 0%, var(--c) 46%, var(--c) 100%);
    border: 2px solid var(--c-lo);
    box-shadow: 0 6px 0 rgba(0, 0, 0, 0.3);
    /* White text on every tile, in every state. */
    color: #FFFFFF;
    font-family: 'Nunito', sans-serif;
    /* One uniform size on every tile in every state — no adaptive sizing.
       Text that doesn't fit wraps, and anything past the fixed height is
       clipped by .tile-text's overflow: hidden. */
    font-size: 13px;
    font-weight: 800;
    line-height: 1.2;
    text-shadow: 0 1px 4px rgba(0, 0, 0, 0.6);
    cursor: pointer;
    user-select: none;
    transition: transform 0.12s ease, box-shadow 0.12s ease, background 0.3s ease,
      border-color 0.3s ease, opacity 0.3s ease;
  }

  /* Glossy highlight sweep across the top of every tile. */
  .tile::after {
    content: '';
    position: absolute;
    inset: 2px 2px auto 2px;
    height: 38%;
    border-radius: 12px 12px 60% 60% / 12px 12px 22px 22px;
    background: linear-gradient(180deg, rgba(255, 255, 255, 0.42), rgba(255, 255, 255, 0));
    pointer-events: none;
  }

  /* The ✓/✗ badge is out of the layout flow: pinned to the corner, adding no
     height, so badged and un-badged tiles are dimensionally identical. */
  .tile-mark-row {
    position: absolute;
    top: 3px;
    right: 5px;
    display: flex;
    align-items: flex-start;
    justify-content: flex-end;
    pointer-events: none;
    z-index: 2;
  }

  .mark {
    width: 15px;
    height: 15px;
    flex-shrink: 0;
    display: flex;
    align-items: center;
    justify-content: center;
    font-size: 11px;
    font-family: 'Nunito', sans-serif;
    font-weight: 900;
    line-height: 1;
  }

  .tile-text {
    position: relative;
    z-index: 1;
    flex: 1;
    min-height: 0;
    display: flex;
    flex-direction: column;
    align-items: center;
    justify-content: center;
    text-align: center;
    width: 100%;
    /* Words are never split. The ladder shrinks 13 -> 12 -> 11 instead, and
       only if a word still cannot fit at the floor does .allow-break let it
       break, as an absolute last resort. */
    overflow-wrap: normal;
    word-break: normal;
    hyphens: none;
    /* Tile height is fixed, so text past the available lines is clipped
       rather than allowed to grow the box. */
    overflow: hidden;
  }

  /* Last resort, applied by the ladder only when a single word still does not
     fit at the 11px floor — better a broken word than one running off the
     tile and being cut mid-air. */
  .tile-text.allow-break {
    overflow-wrap: break-word;
    word-break: break-word;
  }

  /* Two-part answers stack as two centred lines rather than using a dash. */
  .tile-line {
    display: block;
    width: 100%;
    text-align: center;
  }

  /* Italic to mark it as prose, but the same 13px as every other tile —
     the full sentence is always shown in the message box below the row. */
  .tile-text.is-explanation {
    font-style: italic;
    font-weight: 700;
    line-height: 1.2;
  }

  /* --- Press --- */
  .row.active .tile:hover:not(.disabled) { filter: brightness(1.06); }

  .row.active .tile:active:not(.disabled) {
    transform: translateY(4px);
    box-shadow: 0 2px 0 rgba(0, 0, 0, 0.3);
  }

  .tile.disabled { cursor: default; }

  /* --- Correct --- */
  .tile.correct,
  .tile.revealed-correct {
    background: linear-gradient(180deg, var(--win-hi) 0%, var(--win) 46%, var(--win) 100%);
    border-color: var(--win-lo);
    color: #FFFFFF;
    text-shadow: 0 1px 2px rgba(0, 0, 0, 0.35);
  }

  .tile.correct { animation: burst 0.55s cubic-bezier(0.34, 1.56, 0.64, 1); z-index: 3; }

  @keyframes burst {
    0%   { transform: scale(1); }
    45%  { transform: scale(1.2); box-shadow: 0 6px 0 rgba(0,0,0,0.3), 0 0 34px rgba(255, 68, 170, 0.95); }
    100% { transform: scale(1); }
  }

  .sparkle {
    position: absolute;
    inset: 0;
    display: flex;
    align-items: center;
    justify-content: center;
    font-size: 26px;
    pointer-events: none;
    animation: sparklePop 0.9s ease forwards;
    z-index: 4;
  }

  @keyframes sparklePop {
    0%   { opacity: 0; transform: scale(0.4) rotate(-20deg); }
    35%  { opacity: 1; transform: scale(1.25) rotate(8deg); }
    100% { opacity: 0; transform: scale(1.5) rotate(20deg); }
  }

  /* --- Wrong / spent --- */
  .tile.wrong {
    background: linear-gradient(180deg, #FF6B6B 0%, var(--danger) 50%);
    border-color: #A80000;
    animation: violentShake 0.55s ease;
  }

  @keyframes violentShake {
    0%, 100% { transform: translateX(0); }
    8%  { transform: translateX(-8px) rotate(-3deg); }
    24% { transform: translateX(8px) rotate(3deg); }
    40% { transform: translateX(-7px) rotate(-2deg); }
    56% { transform: translateX(7px) rotate(2deg); }
    72% { transform: translateX(-5px) rotate(-1deg); }
    88% { transform: translateX(4px); }
  }

  /* "Spent" rather than dull: a vibrant purple that still reads as finished
     next to the hot-pink correct tile, without going dark. */
  .tile.muted {
    background: linear-gradient(180deg, var(--spent-hi) 0%, var(--spent) 50%);
    border-color: var(--spent-lo);
    box-shadow: 0 4px 0 rgba(0, 0, 0, 0.3);
    color: #FFFFFF;
    text-shadow: 0 1px 2px rgba(0, 0, 0, 0.35);
  }

  /* ---------- Wrong-answer message ---------- */
  .row-message {
    width: 100%;
    background: linear-gradient(180deg, #FF5A5A 0%, #E01414 100%);
    border: 3px solid #A80000;
    border-radius: 18px;
    padding: 14px 16px;
    font-family: 'Nunito', sans-serif;
    font-size: 15px;
    font-weight: 700;
    line-height: 1.45;
    color: #FFFFFF;
    box-shadow: 0 6px 0 rgba(0, 0, 0, 0.28);
    text-align: center;
  }

  .row-message.entering { animation: rowDrop 0.5s cubic-bezier(0.34, 1.56, 0.64, 1); }
  .row-message strong { font-weight: 900; }

  /* ---------- End panel ---------- */
  .end-panel-block {
    width: 100%;
    text-align: center;
    background: rgba(255, 255, 255, 0.13);
    border: 3px solid rgba(255, 255, 255, 0.35);
    border-radius: 22px;
    padding: 26px 20px;
    backdrop-filter: blur(6px);
    box-shadow: 0 8px 0 rgba(0, 0, 0, 0.22);
  }

  .end-panel-block.entering { animation: rowDrop 0.55s cubic-bezier(0.34, 1.56, 0.64, 1); }

  /* The win screen arrives after a beat, so it eases in rather than bouncing
     — a straight opacity fade reads as calmer after the tile's green flash. */
  .end-panel-block.win-fade {
    animation: winFade 0.5s ease forwards;
  }

  @keyframes winFade {
    from { opacity: 0; transform: none; }
    to   { opacity: 1; transform: none; }
  }

  .end-headline {
    font-family: 'Fredoka One', 'Nunito', sans-serif;
    font-weight: 400;
    font-size: 34px;
    line-height: 1.1;
    margin: 0 0 12px;
  }

  .end-headline.win {
    background: linear-gradient(90deg, #FFD700, #FF8C00 40%, #FF44AA 70%, #4488FF);
    -webkit-background-clip: text;
    background-clip: text;
    color: transparent;
    -webkit-text-fill-color: transparent;
    filter: drop-shadow(0 3px 0 rgba(0, 0, 0, 0.3));
  }

  .end-headline.lose {
    color: #FFFFFF;
    text-shadow: 0 0 16px rgba(255, 45, 45, 0.95), 0 0 34px rgba(255, 45, 45, 0.6), 0 3px 0 rgba(0, 0, 0, 0.3);
  }

  .end-sub {
    font-family: 'Nunito', sans-serif;
    font-size: 15px;
    font-weight: 800;
    letter-spacing: 0.04em;
    color: #FFFFFF;
    margin: 0 0 20px;
    text-transform: uppercase;
  }

  /* The white box owns the clipping and the fade; the paragraph inside just
     holds text. Keeping them separate matters: -webkit-line-clamp needs
     display:-webkit-box, and a ::after inside that box is treated as a box
     child, which is what let text bleed out behind the Read more link. A
     plain max-height on the paragraph avoids that entirely. */
  .fact-box {
    position: relative;
    background: #FFFFFF;
    border-radius: 14px;
    padding: 12px 14px;
    margin: 0 0 8px;
    box-shadow: 0 4px 0 rgba(0, 0, 0, 0.2);
    overflow: hidden;
  }

  .completion-fact {
    font-family: 'Nunito', sans-serif;
    font-size: 14px;
    font-weight: 600;
    line-height: 1.5;
    color: #2A1548;
    margin: 0;
    text-align: left;
  }

  /* Exactly three lines: 3 x 1.5 line-height. */
  .fact-box.collapsed .completion-fact {
    max-height: 4.5em;
    overflow: hidden;
  }

  /* Softens the cut so it reads as deliberate rather than broken. Sits above
     the text but takes no clicks, and is removed entirely when expanded. */
  .fact-box.collapsed::after {
    content: '';
    position: absolute;
    left: 0;
    right: 0;
    bottom: 0;
    height: 32px;
    background: linear-gradient(180deg, rgba(255, 255, 255, 0) 0%, #FFFFFF 80%);
    pointer-events: none;
  }

  .fact-toggle {
    display: block;
    margin-left: auto;
    margin-right: auto;
    font-family: 'Nunito', sans-serif;
    font-size: 13px;
    font-weight: 800;
    letter-spacing: 0.03em;
    color: var(--gold);
    background: none;
    border: none;
    padding: 4px 0;
    margin: 0 0 14px;
    cursor: pointer;
    text-decoration: underline;
  }

  .fact-toggle[hidden] { display: none; }

  .end-reached {
    font-family: 'Nunito', sans-serif;
    font-size: 17px;
    font-weight: 900;
    color: var(--gold);
    margin: 0 0 16px;
  }

  .exhausted-note {
    font-family: 'Nunito', sans-serif;
    font-size: 14px;
    font-weight: 700;
    line-height: 1.5;
    color: #FFFFFF;
    background: rgba(0, 0, 0, 0.28);
    border: 2px solid rgba(255, 255, 255, 0.3);
    border-radius: 14px;
    padding: 14px 16px;
    margin: 0 0 16px;
  }

  .btn {
    font-family: 'Nunito', sans-serif;
    font-size: 17px;
    font-weight: 900;
    letter-spacing: 0.04em;
    text-transform: uppercase;
    border: none;
    border-radius: 16px;
    padding: 17px 24px;
    cursor: pointer;
    width: 100%;
    color: #FFFFFF;
    background: linear-gradient(180deg, #7CE07C 0%, var(--go) 50%);
    border-bottom: 5px solid var(--go-dark);
    box-shadow: 0 5px 0 rgba(0, 0, 0, 0.22);
    transition: transform 0.12s ease, box-shadow 0.12s ease;
    text-shadow: 0 1px 2px rgba(0, 0, 0, 0.3);
  }

  .btn:active {
    transform: translateY(4px);
    box-shadow: 0 1px 0 rgba(0, 0, 0, 0.22);
  }

  .retry-btn {
    margin-bottom: 12px;
    animation: btnBounce 1.9s ease-in-out infinite;
  }

  @keyframes btnBounce {
    0%, 82%, 100% { transform: translateY(0); }
    88% { transform: translateY(-7px); }
    94% { transform: translateY(-3px); }
  }

  .btn.gold {
    background: linear-gradient(180deg, #FFEE88 0%, var(--gold) 50%);
    border-bottom-color: #C79A00;
    color: #6A4B00;
    text-shadow: 0 1px 0 rgba(255, 255, 255, 0.5);
  }

  .share-status {
    font-family: 'Nunito', sans-serif;
    font-size: 13px;
    font-weight: 700;
    color: var(--gold);
    margin: 12px 0 0;
    min-height: 16px;
  }

  .load-state {
    font-family: 'Nunito', sans-serif;
    font-size: 15px;
    font-weight: 700;
    color: rgba(255, 255, 255, 0.85);
    text-align: center;
    margin-top: 44px;
  }

  /* ---------- Win effects ---------- */
  .confetti-layer {
    position: fixed;
    inset: 0;
    z-index: 40;
    pointer-events: none;
    overflow: hidden;
  }

  .confetti {
    position: absolute;
    top: -24px;
    width: 11px;
    height: 16px;
    border-radius: 2px;
    opacity: 0.95;
    animation: confettiFall linear forwards;
  }

  @keyframes confettiFall {
    0%   { transform: translateY(-24px) rotate(0deg); opacity: 1; }
    100% { transform: translateY(105vh) rotate(760deg); opacity: 0.9; }
  }

  .rainbow-flash {
    position: fixed;
    inset: 0;
    z-index: 39;
    pointer-events: none;
    mix-blend-mode: screen;
    animation: rainbowFlash 1.5s ease forwards;
  }

  @keyframes rainbowFlash {
    0%   { background: rgba(255, 215, 0, 0); }
    15%  { background: rgba(255, 215, 0, 0.5); }
    35%  { background: rgba(255, 68, 170, 0.45); }
    55%  { background: rgba(68, 136, 255, 0.45); }
    75%  { background: rgba(68, 204, 68, 0.4); }
    100% { background: rgba(255, 255, 255, 0); }
  }

  @media (max-width: 400px) {
    .game-container { padding-left: 10px; padding-right: 10px; }
    .title { font-size: 44px; }
  }

  /* Respect a reduced-motion preference: keep the colour, drop the movement. */
  @media (prefers-reduced-motion: reduce) {
    .blob, .progress-line, .retry-btn { animation: none; }
    .row.reveal, .row.entering, .end-panel-block.entering, .row-message.entering,
    .tile.correct, .tile.wrong, .game-container.shake-screen,
    .confetti, .rainbow-flash, .sparkle { animation-duration: 0.01ms; animation-iteration-count: 1; }
  }
