/* ============================================================
   スタイルシート
   index.html から分離。<link rel="stylesheet" href="style.css">
   で読み込まれる。
   ============================================================ */
    /* ============================================================
       テーマカラー（ショップで引き換えるメインカラー）
       これまで直書きしていた #00ffcc は、すべて var(--tc) と
       rgba(var(--tc-rgb), a) に置き換えてある。ここの2つを書き
       換えるだけで、ボタン・枠線・発光・スライダーまで画面全体の
       アクセントが一度に切り替わる。
       既定値はこれまでと同じミント (#00ffcc)。実際の値は
       ショップ(Shop.applyTheme)が :root に上書きして流し込む。
       --tc-rgb は rgba() の中で使うため、カンマ区切りの数値で持つ。
       ============================================================ */
    :root {
      --tc: #00ffcc;
      --tc-rgb: 0, 255, 204;
    }
    * {
      box-sizing: border-box;
      margin: 0;
      padding: 0;
      user-select: none;
      -webkit-user-select: none;
      -webkit-tap-highlight-color: transparent;
    }
    /* This is a touch-only interface, so a tapped control should never show
       the browser's default focus box -- most visibly on the round OLL/PLL
       buttons, where that rectangle sits obviously outside the circle, but
       just as much on the face-turn cards, which are divs with
       tabindex="0" rather than real <button> elements and so need the same
       reset applied explicitly. appearance is reset too, since on iOS the
       native control chrome -- not just the CSS outline -- is what actually
       draws that leftover black rectangle. Real keyboard/switch-control
       navigation still gets a visible ring via :focus-visible. */
    button {
      -webkit-appearance: none;
      appearance: none;
    }
    button,
    .face-card,
    [tabindex],
    input[type="range"] {
      outline: none;
    }
    button:focus-visible,
    .face-card:focus-visible,
    [tabindex]:focus-visible,
    input[type="range"]:focus-visible {
      outline: 2px solid var(--tc);
      outline-offset: 2px;
    }
    html {
      /* splash 画像と全く同じ色にしておくことで、万一 splash から本体への
         切り替えの間に一瞬 html の背景が見えても継ぎ目が出ない。 */
      background-color: #121214;
      /* iPad Safari で「揃え終わった瞬間に画面が上下にビョンと揺れる」
         不具合の対策。原因はページ本体(html)のラバーバンド(慣性)スクロール:
         最後の一手をスワイプした指を離すタイミングと重なると、body側の
         overflow:hidden だけでは防ぎきれず、html(ドキュメント自体)が
         一瞬オーバースクロールしてから跳ね返る。html 自体をビューポートに
         固定して伸縮スクロールできなくすることで、この跳ね返りを止める。 */
      height: 100%;
      overflow: hidden;
      overscroll-behavior: none;
    }
    /* ============================================================
       言語ごとの本文フォント
       言語を選ぶと <html lang> が切り替わり、次の「回し方を選ぶ」画面
       以降すべての文字がその言語用の書体で出る。
       日本語の書体で中国語や韓国語を出すと、字形が違う (直/骨/類 など)
       か、そもそも字が無くて OS の適当な代替に落ち、1画面の中で
       行の太さや高さが揃わなくなる。そのため font-family を1つに
       固定せず、言語ごとに変数で差し替える。
       追加ダウンロードは行わず、各 OS に元から入っている書体だけを
       並べる (Noto 系は Android/Linux 用のフォールバック)。
       変数は [lang="..."] に載せているので、html だけでなく
       言語選択の9マスのように「その要素だけ別言語」の場所にも
       lang 属性を付ければ同じ仕組みでそのまま効く。
       ============================================================ */
    :root {
      --font-latin: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto,
                    "Helvetica Neue", Arial, sans-serif;
      /* 見出し・本文の既定 */
      --ui-font: var(--font-latin);
      /* 補足文 (やわらかい丸ゴシック系を使いたい所) */
      --ui-font-soft: var(--font-latin);
      /* 補足文の太さ。細字は CJK だと線が消えて読みにくいので
         言語ごとに上書きできるようにしておく。 */
      --ui-weight-light: 300;
    }
    [lang="ja"] {
      --ui-font: "Hiragino Sans", "Hiragino Kaku Gothic ProN", "Yu Gothic",
                 "YuGothic", "Noto Sans JP", var(--font-latin);
      --ui-font-soft: "Hiragino Maru Gothic ProN", "ヒラギノ丸ゴ ProN W4",
                      "Hiragino Maru Gothic Pro", "Yu Gothic", "YuGothic",
                      "Noto Sans JP", var(--font-latin);
      /* 日本語は元のまま。丸ゴシックは細字でも線が残る。 */
      --ui-weight-light: 300;
    }
    [lang="zh-CN"] {
      --ui-font: "PingFang SC", "Hiragino Sans GB", "Microsoft YaHei",
                 "Noto Sans SC", "Source Han Sans SC", var(--font-latin);
      --ui-font-soft: var(--ui-font);
      --ui-weight-light: 400;
    }
    [lang="zh-TW"] {
      --ui-font: "PingFang TC", "Microsoft JhengHei", "Noto Sans TC",
                 "Source Han Sans TC", var(--font-latin);
      --ui-font-soft: var(--ui-font);
      --ui-weight-light: 400;
    }
    [lang="ko"] {
      --ui-font: "Apple SD Gothic Neo", "Pretendard", "Malgun Gothic",
                 "Noto Sans KR", "Source Han Sans KR", var(--font-latin);
      --ui-font-soft: var(--ui-font);
      --ui-weight-light: 400;
    }
    [lang="ru"] {
      /* キリル文字。-apple-system / Segoe UI / Roboto はどれも収録済み。
         Noto Sans は Linux 等でこれらが無いときの受け皿。 */
      --ui-font: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto,
                 "Noto Sans", "Helvetica Neue", Arial, sans-serif;
      --ui-font-soft: var(--ui-font);
    }
    [lang="en"],
    [lang="es"],
    [lang="id"],
    [lang="pt-BR"] {
      --ui-font: var(--font-latin);
      --ui-font-soft: var(--font-latin);
    }
    body {
      background-color: #121214;
      color: #ffffff;
      font-family: var(--ui-font);
      overflow: hidden;
      display: flex;
      flex-direction: column;
      /* 100vh は Android Chrome 等でアドレスバー分を含んだ高さになり、
         実際に表示される領域より大きくなってしまう。その結果 #controls
         (回転記号ボタンや、タップキューブモードの OLL/PLL/ZBLL ボタン)
         が画面下端からはみ出て見えなくなる。dvh (dynamic viewport height)
         に対応しているブラウザではそちらを優先し、実際に見えている高さに
         正しく収まるようにする。100vh は非対応ブラウザ向けのフォールバック
         として残す。 */
      height: 100vh;
      height: 100dvh;
      /* html 側と揃えて、body 自身もラバーバンドで伸縮しないようにする。 */
      overscroll-behavior: none;
      /* ダブルタップ拡大を全画面で無効にする。manipulation は「スクロールと
         ピンチは許すが、ダブルタップ拡大のような追加のジェスチャーは許さない」
         という指定。子要素の touch-action と掛け合わされるだけなので、
         #canvas-container の none も .face-card の pan-y も、設定画面などの
         縦スクロールもこれまでどおり効く。
         残るピンチ拡大のほうは index.html の head にあるズーム封じの
         スクリプト側で止めている(CSS だけでは Android で素通りするため)。
         「ダブルタップで180°回転」は pointer イベントで自前に時間を測って
         判定しているので、この指定の影響を受けない。 */
      touch-action: manipulation;
    }
    #header {
      padding: 10px 12px 10px;
      padding-top: calc(10px + env(safe-area-inset-top));
      display: flex;
      justify-content: space-between;
      align-items: center;
      flex-wrap: wrap;
      gap: 8px;
      background: transparent;
      /* #header と #controls は body (flex コンテナ) の兄弟同士で、どちらも
         z-index を持つ flex アイテムなので、それぞれが独立した重なり
         コンテキストを作る。以前は両方とも 10 で同点だったため、DOM 順が
         後ろの #controls (回転記号ボタン = .face-grid を含む) が同点タイ
         ブレークで #header より前面に描画されていた。その結果、#header の
         中にある .menu-actions (ドロワーの中身、z-index:100) は「#header
         というコンテキストの中でだけ」100 位という扱いになり、#header
         自体はまるごと #controls の後ろに回ってしまっていた。これが
         Android Chrome でドロワーメニューを開くと回転記号ボタンの裏側に
         隠れ、特にリセットボタンが押せなくなっていた不具合の原因。
         #header 側の z-index を #controls よりはっきり大きくすることで、
         ドロワーを含む #header 全体を回転記号ボタンより確実に手前
         (前面) に固定する。 */
      z-index: 11;
      flex-shrink: 0;
    }
    .timer-box {
      display: flex;
      flex-direction: column;
    }
    .timer-label {
      font-size: 10px;
      color: #888;
      text-transform: uppercase;
      letter-spacing: 1px;
    }
    #timer {
      font-size: 26px;
      font-weight: 700;
      font-family: monospace;
      color: var(--tc);
      line-height: 1.15;
    }
    /* The only gray surface left in the header: a small badge just around
       the best time, clearly marking it as the secondary stat next to the
       big, bright current-run timer. */
    #best-time {
      display: inline-flex;
      align-items: center;
      gap: 3px;
      font-size: 11px;
      font-weight: 700;
      color: #ffd700;
      margin-top: 4px;
      padding: 3px 9px;
      border-radius: 999px;
      background: rgba(255, 255, 255, 0.07);
      width: fit-content;
    }
    /* Records/Settings/Reset stay tucked away behind a small toggle. Reset
       is destructive mid-solve, so nothing here is reachable by a stray
       touch -- you have to open the menu first, and it closes itself again
       as soon as you act, start solving, or leave it alone. Shuffle used to
       live in here too, but a shuffle is something you reach for constantly
       (every single solve), so it stays out in the open -- now as a vertical
       slide-to-confirm control hanging directly under this toggle. It is
       absolutely positioned against this box (hence position: relative
       here), so it never adds height to the header. See .shuffle-slider. */
    .top-btns {
      position: relative;
      display: flex;
      align-items: flex-start;
      justify-content: flex-end;
      gap: 6px;
    }
    /* --- シャッフル: ドロワーの真下にぶら下がる縦スライダー -------------
       以前はタイマー横に置いた「右へスライド」式だった。だが横なぞりは
       キューブの操作(面をなぞって回転／背景をなぞって全体回転)とまったく
       同じ向きで、指が少し外れたときに誤って触れてしまう余地が残る。
       そこでジェスチャーを二段階のL字に変えた:
         ① つまみを下から上へ持ち上げる → ② そこからほんの少し左へ倒す
       上げただけでは何も起きず、上げきった状態(.armed)から左へ倒した
       瞬間だけ発火する。キューブ側の横スワイプとも縦スワイプとも
       「L字に折り返す」という形が違うので、取り違えようがない。
       置き場所もヘッダーからドロワー(≡)の真下へ移し、空いた行は所持
       クリスタルに譲った。position:absolute で浮かせてあるのでヘッダーの
       高さは増えず、キューブの表示領域を削らない。
       計測中(.muted)は縮んで薄くなり、そろった瞬間(.celebrate)は
       光って脈打ち「次はこれ」と誘う、という性格はそのまま。
       top の値: 固定値にはできない。ふだんは ≡ ボタンの下でよいが、ドロワーを
       開いたらカードの下端まで下がらないと灰色のカードへ食い込むし、逆に
       Android Chrome のように表示領域が縦に短い端末では、下がりすぎると
       キューブの領域を飛び出して回転ボタンの列に垂れ下がる。そこで
       --shuffle-top を JS 側 (positionShuffleSlider) が実測して都度入れる。
       この行の値は JS 実行前・失敗時だけに使われるフォールバック。 */
    .shuffle-slider {
      position: absolute;
      top: var(--shuffle-top, 210px);
      right: 0;
      width: 48px;
      height: 116px;
      border-radius: 24px;
      background: rgba(18, 18, 24, 0.55);
      border: 1.5px solid #46465a;
      /* つまみは左へはみ出しながら動くので、切り抜きは内側の
         .shuffle-slider-clip (塗り専用) に任せ、こちらは visible にする。 */
      overflow: visible;
      /* 縦ドラッグをブラウザのスクロールに取られないよう none。 */
      touch-action: none;
      cursor: grab;
      -webkit-user-select: none;
      user-select: none;
      opacity: 0.96;
      transform: scale(1);
      transform-origin: top right;
      transition: opacity 0.25s ease, transform 0.25s ease,
                  border-color 0.25s ease, box-shadow 0.25s ease,
                  top 0.16s ease;
    }
    .shuffle-slider:active {
      cursor: grabbing;
    }
    /* 2枚の塗り(緑=持ち上げ進捗 / 赤=長押しリセット)だけを角丸で切り抜く器。 */
    .shuffle-slider-clip {
      position: absolute;
      inset: 0;
      border-radius: inherit;
      overflow: hidden;
      pointer-events: none;
    }
    .shuffle-slider-fill {
      position: absolute;
      left: 0;
      right: 0;
      bottom: 0;
      height: 0;
      background: linear-gradient(0deg, rgba(var(--tc-rgb), 0.40), rgba(var(--tc-rgb), 0.08));
      pointer-events: none;
    }
    .shuffle-slider.dragging .shuffle-slider-fill {
      transition: none;
    }
    .shuffle-slider:not(.dragging) .shuffle-slider-fill {
      transition: height 0.18s ease;
    }
    /* --- 長押しでリセット ------------------------------------------------
       つまみをドラッグせず3秒押さえ続けると、赤い塗りが下から上へ
       ゆっくり伸びる。伸びきる=3秒経過の合図で、そこでリセットが発火する。
       指を動かす(ドラッグに切り替わる)か離せば高さ0へ戻るだけで済み、
       リセットは一切実行されない。 */
    .shuffle-slider-longpress-fill {
      position: absolute;
      left: 0;
      right: 0;
      bottom: 0;
      height: 0;
      background: linear-gradient(0deg, rgba(255, 90, 90, 0.5), rgba(255, 90, 90, 0.1));
      pointer-events: none;
      transition: height 0.15s ease;
    }
    .shuffle-slider-longpress-fill.filling {
      height: 100%;
      transition: height 3s linear;
    }
    .shuffle-slider.longpressing {
      border-color: #ff8f8f;
    }
    .shuffle-slider.longpressing .shuffle-slider-thumb {
      border-color: #ff8f8f;
    }
    /* 次に進む向きの案内。上げきる前は「↑」、上げきったら(.armed)「←」に
       変わって残りの一手だけを示す。文字ではなく矢印なので、どの言語でも
       幅が変わらず、この細い縦トラックに確実に収まる。 */
    .shuffle-slider-hint {
      position: absolute;
      left: 0;
      right: 0;
      top: 9px;
      text-align: center;
      font-size: 15px;
      font-weight: 900;
      line-height: 1;
      color: #8a8a9c;
      pointer-events: none;
      transition: color 0.2s ease;
    }
    .shuffle-slider-hint::after { content: '↑'; }
    .shuffle-slider.armed .shuffle-slider-hint::after { content: '←'; }
    /* 上げきると、つまみ自身がトラック上端(=矢印の居場所)まで来てしまう。
       そこで矢印はトラックの外、つまみのすぐ左へ回り込み、次に倒す向きを
       指し示す位置から案内する。トラックを overflow:visible にしてあるのは
       この一手のため。 */
    .shuffle-slider.armed .shuffle-slider-hint {
      left: auto;
      right: calc(100% + 6px);
      top: 14px;
      font-size: 17px;
    }
    /* 上げきった状態。ここから左へ倒せば発火、という「あと一手」を
       色でもはっきり伝える。 */
    .shuffle-slider.armed {
      border-color: var(--tc);
      box-shadow: 0 0 14px rgba(var(--tc-rgb), 0.45);
    }
    .shuffle-slider.armed .shuffle-slider-hint {
      color: var(--tc);
      animation: shuffleHintNudge 0.7s ease-in-out infinite;
    }
    @keyframes shuffleHintNudge {
      0%, 100% { transform: translateX(0); }
      50%      { transform: translateX(-4px); }
    }
    .shuffle-slider-thumb {
      position: absolute;
      left: 50%;
      bottom: 4px;
      width: 40px;
      height: 40px;
      margin-left: -20px;
      border-radius: 50%;
      background: #33333e;
      border: 1.5px solid #66667c;
      display: flex;
      align-items: center;
      justify-content: center;
      font-size: 18px;
      line-height: 1;
      color: #ffffff;
      pointer-events: none;
      transform: translate(0, 0);
      transition: transform 0.2s cubic-bezier(.34, 1.56, .64, 1),
                  background 0.15s, border-color 0.15s, box-shadow 0.15s;
    }
    .shuffle-slider.dragging .shuffle-slider-thumb {
      transition: background 0.15s, border-color 0.15s, box-shadow 0.15s;
    }
    .shuffle-slider.armed .shuffle-slider-thumb {
      background: var(--tc);
      border-color: var(--tc);
      color: #04120f;
    }
    /* 以前はドロワーが開いている間このつまみを opacity:0 で隠していたが、
       top の位置をドロワーの実測の下端から計算するようにしたことで、
       開いていても物理的に重ならなくなったため、この非表示化はもう不要。
       ドロワーを開いたままでもシャッフルはそのまま使える。 */
    /* 計測中: 縮んで薄くなり、誤ってドラッグしてしまう気配そのものを
       減らす。押せなくはしない(考え直してやり直したい場合もあるため)
       が、視線を引かないところまで存在感を落とす。 */
    .shuffle-slider.muted {
      opacity: 0.34;
      transform: scale(0.88);
    }
    .shuffle-slider.muted .shuffle-slider-hint {
      color: #6a6a78;
    }
    /* --- チュートリアル未到達のあいだはロック ------------------------
       まだ「シャッフルしてみよう」の説明に来ていないのに先に混ぜられて
       しまうと、後のステップと噛み合わなくなる。トラックやつまみの形は
       そのまま残し、彩度と存在感だけを落として、右上に小さな禁止バッジを
       重ねる。押しても何も起きない(pointer-events:none)ので、誤操作の
       手応えごと消してしまう。 */
    .shuffle-slider.locked {
      pointer-events: none;
      cursor: not-allowed;
      filter: grayscale(0.9);
      opacity: 0.38;
      box-shadow: none;
    }
    .shuffle-slider.locked .shuffle-slider-hint {
      opacity: 0;
    }
    .shuffle-slider-lock {
      position: absolute;
      top: -7px;
      right: -7px;
      width: 22px;
      height: 22px;
      border-radius: 50%;
      background: #1c1c24;
      border: 1.5px solid #5a5a6c;
      display: none;
      align-items: center;
      justify-content: center;
      font-size: 12px;
      line-height: 1;
      pointer-events: none;
      z-index: 2;
    }
    .shuffle-slider.locked .shuffle-slider-lock {
      display: flex;
    }
    /* そろった瞬間: 逆にはっきり光らせて、次のシャッフルへ誘う。 */
    .shuffle-slider.celebrate {
      opacity: 1;
      border-color: var(--tc);
      box-shadow: 0 0 12px rgba(var(--tc-rgb), 0.5), inset 0 0 10px rgba(var(--tc-rgb), 0.14);
      animation: shuffleSliderCelebrate 1.3s ease-in-out infinite;
    }
    .shuffle-slider.celebrate .shuffle-slider-thumb {
      background: var(--tc);
      border-color: var(--tc);
      color: #04120f;
    }
    .shuffle-slider.celebrate .shuffle-slider-hint {
      color: var(--tc);
    }
    @keyframes shuffleSliderCelebrate {
      0%, 100% { box-shadow: 0 0 12px rgba(var(--tc-rgb), 0.5), inset 0 0 10px rgba(var(--tc-rgb), 0.14); }
      50% { box-shadow: 0 0 26px rgba(var(--tc-rgb), 0.95), inset 0 0 16px rgba(var(--tc-rgb), 0.3); }
    }
    @media (prefers-reduced-motion: reduce) {
      .shuffle-slider.celebrate { animation: none; }
      .shuffle-slider.armed .shuffle-slider-hint { animation: none; }
    }
    .menu-toggle {
      flex: 0 0 auto;
      width: 34px;
      height: 34px;
      border-radius: 10px;
      background: rgba(255, 255, 255, 0.05);
      border: 1px solid #3f3f4e;
      color: #9a9aad;
      font-size: 17px;
      font-weight: 800;
      line-height: 1;
      cursor: pointer;
      touch-action: manipulation;
      display: flex;
      align-items: center;
      justify-content: center;
      transition: color 0.15s, border-color 0.15s, background 0.15s;
    }
    .menu-toggle.open {
      color: var(--tc);
      border-color: var(--tc);
      background: rgba(var(--tc-rgb), 0.12);
    }
    /* Three-line "drawer" icon that morphs into an X when the menu is
       open, so the button's own shape communicates open/closed state. */
    .menu-toggle-icon {
      position: relative;
      width: 16px;
      height: 12px;
      display: block;
    }
    .menu-toggle-icon span {
      position: absolute;
      left: 0;
      width: 100%;
      height: 2px;
      border-radius: 2px;
      background: currentColor;
      transition: transform 0.2s ease, opacity 0.15s ease, top 0.2s ease;
    }
    .menu-toggle-icon span:nth-child(1) { top: 0; }
    .menu-toggle-icon span:nth-child(2) { top: 5px; }
    .menu-toggle-icon span:nth-child(3) { top: 10px; }
    .menu-toggle.open .menu-toggle-icon span:nth-child(1) {
      top: 5px;
      transform: rotate(45deg);
    }
    .menu-toggle.open .menu-toggle-icon span:nth-child(2) {
      opacity: 0;
    }
    .menu-toggle.open .menu-toggle-icon span:nth-child(3) {
      top: 5px;
      transform: rotate(-45deg);
    }
    /* Dropdown panel, pinned directly under the drawer toggle so the
       four actions always appear right where the button that revealed
       them is -- not shifted off to the side as an inline row. */
    .menu-actions {
      position: absolute;
      top: calc(100% + 10px);
      right: 0;
      display: flex;
      flex-direction: column;
      gap: 8px;
      width: max-content;
      min-width: 78vw;
      max-width: 340px;
      background: #1c1c22;
      border: 1px solid #3a3a48;
      border-radius: 18px;
      padding: 14px;
      box-shadow: 0 10px 30px rgba(0, 0, 0, 0.5);
      z-index: 100;
      opacity: 0;
      visibility: hidden;
      transform: translateY(-6px) scale(0.97);
      transform-origin: top right;
      pointer-events: none;
      transition: opacity 0.16s ease, transform 0.16s ease, visibility 0s linear 0.16s;
    }
    .menu-actions.open {
      opacity: 1;
      visibility: visible;
      transform: translateY(0) scale(1);
      pointer-events: auto;
      transition: opacity 0.16s ease, transform 0.16s ease;
    }
    /* Small tail pointing back up at the toggle button, so the connection
       between button and menu is obvious at a glance. */
    .menu-actions::before {
      content: '';
      position: absolute;
      top: -6px;
      right: 11px;
      width: 12px;
      height: 12px;
      background: #1c1c22;
      border-left: 1px solid #3a3a48;
      border-top: 1px solid #3a3a48;
      transform: rotate(45deg);
      border-radius: 2px 0 0 0;
    }
    /* Rows inside the drawer dropdown: full width, icon + label left
       aligned, one clear tap target per line instead of cramped inline
       chips. */
    .menu-actions .action-btn {
      display: flex;
      align-items: center;
      gap: 14px;
      width: 100%;
      background: transparent;
      color: #fff;
      border: none;
      padding: 20px 16px;
      border-radius: 12px;
      font-size: 19px;
      font-weight: 600;
      text-align: left;
      cursor: pointer;
      white-space: nowrap;
    }
    .menu-actions .action-btn:active {
      background: rgba(var(--tc-rgb), 0.14);
      color: var(--tc);
    }
    .action-btn {
      background: #2a2a32;
      color: #fff;
      border: 1px solid #444;
      padding: 7px 10px;
      border-radius: 8px;
      font-size: 12px;
      font-weight: 600;
      cursor: pointer;
      white-space: nowrap;
    }
    .action-btn:active {
      background: var(--tc);
      color: #000;
    }
    .action-btn.active {
      background: var(--tc);
      color: #000;
      border-color: var(--tc);
    }
    /* チュートリアルが記録/設定の説明中は、ボタン自体を押させない。
       押せてしまうと裏で記録/設定オーバーレイが開き、保持しているはずの
       ドロワーやその後のステップと噛み合わなくなるため。 */
    .menu-actions.tut-explain-lock .action-btn {
      pointer-events: none;
      opacity: 0.5;
      cursor: not-allowed;
    }
    /* Every overlay's bottom action row (settings/records/stats/OLL/PLL/
       ZBLL, and anything added later) shares this same larger tap target --
       styled once via the shared .settings-actions wrapper class instead of
       an ID list, so a new panel's buttons match automatically as long as
       they live inside a .settings-actions row. */
    .settings-actions .action-btn {
      padding: 14px 22px;
      font-size: 16px;
      border-radius: 10px;
      min-height: 50px;
      min-width: 130px;
    }
    #canvas-container {
      flex: 1;
      min-height: 0;
      width: 100%;
      position: relative;
      touch-action: none;
    }
    /* チュートリアルが「記録/設定」ドロワーの中身を説明している間は、
       背後のキューブに触れて回せてしまうと、まだ混ぜてもいないのに
       局面が崩れたりタイマーの前提が狂ったりして後続ステップと噛み合わ
       なくなる。説明を読んでいるだけの短い間だけ、指を素通りさせる。 */
    #canvas-container.tut-locked {
      pointer-events: none;
    }
    #controls {
      padding: 8px 12px 12px;
      padding-bottom: calc(12px + env(safe-area-inset-bottom));
      /* Solid color equal to rgba(0,0,0,0.4) blended over the body's
         #121214 background, instead of the rgba() itself. A semi-transparent
         layer sitting directly against the WebGL canvas is what let iOS
         WebKit's compositor get the seam wrong in the first place; a plain
         opaque color needs no blending against anything behind it, so there
         is no seam left to get wrong. (Forcing a separate GPU layer here
         instead, via transform/isolation, was tried and made things worse --
         it produced a stretched, mispositioned duplicate of this panel near
         the header -- so that approach was reverted.) */
      background: #0a0a0c;
      display: flex;
      flex-direction: column;
      gap: 6px;
      z-index: 10;
      flex-shrink: 0;
    }
    /* Never let a button row collapse when vertical space runs short (the
       landscape sidebar is the case that hits this) -- keep full size and
       let the container scroll instead. */
    #controls > * {
      flex-shrink: 0;
    }
    .controls-label {
      font-size: 11px;
      color: #999;
      text-align: center;
      margin-bottom: 2px;
      letter-spacing: 0.5px;
    }
    .mode-indicator {
      display: none;
      font-size: 11px;
      font-weight: 700;
      color: #ffd700;
      text-align: center;
      margin-bottom: 4px;
      letter-spacing: 0.3px;
    }
    .mode-indicator.show {
      display: block;
    }
    /* Control layout: a ring of buttons around the middle row, mirroring how
       the faces sit around the cube. Corner slots are small, the top and
       bottom (U and D) stretch across the middle three columns, and the
       middle row is taller since it holds the five side turns. Every slot
       is interchangeable from Settings. */
    .face-grid {
      display: grid;
      grid-template-columns: repeat(5, 1fr);
      grid-template-rows: 52px 84px 52px;
      gap: 8px;
    }
    .slot-wide {
      grid-column: span 3;
    }
    /* The middle slice has no sticker of its own, so it stays a quiet dark
       card rather than borrowing a face colour. */
    .slice-card .edge-hint,
    .slice-card .face-letter {
      background: rgba(0, 0, 0, 0.35);
      border-radius: 8px;
    }
    .slice-card .face-letter {
      padding: 3px 10px;
      color: #fff;
    }

    .face-card {
      --fc: #23232c;
      --fc-text: #ffffff;
      display: flex;
      align-items: center;
      justify-content: space-between;
      height: 100%;
      padding: 0 8px;
      border-radius: 14px;
      overflow: hidden;
      background: var(--fc);
      box-shadow: 0 3px 0 rgba(0, 0, 0, 0.35), inset 0 0 0 1px rgba(255, 255, 255, 0.15);
      touch-action: pan-y;
      cursor: grab;
      user-select: none;
      -webkit-touch-callout: none;
      -webkit-user-drag: none;
      position: relative;
    }
    /* L / M / R / Rw / Lw turn a vertical layer, so they take an up/down swipe
       instead of left/right -- flipping the flex axis puts the same two
       edge-hint elements above and below the letter instead of beside it,
       so no separate markup is needed for the two orientations. */
    .face-card.vertical-slide {
      flex-direction: column;
      touch-action: pan-x;
    }
    .face-card::after {
      content: '';
      position: absolute;
      inset: 0;
      background: radial-gradient(circle at var(--ripple-x, 50%) var(--ripple-y, 50%), rgba(255, 255, 255, 0.16) 0%, rgba(255, 255, 255, 0) 70%);
      opacity: 0;
      pointer-events: none;
    }
    .face-letter {
      font-size: 19px;
      font-weight: 800;
      line-height: 1;
      color: var(--fc-text);
      pointer-events: none;
      transition: transform 0.18s cubic-bezier(.34, 1.56, .64, 1);
    }
    .edge-hint {
      font-size: 12px;
      font-weight: 700;
      line-height: 1;
      color: var(--fc-text);
      opacity: 0.3;
      pointer-events: none;
      transition: opacity 0.12s, transform 0.12s;
      /* Small and low-opacity on purpose: these are just a subtle hint for
         which way a slide turns the face, kept out of the way so the big
         face-letter (回転記号) stays the clear focal point of the button.
         Hidden entirely when the arrow-hint setting is turned OFF below. */
    }
    body.hide-edge-arrows .edge-hint {
      visibility: hidden;
    }
    /* Slide "juice": the letter eases back to center, the winning edge
       arrow briefly pops, and a bright ripple flashes from whichever side
       was pushed -- so the card visibly snaps back like the physical
       cube's click. Re-triggered from JS on every committed slide. */
    .face-card.zap {
      animation: zapScale 0.32s cubic-bezier(.34, 1.56, .64, 1);
    }
    .face-card.zap::after {
      animation: zapRipple 0.42s ease-out;
    }
    .face-card.zap.dir-right .edge-hint.right,
    .face-card.zap.dir-left .edge-hint.left {
      animation: zapArrow 0.32s ease-out;
    }
    /* Vertical cards: the same two edge-hint elements now sit top/bottom
       (see .vertical-slide above), so "down" maps to the second element
       (the one on the right in the horizontal layout) and "up" to the first. */
    .face-card.zap.dir-down .edge-hint.right,
    .face-card.zap.dir-up .edge-hint.left {
      animation: zapArrow 0.32s ease-out;
    }
    .face-card.dragging {
      cursor: grabbing;
      filter: brightness(1.08);
    }
    @keyframes zapScale {
      0% { filter: brightness(1); }
      35% { filter: brightness(1.09); }
      100% { filter: brightness(1); }
    }
    @keyframes zapRipple {
      0% { opacity: 0.3; }
      100% { opacity: 0; }
    }
    @keyframes zapArrow {
      0% { opacity: 1; transform: scale(1.5); }
      100% { opacity: 0.35; transform: scale(1); }
    }
    #msg {
      position: absolute;
      top: 50%;
      left: 50%;
      transform: translate(-50%, -50%) scale(0.7);
      /* 背面のテキストが透けて重なって見える不具合の修正:
         半透明(rgba)だった背景を完全不透明な単色に変更。 */
      background: #08080b;
      border: 2px solid var(--tc);
      color: var(--tc);
      padding: 16px 24px;
      border-radius: 12px;
      font-size: 20px;
      font-weight: bold;
      text-align: center;
      pointer-events: none;
      opacity: 0;
      transition: opacity 0.3s;
      z-index: 20;
    }
    .msg-sub {
      display: block;
      margin-top: 6px;
      font-size: 11px;
      font-weight: 600;
      color: #aaa;
      letter-spacing: 0.3px;
    }
    #msg.pop {
      animation: msgPop 0.55s cubic-bezier(.34, 1.56, .64, 1);
      animation-fill-mode: forwards;
    }
    @keyframes msgPop {
      0% { transform: translate(-50%, -50%) scale(0.6); box-shadow: 0 0 0 rgba(var(--tc-rgb), 0); }
      55% { transform: translate(-50%, -50%) scale(1.08); box-shadow: 0 0 40px rgba(var(--tc-rgb), 0.55); }
      100% { transform: translate(-50%, -50%) scale(1); box-shadow: 0 0 20px rgba(var(--tc-rgb), 0.25); }
    }
    #settings-overlay {
      display: none;
      position: fixed;
      inset: 0;
      /* チュートリアル等のダイアログ背面のテキストが透けて重なって見える
         不具合の修正: 半透明(rgba)だった背景を完全不透明な単色に変更。
         背面の要素と合成されないため、透け・重なりが起こらなくなる。 */
      background: #08080b;
      z-index: 30;
      align-items: center;
      justify-content: center;
    }
    #layout-overlay {
      display: none;
      position: fixed;
      inset: 0;
      /* 背面が透けないよう完全不透明の背景に変更 */
      background: #08080b;
      z-index: 31;
      align-items: center;
      justify-content: center;
    }
    #layout-overlay.show {
      display: flex;
    }
    /* A settings row shaped like a navigation link (label + chevron),
       matching the "drill into a sub-screen" pattern rather than an inline
       editor, so the main Settings screen stays short and uncluttered. */
    .layout-link-btn {
      width: 100%;
      display: flex;
      align-items: center;
      justify-content: space-between;
      margin: 4px 0 14px;
    }
    .layout-link-arrow {
      color: var(--tc);
      font-weight: 900;
      font-size: 16px;
    }
    #settings-panel {
      background: #1c1c22;
      border: 1px solid #3a3a48;
      border-radius: 14px;
      padding: 0;
      width: min(90vw, 360px);
      /* 100vh 同様、90vh も Android のアドレスバー分を含んだ高さになり
         実際に見えている領域より大きくなることがある。dvh 対応ブラウザ
         では実際の表示領域を基準にする。 */
      max-height: 90vh;
      max-height: 90dvh;
      position: relative;
      overflow: hidden;
      text-align: center;
    }
    #settings-panel-scroll {
      max-height: 90vh;
      max-height: 90dvh;
      overflow-y: auto;
      -webkit-overflow-scrolling: touch;
      overscroll-behavior: contain;
      padding: 20px;
    }
    #settings-panel h2 {
      font-size: 16px;
      margin-bottom: 6px;
    }
    .settings-hint {
      font-size: 12px;
      color: #aaa;
      margin-bottom: 14px;
    }
    .settings-toggle-row {
      display: flex;
      align-items: center;
      justify-content: space-between;
      gap: 12px;
      margin-bottom: 6px;
    }
    .settings-toggle-label {
      font-size: 13px;
      font-weight: 600;
      color: #fff;
      text-align: left;
    }
    .toggle-switch {
      flex: 0 0 auto;
      width: 46px;
      height: 26px;
      border-radius: 13px;
      border: 1px solid #3a3a48;
      background: #23232c;
      position: relative;
      cursor: pointer;
      padding: 0;
      transition: background 0.15s, border-color 0.15s;
    }
    .toggle-switch .toggle-knob {
      position: absolute;
      top: 2px;
      left: 2px;
      width: 20px;
      height: 20px;
      border-radius: 50%;
      background: #888;
      transition: transform 0.18s cubic-bezier(.34, 1.56, .64, 1), background 0.15s;
    }
    .toggle-switch.on {
      background: rgba(var(--tc-rgb), 0.18);
      border-color: var(--tc);
    }
    .toggle-switch.on .toggle-knob {
      transform: translateX(20px);
      background: var(--tc);
    }
    .settings-toggle-row.disabled {
      opacity: 0.4;
    }
    .settings-toggle-row.disabled .toggle-switch {
      cursor: default;
    }
    /* Used for the OLL/PLL mode row while a solve is being timed: the mode
       is forced ON, and it should read as emphasized/active, not greyed
       out like a normal disabled control. */
    .settings-toggle-row.locked .toggle-switch {
      cursor: default;
    }
    .settings-toggle-row.locked .settings-toggle-label {
      color: var(--tc);
    }
    .toggle-hint {
      margin-top: -2px;
    }
    /* --- OLL / ZBLL / PLL ボタンの表示・非表示設定 -----------------------
       ON/OFF をスイッチではなく「実物のボタンを模したミニボタン」で表現:
       明るく光っている = 表示中、暗く沈んでいる = 非表示、というように
       見た目の明暗そのものが状態を表すので、丸ボタン本体との対応が
       ひと目でわかる。さらに文字ラベル(表示中/非表示)も添えて、色の
       区別が付きにくい場合でも状態を読み違えないようにしてある。 */
    .alg-visibility-grid {
      display: grid;
      grid-template-columns: repeat(3, 1fr);
      gap: 10px;
      margin: 2px 0 14px;
    }
    .alg-vis-btn {
      --accent: var(--tc);
      width: 100%;
      height: 58px;
      border-radius: 14px;
      background: linear-gradient(160deg, #2a2a32, #1c1c22);
      border: 1px solid #3a3a48;
      color: #7a7a86;
      cursor: pointer;
      touch-action: manipulation;
      display: flex;
      flex-direction: column;
      align-items: center;
      justify-content: center;
      gap: 3px;
      opacity: 0.55;
      filter: grayscale(0.7);
      transition: transform 0.08s, box-shadow 0.15s, border-color 0.15s,
                  color 0.15s, opacity 0.15s, filter 0.15s;
    }
    .alg-vis-btn .alg-name {
      font-size: 13px;
      font-weight: 800;
      letter-spacing: 0.5px;
      line-height: 1;
    }
    .alg-vis-btn .alg-vis-state {
      font-size: 10px;
      font-weight: 700;
      line-height: 1;
      color: inherit;
    }
    .alg-vis-btn:active {
      transform: translateY(2px);
    }
    .alg-vis-btn.on {
      color: var(--accent);
      border-color: var(--accent);
      opacity: 1;
      filter: none;
      box-shadow: 0 0 14px var(--accent), inset 0 0 10px rgba(255, 255, 255, 0.1);
    }
    #zbll-vis-btn { --accent: #ffd24a; }
    #pll-vis-btn { --accent: #ff9d6b; }
    /* 実際の丸ボタン側の表示/非表示切り替え。3つは独立して隠せる。 */
    body.hide-oll-btn #oll-btn { display: none; }
    body.hide-zbll-btn #zbll-btn { display: none; }
    body.hide-pll-btn #pll-btn { display: none; }

    /* --- ヘルプ機能の ON/OFF (ボタン配置画面、矢印設定のすぐ下) -----------
       OLL/ZBLL/PLL の表示切り替えボタンと同じ「明るく光る＝ON、暗く沈む＝
       OFF」という見た目のルールをそのまま流用し、アイコンは 💡 一つだけ。
       横長 1 個のボタンなので、テキスト付きの pill 型 (#help-btn と同系色)
       に寄せて見た目の対応関係を分かりやすくしてある。 */
    .help-visibility-grid {
      display: grid;
      grid-template-columns: 1fr;
      margin: 2px 0 14px;
    }
    .help-vis-btn {
      --accent: #ffd54a;
      flex-direction: row;
      justify-content: center;
      gap: 10px;
      height: 52px;
    }
    .help-vis-btn .help-vis-icon {
      font-size: 22px;
      line-height: 1;
    }
    .help-vis-btn .alg-vis-state {
      font-size: 12px;
      letter-spacing: 1px;
    }
    /* タップ操作モードでのヘルプボタン本体の表示切り替え。
       body.tap-mode #help-btn { display:flex } と詳細度が同じため、
       定義順に関係なく確実に勝たせるよう !important を付ける。 */
    body.hide-help-btn #help-btn { display: none !important; }
    .size-row {
      display: flex;
      align-items: center;
      justify-content: space-between;
      margin-top: 10px;
      margin-bottom: 6px;
    }
    .size-value {
      font-size: 13px;
      font-weight: 800;
      font-family: monospace;
      color: var(--tc);
      min-width: 42px;
      text-align: right;
    }
    .size-slider {
      width: 100%;
      accent-color: var(--tc);
      height: 28px;
      touch-action: pan-y;
    }
    .size-slider:disabled {
      opacity: 0.35;
      cursor: not-allowed;
    }
    .size-hint {
      margin-top: 2px;
    }
    .size-hint.locked {
      color: #ff9a5a;
    }
    /* Mirrors the real control layout, so a swap is a spatial choice rather
       than a list edit -- you tap the two positions you want exchanged. */
    #settings-grid {
      display: grid;
      grid-template-columns: repeat(5, 1fr);
      grid-template-rows: 42px 58px 42px;
      gap: 6px;
      margin-bottom: 16px;
    }
    .slot-btn.slot-wide {
      grid-column: span 3;
    }
    .slot-btn {
      --fc: #23232c;
      --fc-text: #fff;
      background: var(--fc);
      color: var(--fc-text);
      border: 1px solid #3a3a48;
      border-radius: 8px;
      padding: 14px 0 10px;
      font-size: 18px;
      font-weight: bold;
      cursor: pointer;
      display: flex;
      flex-direction: column;
      align-items: center;
      gap: 4px;
    }
    .slot-btn.selected {
      box-shadow: 0 0 0 3px var(--tc);
      border-color: var(--tc);
    }
    /* The 3-slot spare bench: same tappable button, but dashed and a touch
       dimmer so it visibly reads as "parked here, not on the main screen"
       until someone deliberately swaps it into an active position. */
    #settings-spare-grid {
      display: flex;
      gap: 6px;
      margin-bottom: 16px;
    }
    #settings-spare-grid .slot-btn {
      flex: 1;
    }
    .slot-btn.slot-spare {
      border-style: dashed;
      opacity: 0.82;
    }
    .spare-label {
      margin-top: 14px;
      margin-bottom: 2px;
      font-size: 11px;
      color: #888;
      text-transform: uppercase;
      letter-spacing: 0.5px;
    }
    .settings-actions {
      display: flex;
      gap: 8px;
      justify-content: center;
      flex-wrap: wrap;
    }

    #records-overlay {
      display: none;
      position: fixed;
      inset: 0;
      /* 背面が透けないよう完全不透明の背景に変更 */
      background: #08080b;
      z-index: 30;
      align-items: center;
      justify-content: center;
    }
    #records-overlay.show {
      display: flex;
    }
    #records-panel {
      background: #1c1c22;
      border: 1px solid #3a3a48;
      border-radius: 14px;
      padding: 20px;
      width: min(90vw, 360px);
      /* 100vh と同じ理由で、実表示領域に収まるよう dvh を優先する。 */
      max-height: 80vh;
      max-height: 80dvh;
      display: flex;
      flex-direction: column;
      text-align: center;
      position: relative;
    }
    /* 記録の全消去は取り消せないので、誤タップで押してしまわないよう
       あえて他の操作ボタンから離し、隅に小さく置く。押すたびにパネルの
       別の隅へ移動する「4隅チェイス」方式にして、同じ場所を連続で押した
       だけでは消去できないようにしてある。 */
    .records-delete-btn {
      position: absolute;
      left: 14px;
      top: auto;
      right: auto;
      bottom: 14px;
      width: 24px;
      height: 24px;
      padding: 0;
      display: flex;
      align-items: center;
      justify-content: center;
      background: rgba(255, 70, 70, 0.1);
      border: 1px solid rgba(255, 70, 70, 0.35);
      border-radius: 50%;
      color: #ff5a5a;
      font-size: 11px;
      line-height: 1;
      cursor: pointer;
      transition: left 0.2s ease, right 0.2s ease, top 0.2s ease,
        bottom 0.2s ease, background 0.2s ease, border-color 0.2s ease;
    }
    .records-delete-btn:active {
      background: rgba(255, 70, 70, 0.3);
    }
    .records-delete-btn.stage-1 {
      left: 14px;
      top: 48px;
      right: auto;
      bottom: auto;
      background: rgba(255, 70, 70, 0.2);
    }
    .records-delete-btn.stage-2 {
      left: auto;
      right: 14px;
      top: 48px;
      bottom: auto;
      background: rgba(255, 70, 70, 0.3);
    }
    .records-delete-btn.stage-3 {
      left: auto;
      right: 14px;
      top: auto;
      bottom: 14px;
      width: auto;
      padding: 0 8px;
      border-radius: 12px;
      background: rgba(255, 40, 40, 0.5);
      border-color: #ff2b2b;
      color: #ff2b2b;
      font-size: 12px;
      font-weight: 800;
    }
    #records-panel h2 {
      font-size: 16px;
      margin-bottom: 6px;
    }
    .records-panel-header {
      display: grid;
      grid-template-columns: 1fr auto 1fr;
      align-items: center;
      margin-bottom: 6px;
    }
    .records-panel-header h2 {
      grid-column: 2;
      text-align: center;
      margin-bottom: 0;
    }
    .records-panel-header .icon-btn {
      grid-column: 3;
      justify-self: end;
    }
    .icon-btn {
      flex: 0 0 auto;
      background: rgba(255, 255, 255, 0.06);
      border: 1px solid #3a3a48;
      border-radius: 10px;
      font-size: 16px;
      line-height: 1;
      padding: 6px 9px;
      cursor: pointer;
    }
    .icon-btn:active {
      background: rgba(255, 255, 255, 0.14);
    }
    .icon-btn-cube {
      display: inline-flex;
      align-items: center;
      justify-content: center;
    }
    .icon-btn-cube svg {
      display: block;
    }
    #stats-overlay {
      display: none;
      position: fixed;
      inset: 0;
      /* 背面が透けないよう完全不透明の背景に変更 */
      background: #08080b;
      z-index: 31;
      align-items: center;
      justify-content: center;
    }
    #stats-overlay.show {
      display: flex;
    }
    #stats-panel {
      background: #1c1c22;
      border: 1px solid #3a3a48;
      border-radius: 14px;
      padding: 20px;
      width: min(90vw, 360px);
      max-height: 80vh;
      max-height: 80dvh;
      display: flex;
      flex-direction: column;
      text-align: center;
    }
    #stats-panel h2 {
      font-size: 16px;
      margin-bottom: 14px;
    }
    #stats-list {
      overflow-y: auto;
      max-height: 50vh;
      margin-bottom: 10px;
      border-radius: 10px;
      background: rgba(255, 255, 255, 0.03);
    }
    .stats-row {
      display: flex;
      align-items: center;
      gap: 12px;
      padding: 10px 12px;
      border-bottom: 1px solid rgba(255, 255, 255, 0.06);
      font-size: 13px;
    }
    .stats-row:last-child {
      border-bottom: none;
    }
    .stats-row.rank-1 .record-rank { color: #ffd700; }
    .stats-size-wrap {
      flex: 0 0 auto;
      display: flex;
      flex-direction: column;
      align-items: flex-start;
      gap: 4px;
      min-width: 58px;
    }
    .stats-size-num {
      font-family: monospace;
      font-weight: 900;
      font-size: 19px;
      line-height: 1;
      color: #666;
    }
    .stats-size-num.size-rainbow {
      font-size: 19px;
      background: linear-gradient(90deg, #ff2d2d, #ff9d2d, #f6ff2d, #2dff5c, #2de0ff, #4a5bff, #d92dff, #ff2d2d);
      background-size: 300% 100%;
      -webkit-background-clip: text;
      background-clip: text;
      color: transparent;
      animation: size-rainbow-shift 1.4s linear infinite,
                 size-rainbow-pulse 0.9s ease-in-out infinite;
    }
    .stats-size-num.size-gold {
      color: #ffd700;
      animation: size-gold-shine 1.2s ease-in-out infinite;
    }
    @keyframes size-gold-shine {
      0%, 100% { text-shadow: 0 0 6px rgba(255, 215, 0, 0.5), 0 0 12px rgba(255, 215, 0, 0.25); }
      50% { text-shadow: 0 0 14px rgba(255, 215, 0, 1), 0 0 26px rgba(255, 215, 0, 0.7); }
    }
    .stats-size-num.size-cyan {
      color: #8fd9ff;
      text-shadow: 0 0 8px rgba(143, 217, 255, 0.55);
    }
    .stats-size-num.size-lilac {
      color: #c8a2ff;
      text-shadow: 0 0 8px rgba(200, 162, 255, 0.55);
    }
    .stats-size-num.size-gray {
      color: #666;
    }
    .stats-size-bar {
      width: 58px;
      height: 5px;
      border-radius: 3px;
      background: rgba(255, 255, 255, 0.08);
      overflow: hidden;
    }
    .stats-size-bar-fill {
      height: 100%;
      border-radius: 3px;
    }
    .stats-time {
      flex: 1;
      text-align: left;
      font-family: monospace;
      color: #aaa;
    }
    /* タイム表記もキューブサイズ%と同じ色演出にする */
    .stats-time.size-rainbow {
      background: linear-gradient(90deg, #ff2d2d, #ff9d2d, #f6ff2d, #2dff5c, #2de0ff, #4a5bff, #d92dff, #ff2d2d);
      background-size: 300% 100%;
      -webkit-background-clip: text;
      background-clip: text;
      color: transparent;
      font-weight: 700;
      animation: size-rainbow-shift 1.4s linear infinite,
                 size-rainbow-pulse 0.9s ease-in-out infinite;
    }
    .stats-time.size-gold {
      color: #ffd700;
      font-weight: 700;
      animation: size-gold-shine 1.2s ease-in-out infinite;
    }
    .stats-time.size-cyan {
      color: #8fd9ff;
      font-weight: 700;
      text-shadow: 0 0 8px rgba(143, 217, 255, 0.55);
    }
    .stats-time.size-lilac {
      color: #c8a2ff;
      font-weight: 700;
      text-shadow: 0 0 8px rgba(200, 162, 255, 0.55);
    }
    .stats-time.size-gray {
      color: #aaa;
    }
    /* タイム横に付ける OLL/PLL 使用マーク */
    .stats-assist-mark {
      flex: 0 0 auto;
      display: inline-flex;
      gap: 3px;
      margin-left: 4px;
    }
    #records-best {
      font-size: 22px;
      font-weight: 700;
      font-family: monospace;
      color: #ffd700;
      margin-bottom: 10px;
    }
    #records-list, #records-list-assist {
      overflow-y: auto;
      max-height: 30vh;
      margin-bottom: 10px;
      border-radius: 10px;
      background: rgba(255, 255, 255, 0.03);
    }
    .records-subheading {
      text-align: left;
      font-size: 12px;
      font-weight: 700;
      color: #999;
      letter-spacing: 0.3px;
      margin: 2px 2px 6px;
    }
    #records-best-assist {
      font-size: 16px;
      font-weight: 700;
      font-family: monospace;
      color: #ff9d6b;
      margin-bottom: 8px;
    }
    .record-row {
      display: flex;
      align-items: center;
      gap: 10px;
      padding: 8px 12px;
      border-bottom: 1px solid rgba(255, 255, 255, 0.06);
      font-size: 13px;
    }
    .record-row:last-child {
      border-bottom: none;
    }
    .record-rank {
      flex: 0 0 auto;
      width: 22px;
      color: #999;
      font-weight: 700;
      text-align: right;
    }
    .record-row.rank-1 .record-rank { color: #ffd700; }
    .record-time {
      flex: 1;
      text-align: left;
      font-family: monospace;
      font-weight: 700;
      color: var(--tc);
    }
    .record-badges {
      flex: 0 0 auto;
      display: flex;
      gap: 3px;
    }
    .record-badge {
      font-size: 9px;
      font-weight: 800;
      letter-spacing: 0.3px;
      padding: 2px 5px;
      border-radius: 4px;
      line-height: 1;
    }
    .record-badge.help {
      background: rgba(255, 221, 87, 0.15);
      color: #ffdd57;
      border: 1px solid rgba(255, 221, 87, 0.45);
      padding: 2px 4px;
    }
    .record-badge.oll {
      background: rgba(var(--tc-rgb), 0.15);
      color: var(--tc);
      border: 1px solid rgba(var(--tc-rgb), 0.45);
    }
    .record-badge.pll {
      background: rgba(255, 140, 90, 0.15);
      color: #ff9d6b;
      border: 1px solid rgba(255, 140, 90, 0.45);
    }
    .record-date {
      flex: 0 0 auto;
      color: #888;
      font-size: 11px;
    }
    .records-empty {
      padding: 24px 12px;
      color: #888;
      font-size: 13px;
    }

    /* Fireworks: small colored sparks burst outward from a few points and
       fade out, reusing the cube's own sticker colors so the celebration
       ties back to the thing you just solved. */
    #fireworks-container {
      position: absolute;
      inset: 0;
      overflow: hidden;
      pointer-events: none;
      z-index: 18;
    }
    .fw-particle {
      position: absolute;
      width: 8px;
      height: 8px;
      border-radius: 50%;
      margin: -4px 0 0 -4px;
      animation-name: fw-burst;
      animation-timing-function: cubic-bezier(.12, .7, .3, 1);
      animation-fill-mode: forwards;
      box-shadow: 0 0 10px currentColor, 0 0 20px currentColor;
    }
    @keyframes fw-burst {
      0% { transform: translate(0, 0) scale(1.3); opacity: 1; }
      75% { opacity: 1; }
      100% { transform: translate(var(--dx), var(--dy)) scale(0.2); opacity: 0; }
    }
    /* A brief full-screen wash of light at the moment of the solve, gold and
       longer for a new personal best. */
    #fireworks-container.flash::before,
    #fireworks-container.flash-best::before {
      content: '';
      position: absolute;
      inset: 0;
      pointer-events: none;
    }
    #fireworks-container.flash::before {
      background: radial-gradient(circle at 50% 45%, rgba(var(--tc-rgb), 0.45), rgba(var(--tc-rgb), 0) 70%);
      animation: fw-flash 0.7s ease-out forwards;
    }
    #fireworks-container.flash-best::before {
      background: radial-gradient(circle at 50% 45%, rgba(255, 215, 0, 0.55), rgba(255, 215, 0, 0) 72%);
      animation: fw-flash 1.1s ease-out forwards;
    }
    @keyframes fw-flash {
      0% { opacity: 0; transform: scale(0.7); }
      18% { opacity: 1; transform: scale(1); }
      100% { opacity: 0; transform: scale(1.25); }
    }

    /* --- ここから: 完成の瞬間のインパクト演出 ---
       白い瞬間フラッシュ + 画面シェイク + 紙吹雪 + ゴールド文字。 */

    /* 白フラッシュ: 花火の柔らかい光とは別に、決まった瞬間だけ画面全体を
       真っ白に一瞬だけ叩いて「バシッ」という手応えを出す。 */
    #impact-flash {
      position: fixed;
      inset: 0;
      background: #fff;
      opacity: 0;
      pointer-events: none;
      z-index: 9998;
    }
    #impact-flash.hit {
      animation: impact-flash-anim 0.42s ease-out forwards;
    }
    @keyframes impact-flash-anim {
      0%   { opacity: 0.92; }
      100% { opacity: 0; }
    }

    /* 画面シェイク: 完成の瞬間、画面全体をガクッと揺らす。 */
    /* 完成の瞬間の「バシッ」という手応え。
       もとは body 全体を上下左右に振っていたが、それだと
         ・ヘッダー(タイマー)やボタンまで一緒に上下し、
         ・画面そのものが縦に動くので、端末側のスクロール/ラバーバンドや
           iOS WebKit の合成と噛み合ってガタガタに見える
       という2つの問題が出ていた（「完成すると上下にギザギザ動く」の正体）。
       いまはキューブの居るところ(#canvas-container)だけを、しかも
       横方向にだけ振る。縦の動きとわずかな回転は完全に無くしたので、
       画面が上下にずれることはもう起こらない。translate3d で描くのは、
       整数ピクセルへの丸めでギザついて見えるのを避けるため。 */
    @keyframes screen-shake-anim {
      0%   { transform: translate3d(0, 0, 0); }
      10%  { transform: translate3d(-7px, 0, 0); }
      20%  { transform: translate3d(6px, 0, 0); }
      30%  { transform: translate3d(-6px, 0, 0); }
      40%  { transform: translate3d(5px, 0, 0); }
      50%  { transform: translate3d(-4px, 0, 0); }
      65%  { transform: translate3d(3px, 0, 0); }
      80%  { transform: translate3d(-2px, 0, 0); }
      100% { transform: translate3d(0, 0, 0); }
    }
    #canvas-container.screen-shake {
      animation: screen-shake-anim 0.5s cubic-bezier(.36,.07,.19,.97) both;
    }

    /* 紙吹雪: 画面いちばん上のレイヤーに敷いた1枚のcanvasに物理演算で描く。 */
    #confetti-canvas {
      position: fixed;
      inset: 0;
      width: 100%;
      height: 100%;
      pointer-events: none;
      z-index: 9997;
      display: none;
    }

    /* ゴールドの帯文字。"SOLVED!"/"COMPLETE!" など、英数字だけに使うこと
       (Orbitronは和文グリフを持たないので、ひらがな/漢字に使うと文字化け
       のように見えてしまう)。 */
    .msg-gold-text {
      display: inline-block;
      font-family: 'Orbitron', sans-serif;
      font-weight: 900;
      letter-spacing: 1px;
      background: linear-gradient(90deg, #fff6c8, #ffd54a, #ff9d1f, #ffd54a, #fff6c8);
      background-size: 200% auto;
      -webkit-background-clip: text;
      background-clip: text;
      -webkit-text-fill-color: transparent;
      animation: demo-rainbow-bg 1.6s linear infinite;
    }
    @media (prefers-reduced-motion: reduce) {
      #canvas-container.screen-shake { animation: none; }
      #impact-flash.hit { animation: none; opacity: 0 !important; }
      .msg-gold-text { animation: none; }
    }
    /* OLL / PLL sit well above the Lw and Rw cards, out of the slide area.
       They read as raised physical buttons: a lit rim and inner glow in
       their own accent colour when pressed, so a tap feels answered. */
    .alg-row {
      display: grid;
      grid-template-columns: repeat(3, 1fr);
      gap: 10px;
      justify-items: center;
      margin-bottom: 14px;
      padding-top: 2px;
    }
    .alg-btn {
      --accent: var(--tc);
      width: 56px;
      height: 56px;
      border-radius: 50%;
      background: linear-gradient(160deg, #34343f, #212129);
      border: 1px solid #4a4a5c;
      box-shadow: 0 3px 0 rgba(0, 0, 0, 0.5), inset 0 1px 0 rgba(255, 255, 255, 0.09);
      color: #dcdce6;
      cursor: pointer;
      touch-action: manipulation;
      display: flex;
      flex-direction: column;
      align-items: center;
      justify-content: center;
      gap: 1px;
      transition: transform 0.08s, box-shadow 0.15s, border-color 0.15s, color 0.15s;
    }
    .alg-btn .alg-name {
      font-size: 13px;
      font-weight: 800;
      letter-spacing: 0.5px;
      line-height: 1;
    }
    .alg-btn .alg-sub {
      font-size: 8px;
      font-weight: 700;
      color: var(--accent);
      opacity: 0.85;
      line-height: 1;
    }
    .alg-btn:active {
      transform: translateY(3px);
      border-color: var(--accent);
      color: var(--accent);
      box-shadow: 0 0 18px var(--accent), inset 0 0 14px rgba(255, 255, 255, 0.12);
    }
    .alg-btn#pll-btn {
      --accent: #ff9d6b;
    }
    .alg-spacer {
      pointer-events: none;
    }

    /* --- OLL/ZBLL/PLL を全部非表示にしたとき (ボタン配置画面の設定) ---
       alg-row が空になって帯だけ残るので、その帯ごと畳んで回転記号ボタン
       (face-grid) に空間を回す。tap-mode 中は alg-row が💡ヘルプボタンを
       表示する場所として使われているので、この畳み込みは対象外。 */
    body.all-alg-hidden:not(.tap-mode) .alg-row {
      display: none;
    }
    body.all-alg-hidden:not(.tap-mode) .face-grid {
      grid-template-rows: 62px 100px 62px;
      gap: 10px;
    }
    body.all-alg-hidden:not(.tap-mode) .face-letter {
      font-size: 23px;
    }
    body.all-alg-hidden:not(.tap-mode) .edge-hint {
      font-size: 14px;
    }


    /* --- 💡 ヘルプ（タップキューブモード専用） -------------------------
       タップ操作中は OLL / ZBLL / PLL の3つを隠し、空いた同じ場所に
       ヘルプボタンを置く。丸ボタン3つ分の幅を1つで使うので、pill 型に
       広げて文字を添えている。 */
    #help-btn {
      display: none;
      --accent: #ffd54a;
      width: auto;
      min-width: 140px;
      height: 56px;
      border-radius: 28px;
      padding: 0 20px;
      flex-direction: row;
      gap: 8px;
    }
    #help-btn .help-icon { font-size: 21px; line-height: 1; }
    #help-btn .help-label { font-size: 15px; font-weight: 800; letter-spacing: 0.5px; }
    #help-btn.on {
      border-color: #ffd54a;
      color: #ffd54a;
      box-shadow: 0 0 18px rgba(255, 213, 74, 0.45), inset 0 0 14px rgba(255, 213, 74, 0.14);
    }
    /* チュートリアルがこのボタン自体を説明している間は押させない。 */
    #help-btn.tut-locked {
      pointer-events: none;
      opacity: 0.5;
      cursor: not-allowed;
    }
    body.tap-mode .alg-btn:not(#help-btn) { display: none; }
    body.tap-mode #help-btn { display: flex; }
    body.tap-mode .alg-row { grid-template-columns: 1fr; }

    /* --- お手本キューブのパネル ---
       メインキューブのすぐ上に重ねて置く。パネルが出ている間はカメラ側
       (applyCubeZoom) がキューブを一回り小さく・少し下に寄せるので、
       画面幅や高さが変わっても両者は重ならない。 */
    #demo-panel {
      display: none;
      position: absolute;
      left: 8px;
      right: 8px;
      top: 8px;
      z-index: 6;
      gap: 8px;
      align-items: stretch;
      padding: 7px 9px;
      border-radius: 14px;
      background: rgba(22, 22, 28, 0.96);
      border: 1px solid #3d3d4c;
      box-shadow: 0 6px 22px rgba(0, 0, 0, 0.45);
    }
    #demo-panel.show { display: flex; }
    #demo-canvas-wrap {
      /* 初心者が動きを追えるよう、お手本はある程度の大きさを保ちつつ、
         スマホでメインキューブを回す余白を優先して少し控えめにしてある。 */
      flex: 0 0 auto;
      align-self: center;
      width: max(80px, min(26vw, 16vh, 130px));
      height: max(80px, min(26vw, 16vh, 130px));
      border-radius: 10px;
      overflow: hidden;
      background: #17171b;
    }
    #demo-canvas-wrap canvas { display: block; width: 100%; height: 100%; }
    .demo-info {
      flex: 1;
      min-width: 0;
      display: flex;
      flex-direction: column;
      justify-content: center;
      gap: 5px;
    }

    /* 上段: 今どこをそろえているか（左）と、のこり手数（右のバッジ）を
       はっきり分けて置く。以前は1行のテキストに詰め込んでいて読み取り
       づらかったので、役割ごとに要素を分離した。 */
    .demo-progress {
      display: flex;
      align-items: center;
      justify-content: space-between;
      gap: 8px;
      /* マスコットぶん横幅が減ったので、「白いクロス」とのこり手数が
         同じ行に収まらない言語・端末では素直に2行に折り返させる。
         どちらも消したくない情報なので、省略(…)よりは折り返しを選ぶ。 */
      flex-wrap: wrap;
      row-gap: 3px;
    }
    .demo-phase {
      font-size: 11px;
      font-weight: 700;
      color: #c6cad3;
      white-space: nowrap;
      overflow: hidden;
      text-overflow: ellipsis;
    }
    .demo-remain {
      flex: 0 0 auto;
      font-size: 11px;
      font-weight: 800;
      color: #ffd54a;
      background: rgba(255, 213, 74, 0.12);
      border: 1px solid rgba(255, 213, 74, 0.45);
      border-radius: 999px;
      padding: 2px 9px;
      white-space: nowrap;
    }
    .demo-remain b { font-size: 13px; margin: 0 1px; }
    .demo-remain.hidden { display: none; }

    /* 中段: 次の1手。回転記号（文字）だけに頼らず、その面の実際の色で
       バッジを塗るので「どの色を回すか」が一目でわかる。 */
    .demo-move-badge {
      align-self: center;
      display: flex;
      align-items: center;
      justify-content: center;
      min-width: 84px;
      height: 40px;
      padding: 0 14px;
      border-radius: 12px;
      background: #2c2c36;
      border: 2px solid #4a4a5c;
    }
    .demo-move {
      font-family: 'Orbitron', sans-serif;
      font-size: clamp(20px, 6.4vw, 30px);
      font-weight: 900;
      line-height: 1.1;
      color: #ffd54a;
    }
    /* 持ち替え（キューブを回して持ち直す）ステップは、面を回すステップと
       ひと目で区別できるよう配色ごと変える。 */
    .demo-move-badge.grip { background: #123846; border-color: #3fb7e6; }
    .demo-move-badge.grip .demo-move { color: #7fd7ff; font-size: clamp(16px, 4.8vw, 22px); }
    .demo-move-badge.done { background: #163a24; border-color: #3ecf76; }
    .demo-move-badge.done .demo-move { color: #6bffa0; }
    /* 回転記号の下にあった説明文（「白の面を時計回りに…」）は画面から廃止。
       どの面をどちらに回すかは、バッジの色と ↻ / ↺ ですでに伝わっている。
       要素自体は読み上げ用に残すので display:none ではなく視覚的にだけ隠す。 */
    .demo-move-desc,
    .demo-note:not(.good) {
      position: absolute;
      width: 1px;
      height: 1px;
      margin: -1px;
      padding: 0;
      border: 0;
      overflow: hidden;
      clip-path: inset(50%);
      white-space: nowrap;
    }

    /* 完成時の CLEAR TIME / NEW RECORD だけは帯として表に出す。
       （進行中の案内文はマスコットの表情が代わりになっている） */
    .demo-note {
      display: flex;
      align-self: center;      /* iPad では横に伸びきってしまうので中身の幅に合わせる */
      max-width: 100%;
      align-items: center;
      justify-content: center;
      gap: 5px;
      font-size: 11px;
      line-height: 1.4;
      font-weight: 700;
      color: #cdf9de;
      background: rgba(60, 207, 118, 0.18);
      border: 1px solid rgba(80, 220, 140, 0.55);
      border-radius: 9px;
      padding: 4px 8px;
    }
    .demo-note-icon { flex: 0 0 auto; font-size: 14px; line-height: 1.3; }
    .demo-note-text { flex: 0 1 auto; }

    /* --- ゆるキャラ（キューブくん） -----------------------------------
       文章ではなく表情で状態を伝える。4枚のSVGを重ねておき、
       親の data-state で見せる1枚を選ぶ。差し替えにレイアウトが
       動かないよう、全枚を同じグリッドセルに重ねている。 */
    .demo-mascot {
      flex: 0 0 auto;
      align-self: center;
      display: grid;
      width: max(60px, min(21vw, 14vh, 96px));
      aspect-ratio: 200 / 220;
      transform-origin: 50% 85%;
    }
    .mascot-art {
      grid-area: 1 / 1;
      width: 100%;
      height: 100%;
      visibility: hidden;
      opacity: 0;
      transform: scale(0.86);
      transition: opacity .18s ease, transform .18s ease;
    }
    .demo-mascot[data-state="ok"]     .mascot-art[data-mascot="ok"],
    .demo-mascot[data-state="miss"]   .mascot-art[data-mascot="miss"],
    .demo-mascot[data-state="almost"] .mascot-art[data-mascot="almost"],
    .demo-mascot[data-state="clear"]  .mascot-art[data-mascot="clear"] {
      visibility: visible;
      opacity: 1;
      transform: scale(1);
    }

    /* 状態ごとの動き。色・表情・動きの3つで区別する。 */
    @keyframes mascot-bob   { 0%,100% { transform: translateY(0); } 50% { transform: translateY(-4%); } }
    @keyframes mascot-shake { 0%,100% { transform: translateX(0); }
                              20% { transform: translateX(-6%) rotate(-3deg); }
                              40% { transform: translateX(5%) rotate(3deg); }
                              60% { transform: translateX(-4%) rotate(-2deg); }
                              80% { transform: translateX(3%) rotate(1deg); } }
    @keyframes mascot-hop   { 0%,100% { transform: translateY(0) scale(1); }
                              45% { transform: translateY(-11%) scale(1.05, 0.96); }
                              70% { transform: translateY(0) scale(0.97, 1.03); } }
    @keyframes mascot-pop   { 0% { transform: scale(1); } 45% { transform: scale(1.16); } 100% { transform: scale(1); } }

    .demo-mascot[data-state="ok"]     { animation: mascot-bob 2.6s ease-in-out infinite; }
    .demo-mascot[data-state="miss"]   { animation: mascot-shake .5s ease-in-out 1; }
    .demo-mascot[data-state="almost"] { animation: mascot-hop 1s ease-in-out infinite;
                                        filter: drop-shadow(0 0 7px rgba(255, 190, 60, 0.55)); }
    .demo-mascot[data-state="clear"]  { animation: mascot-hop .62s ease-in-out infinite;
                                        filter: drop-shadow(0 0 9px rgba(90, 240, 170, 0.6)); }
    /* 合っていた瞬間のひと跡ね。進行中のループより優先させる。 */
    .demo-mascot.pop { animation: mascot-pop .34s cubic-bezier(.34, 1.56, .64, 1) 1 !important; }

    #demo-close {
      position: absolute;
      top: -8px;
      right: -6px;
      width: 24px;
      height: 24px;
      border-radius: 50%;
      background: #2c2c36;
      border: 1px solid #4a4a5c;
      color: #cfd3dc;
      font-size: 14px;
      line-height: 1;
      cursor: pointer;
    }
    @media (max-height: 620px) {
      #demo-canvas-wrap { width: max(68px, min(22vw, 18vh, 96px)); height: max(68px, min(22vw, 18vh, 96px)); }
      .demo-mascot { width: max(56px, min(19vw, 15vh, 86px)); }
      .demo-phase, .demo-remain, .demo-move-desc, .demo-note { font-size: 10px; }
      .demo-move-badge { height: 34px; padding: 0 10px; }
      .demo-move { font-size: clamp(16px, 5.6vw, 22px); }
    }

    /* --- 完成演出: とにかく派手に。達成感を出すため虹色+ポップ+光る枠 --- */
    @keyframes demo-rainbow-text {
      0%   { color: #ff5b5b; }
      16%  { color: #ffb443; }
      33%  { color: #ffe95b; }
      50%  { color: #5bff8a; }
      66%  { color: #5bc8ff; }
      83%  { color: #b06bff; }
      100% { color: #ff5b5b; }
    }
    @keyframes demo-rainbow-bg {
      0%   { background-position: 0% 50%; }
      100% { background-position: 200% 50%; }
    }
    @keyframes demo-celebrate-pop {
      0%   { transform: scale(0.3) rotate(-10deg); opacity: 0; }
      55%  { transform: scale(1.22) rotate(5deg);  opacity: 1; }
      75%  { transform: scale(0.92) rotate(-2deg); }
      100% { transform: scale(1) rotate(0deg); }
    }
    @keyframes demo-celebrate-shake {
      0%, 100% { transform: translateX(0) scale(1); }
      20%      { transform: translateX(-4px) rotate(-2deg) scale(1.03); }
      40%      { transform: translateX(4px) rotate(2deg) scale(1.03); }
      60%      { transform: translateX(-3px) rotate(-1deg) scale(1.02); }
      80%      { transform: translateX(3px) rotate(1deg) scale(1.02); }
    }
    @keyframes demo-celebrate-border {
      0%   { border-color: #ff5b5b; box-shadow: 0 0 22px 3px rgba(255, 91, 91, 0.6); }
      16%  { border-color: #ffb443; box-shadow: 0 0 22px 3px rgba(255, 180, 67, 0.6); }
      33%  { border-color: #ffe95b; box-shadow: 0 0 22px 3px rgba(255, 233, 91, 0.6); }
      50%  { border-color: #5bff8a; box-shadow: 0 0 22px 3px rgba(91, 255, 138, 0.6); }
      66%  { border-color: #5bc8ff; box-shadow: 0 0 22px 3px rgba(91, 200, 255, 0.6); }
      83%  { border-color: #b06bff; box-shadow: 0 0 22px 3px rgba(176, 107, 255, 0.6); }
      100% { border-color: #ff5b5b; box-shadow: 0 0 22px 3px rgba(255, 91, 91, 0.6); }
    }
    #demo-panel.celebrate { animation: demo-celebrate-border 1.5s linear infinite; }
    .demo-phase.celebrate-text {
      font-family: 'Orbitron', sans-serif;
      font-size: clamp(15px, 5.6vw, 24px) !important;
      font-weight: 900;
      letter-spacing: 0.5px;
      white-space: nowrap;
      overflow: visible;
      text-overflow: clip;
      background: linear-gradient(90deg, #ff5b5b, #ffb443, #ffe95b, #5bff8a, #5bc8ff, #b06bff, #ff5b5b);
      background-size: 200% auto;
      -webkit-background-clip: text;
      background-clip: text;
      -webkit-text-fill-color: transparent;
      animation: demo-rainbow-bg 2s linear infinite, demo-celebrate-pop 0.6s ease-out;
    }
    /* 完成時の 🏆✨🎊 は、以前は高さ40pxのバッジの中に42〜68pxの絵文字を
       入れていたので上下にはみ出し、上の COMPLETE! と下の CLEAR TIME 帯に
       重なっていた。バッジ側を中身なりの高さにして、絵文字も1行に収まる
       大きさまで落とす（お祝いの主役はマスコットとネオン帯に任せる）。 */
    .demo-move-badge.celebrate {
      background: transparent !important;
      border-color: transparent !important;
      min-width: 0;
      height: auto;
      padding: 0;
      animation: demo-celebrate-shake 0.5s ease-in-out 4;
    }
    .demo-move-badge.celebrate .demo-move {
      font-family: 'Orbitron', sans-serif;
      font-size: clamp(24px, 7vw, 34px) !important;
      line-height: 1.15;
      animation: demo-rainbow-text 1.2s linear infinite;
    }
    .demo-note.celebrate {
      color: #fff;
      font-weight: 800;
      font-size: 12px;
      background: linear-gradient(90deg, rgba(255,91,91,.3), rgba(255,180,67,.3), rgba(255,233,91,.3), rgba(91,255,138,.3), rgba(91,200,255,.3), rgba(176,107,255,.3));
      background-size: 200% auto;
      border-color: rgba(255, 255, 255, 0.55);
      animation: demo-rainbow-bg 2s linear infinite;
    }
    /* CLEAR TIME はマスコットぶん狭くなった幅に入りきらないことがあるので、
       はみ出させずに折り返させる。 */
    .demo-note.celebrate .demo-note-text { white-space: normal; text-align: center; }

    /* 自己ベスト更新: 通常の虹色セレブレートとは別に、ゴールドのネオン風
       点滅(グロー明滅)にして「これは特別」と一目でわかるようにする。 */
    @keyframes demo-neon-record {
      0%, 100% {
        box-shadow: 0 0 8px 1px rgba(255, 213, 74, 0.55), 0 0 18px 4px rgba(255, 213, 74, 0.3);
        border-color: rgba(255, 213, 74, 0.85);
      }
      50% {
        box-shadow: 0 0 20px 6px rgba(255, 213, 74, 0.95), 0 0 42px 12px rgba(255, 159, 31, 0.65);
        border-color: #fff2b0;
      }
    }
    .demo-note.new-record {
      color: #fff6d8;
      font-weight: 900;
      /* 狭い端末ではマスコットにかぶるので、幅なりに少しだけ縮める */
      font-size: clamp(11px, 3.3vw, 14px);
      white-space: normal;
      background: linear-gradient(90deg, rgba(255, 213, 74, 0.28), rgba(255, 159, 31, 0.28));
      border: 2px solid rgba(255, 213, 74, 0.85);
      animation: demo-neon-record 0.9s ease-in-out infinite;
    }
    .demo-note.new-record .demo-note-text { white-space: nowrap; text-align: center; }

    /* --- 残り20手以下: 「ギリギリゾーン」スリル演出 ---
       正確な手数を隠しつつ、ゴールが近いことだけは伝える。パネルの枠と
       のこりバッジをオレンジ〜赤でじわっと発光させ、ほんの少しだけ
       揺らして緊張感を出す（派手にしすぎて手元を隠さないよう控えめに）。 */
    /* 以前は赤に近い強い発光＋0.16秒の細かい横揺れで、警告表示のように
       見えて手元がうるさかった。ゴールが近いことは「あと少し」顔の
       マスコットが十分伝えてくれるので、枠側はじわっと明るくなる程度の
       琥珀色にとどめ、横揺れは廃止した。 */
    @keyframes demo-thrill-border {
      0%, 100%  { border-color: rgba(255, 150, 60, 0.32); box-shadow: 0 0 8px 0 rgba(255, 140, 50, 0.16); }
      50%       { border-color: rgba(255, 170, 75, 0.60); box-shadow: 0 0 15px 2px rgba(255, 150, 55, 0.30); }
    }
    @keyframes demo-thrill-pulse {
      0%, 100% { transform: scale(1);    box-shadow: 0 0 3px 0 rgba(255, 150, 60, 0.25); }
      50%      { transform: scale(1.03); box-shadow: 0 0 9px 1px rgba(255, 160, 55, 0.45); }
    }
    #demo-panel.thrill-zone {
      animation: demo-thrill-border 1.9s ease-in-out infinite;
    }
    .demo-remain.thrill-zone {
      color: #ffdcb0;
      background: rgba(255, 150, 60, 0.16);
      border-color: rgba(255, 165, 70, 0.55);
      animation: demo-thrill-pulse 1.9s ease-in-out infinite;
    }
    .demo-phase.thrill-zone-text { color: #ffc79a; }

    /* --- ラスト1手（完成直前）: 一番盛り上がる瞬間 ---
       スリルゾーンよりさらに速く・強く発光/振動させ、テキストも
       「FINISH HIM!」に変えて最高潮の緊張感を演出する。 */
    @keyframes demo-final-border {
      0%, 100% { border-color: #ff2d2d; box-shadow: 0 0 16px 3px rgba(255, 45, 45, 0.55); }
      50%      { border-color: #ff8a00; box-shadow: 0 0 34px 10px rgba(255, 138, 0, 0.9); }
    }
    @keyframes demo-final-shake {
      0%, 100% { transform: translateX(0) scale(1); }
      25%      { transform: translateX(-3px) scale(1.01); }
      75%      { transform: translateX(3px) scale(1.01); }
    }
    @keyframes demo-final-pulse {
      0%, 100% { transform: scale(1); }
      50%      { transform: scale(1.18); }
    }
    #demo-panel.final-move {
      animation: demo-final-border 0.45s ease-in-out infinite, demo-final-shake 0.22s ease-in-out infinite;
    }
    .demo-remain.final-move {
      color: #fff;
      font-family: 'Orbitron', sans-serif;
      font-weight: 900;
      font-size: 14px !important;
      letter-spacing: 0.5px;
      white-space: nowrap;
      background: linear-gradient(135deg, #ff0000, #ff6a00);
      border-color: #fff;
      animation: demo-final-pulse 0.45s ease-in-out infinite;
    }
    .demo-phase.final-move-text { color: #ff8a3d; font-weight: 800; }

    @media (prefers-reduced-motion: reduce) {
      #demo-panel.celebrate,
      .demo-phase.celebrate-text,
      .demo-move-badge.celebrate,
      .demo-move-badge.celebrate .demo-move,
      .demo-note.celebrate,
      .demo-note.new-record,
      #demo-panel.thrill-zone,
      .demo-remain.thrill-zone,
      #demo-panel.final-move,
      .demo-remain.final-move,
      .demo-mascot,
      .demo-mascot.pop { animation: none !important; }
      .mascot-art { transition: none !important; }
    }

    /* --- 💡 吸い込み演出（Genie / Suck-in effect） ---------------------
       閉じたい枠（ヘルプパネル・リザルト表示）に .absorbing を付けると、
       JS が実測した「💡ヘルプボタンまでの距離」へ向かって縮みながら
       吸い込まれて消える。移動量・縮小率は JS が CSS 変数で流し込む:
         --absorb-x / --absorb-y … 2要素の中心どうしの差分(px)
         --absorb-scale          … 吸い込み先の穴の大きさに合わせた最終スケール
         --absorb-rot            … 吸われる方向へのわずかなひねり
         --absorb-dur            … 全体の所要時間
       transform 1本にまとめず translate / scale / rotate を個別プロパティで
       持たせているのは、「移動は溜め→加速（負の制御点あり）」「縮小は
       オーバーシュートさせない ease-in」と、成分ごとに別のイージングを
       当てたいため。transform 1本だと全成分が同じカーブに縛られ、scale が
       マイナスに振れて絵が反転する事故も起きる。 */
    .absorbing {
      /* JS が入れ忘れても破綻しないための既定値 */
      --absorb-x: 0px;
      --absorb-y: 0px;
      --absorb-scale: 0.06;
      --absorb-rot: -8deg;
      --absorb-dur: 520ms;

      /* 飛んでいる最中のタップを殺す。閉じ始めた枠はもう操作対象ではない */
      pointer-events: none !important;

      /* 重要: #demo-panel.final-move の shake 系 keyframes は transform を
         持つため、止めないと吸い込みの移動量を毎フレーム上書きされて
         しまう（thrill-zone の横揺れは廃止したが、念のため止めておく）。 */
      animation: none !important;

      /* 中心基準で縮めば、中心どうしの差分ぶん動かすだけで吸い込み先に
         ぴたりと収束する。 */
      transform-origin: 50% 50%;

      /* 他のUIの下をくぐらせない */
      z-index: 999;
      will-change: translate, scale, opacity, filter;
      backface-visibility: hidden;

      transition:
        /* 動きだしにほんの少し「溜め」を作ってから一気に吸われる（負の制御点） */
        translate var(--absorb-dur) cubic-bezier(0.55, -0.35, 0.65, 1),
        /* 縮小は最後に急加速。オーバーシュートさせない */
        scale     var(--absorb-dur) cubic-bezier(0.45, 0, 0.7, 0.15),
        rotate    var(--absorb-dur) ease-in,
        /* 透明化は遅らせ気味に。早く消えると「吸い込まれた」ではなく
           「消えた」に見えてしまう。 */
        opacity   var(--absorb-dur) cubic-bezier(0.85, 0, 1, 0.55),
        filter    var(--absorb-dur) ease-in;

      translate: var(--absorb-x) var(--absorb-y);
      scale: var(--absorb-scale);
      rotate: var(--absorb-rot);
      /* #msg のようにインラインで opacity を持つ要素にも効かせる必要があるため
         !important。.absorbing は閉じる一瞬しか付かないので副作用はない。 */
      opacity: 0.04 !important;
      /* 吸われる瞬間に光が伸びるようなにじみ。強すぎると汚れて見えるので控えめに */
      filter: blur(2px) brightness(1.35) saturate(1.35);
    }

    /* 個別プロパティ(translate/scale/rotate)が使えない環境向けの保険。
       同じ変数をそのまま transform に流すのでイージングは1本になるが、
       「動かないまま消えるだけ」にはならない。 */
    @supports not (translate: 10px 10px) {
      .absorbing {
        transition:
          transform var(--absorb-dur) cubic-bezier(0.5, -0.3, 0.7, 1),
          opacity   var(--absorb-dur) cubic-bezier(0.85, 0, 1, 0.55),
          filter    var(--absorb-dur) ease-in;
        transform: translate(var(--absorb-x), var(--absorb-y))
                   rotate(var(--absorb-rot))
                   scale(var(--absorb-scale));
      }
    }

    /* #msg（SOLVED! バナー）は自分の位置決めに
       `transform: translate(-50%, -50%) scale(0.7)` を使っている。
       個別プロパティの translate/scale は transform より外側に掛かるため、
       そのままだと -50% ぶんのセンタリングが縮小率に巻き込まれて、
       最後に大きく横へずれる。ここだけは transform 1本にまとめ直し、
       センタリング → 移動 → 縮小 の順を自分で保証する。 */
    #msg.absorbing {
      translate: none;
      scale: 1;
      rotate: 0deg;
      transition:
        transform var(--absorb-dur) cubic-bezier(0.5, -0.3, 0.7, 1),
        opacity   var(--absorb-dur) cubic-bezier(0.85, 0, 1, 0.55),
        filter    var(--absorb-dur) ease-in;
      transform: translate(-50%, -50%)
                 translate(var(--absorb-x), var(--absorb-y))
                 rotate(var(--absorb-rot))
                 scale(var(--absorb-scale));
    }

    /* 吸い込んだ側（💡ヘルプボタン）が「ごくり」と飲み込む反応。
       吸い込みの終わりぎわに JS が付け外しする。 */
    @keyframes absorb-swallow {
      0%   { transform: scale(1);    filter: brightness(1); }
      35%  { transform: scale(1.14); filter: brightness(1.6); }
      60%  { transform: scale(0.94); filter: brightness(1.15); }
      100% { transform: scale(1);    filter: brightness(1); }
    }
    .absorb-hit {
      animation: absorb-swallow 380ms cubic-bezier(0.3, 1.4, 0.5, 1);
      box-shadow: 0 0 22px 6px rgba(255, 213, 74, 0.55),
                  inset 0 0 16px rgba(255, 213, 74, 0.25) !important;
      border-color: #ffe9a3 !important;
    }

    /* 動きを減らす設定では演出しない。JS 側でもアニメを飛ばして即閉じるが、
       CSS 単体で見たときの保険としてここでも無効化しておく。 */
    @media (prefers-reduced-motion: reduce) {
      .absorbing {
        transition: opacity 120ms linear !important;
        translate: none;
        scale: 1;
        rotate: 0deg;
        transform: none;
        filter: none;
        opacity: 0 !important;
      }
      .absorb-hit { animation: none; }
    }

    #oll-overlay {
      display: none;
      position: fixed;
      inset: 0;
      /* 背面が透けないよう完全不透明の背景に変更 */
      background: #08080b;
      z-index: 30;
      align-items: center;
      justify-content: center;
    }
    #oll-overlay.show {
      display: flex;
    }
    .alg-panel {
      background: #1c1c22;
      border: 1px solid #3a3a48;
      border-radius: 14px;
      text-align: center;
      width: min(94vw, 460px);
      /* 86vh は Android のアドレスバー分を含んだ高さになりがちで、特に
         横画面（高さが元々小さい）ではその差が全体に占める割合が大きく、
         パネルが実際の表示領域より下にはみ出す。dvh (実際に見えている
         高さ) を優先することでパネル全体が画面内に収まりやすくする。 */
      max-height: 86vh;
      max-height: 86dvh;
      display: flex;
      flex-direction: column;
      /* パネル自体はもうスクロールしない。右上のバツ印ボタンをスクロール
         内容に関係なく常に同じ場所に表示させるための位置基準にするだけの
         外枠になり、実際のスクロールは中の .alg-panel-scroll が担う。 */
      position: relative;
      overflow: hidden;
      padding: 0;
    }
    .alg-panel-scroll {
      flex: 1;
      min-height: 0;
      overflow-y: auto;
      -webkit-overflow-scrolling: touch;
      overscroll-behavior: contain;
      padding: 18px;
      text-align: center;
    }
    /* 右上に常時浮かぶバツ印の閉じるボタン。テキストの「閉じる」ボタンは
       一覧の下までスクロールしないと押せないので、片手でもすぐ閉じられる
       ように、スクロール量に関係なく同じ場所に留まる丸いアイコンボタンを
       用意する。文字ではなく記号にして、他の丸いアクションボタン（OLL等の
       起動ボタン）と馴染むよう同じ質感にしている。 */
    .alg-panel-close-x {
      position: absolute;
      top: 10px;
      right: 10px;
      z-index: 5;
      width: 34px;
      height: 34px;
      border-radius: 50%;
      border: 1px solid #3a3a48;
      background: rgba(255, 255, 255, 0.06);
      color: #c8c8d4;
      font-size: 18px;
      line-height: 1;
      display: flex;
      align-items: center;
      justify-content: center;
      cursor: pointer;
      touch-action: manipulation;
      transition: background 0.15s, color 0.15s, border-color 0.15s;
    }
    .alg-panel-close-x:active {
      background: var(--tc);
      border-color: var(--tc);
      color: #04120f;
    }
    .alg-panel h2 {
      font-size: 16px;
      margin-bottom: 10px;
    }
    .oll-count {
      font-size: 11px;
      color: var(--tc);
      font-weight: 600;
    }
    .oll-mode-row {
      margin-bottom: 4px;
    }
    .oll-mode-hint {
      margin-bottom: 12px;
      text-align: left;
    }
    #oll-grid {
      display: grid;
      grid-template-columns: repeat(auto-fill, minmax(60px, 1fr));
      gap: 8px;
      /* overflow-y は付けない: スクロールはパネル全体(.alg-panel)側で
         まとめて行う。ここだけ独立してスクロールすると、パネル全体を
         なぞってもリストしか動かなくなってしまう。 */
      padding: 4px 2px 12px;
      margin-bottom: 12px;
    }
    .oll-case {
      background: #23232c;
      border: 1px solid #3a3a48;
      border-radius: 8px;
      padding: 5px 3px 3px;
      cursor: pointer;
      touch-action: manipulation;
      display: flex;
      flex-direction: column;
      align-items: center;
      gap: 3px;
    }
    .oll-case:active {
      border-color: var(--tc);
      background: #2c2c38;
    }
    .oll-case-num {
      font-size: 10px;
      font-weight: 700;
      color: #aaa;
    }
    /* Recognition diagram: the 3x3 top face inset in a 5x5 grid, with the
       12 surrounding tabs showing which side stickers already face up. */
    .oll-diagram {
      display: grid;
      grid-template-columns: repeat(5, 1fr);
      grid-template-rows: repeat(5, 1fr);
      gap: 1px;
      width: 40px;
      height: 40px;
    }
    .oll-cell {
      background: #33333f;
      border-radius: 1px;
    }
    /* The "already facing up" colour is not fixed to yellow: it follows
       whichever colour is actually on top of the cube right now, so the
       diagrams read correctly for a colour-neutral solver. The variable is
       set on the overlay each time the panel opens. */
    .oll-cell.on {
      background: var(--alg-U, #FFD500);
    }
    .oll-cell.tab {
      background: #2a2a33;
      border-radius: 1px;
    }
    .oll-cell.tab.on {
      background: var(--alg-U, #FFD500);
    }
    /* --- ZBLL (493種) ---------------------------------------------------
       The third algorithm button sits between OLL and PLL, where the layout
       already reserved a slot. It is styled in gold so the three read as a
       progression: OLL (teal) -> ZBLL (gold) -> PLL (orange). */
    .alg-btn#zbll-btn {
      --accent: #ffd24a;
      border-color: #6b5a2c;
      background: linear-gradient(160deg, #3b3324, #241f16);
    }
    .alg-btn#zbll-btn .alg-name {
      color: #ffd24a;
      text-shadow: 0 0 6px rgba(255, 210, 74, 0.5);
    }
    /* Brushed-gold lettering where the browser can clip a gradient to text;
       the plain gold above stays as the fallback so the label can never end
       up invisible. */
    @supports ((-webkit-background-clip: text) or (background-clip: text)) {
      .alg-btn#zbll-btn .alg-name {
        background: linear-gradient(180deg, #fff6cf, #ffd24a 48%, #c08f22);
        -webkit-background-clip: text;
        background-clip: text;
        -webkit-text-fill-color: transparent;
        text-shadow: 0 0 4px rgba(255, 210, 74, 0.45);
      }
    }
    .alg-btn#zbll-btn .alg-sub {
      color: #e9c358;
      opacity: 1;
    }
    #zbll-overlay {
      display: none;
      position: fixed;
      inset: 0;
      /* 背面が透けないよう完全不透明の背景に変更 */
      background: #08080b;
      z-index: 30;
      align-items: center;
      justify-content: center;
    }
    #zbll-overlay.show {
      display: flex;
    }
    #zbll-overlay .alg-panel {
      width: min(96vw, 560px);
    }
    #layout-overlay .alg-panel {
      padding: 0;
    }
    .zbll-count {
      font-size: 11px;
      font-weight: 600;
      color: #ffd24a;
    }
    /* Group tabs: the seven corner-orientation families plus the fully
       oriented (PLL) family, and an "all" tab for free-text searching. */
    .zbll-tabs {
      display: flex;
      flex-wrap: wrap;
      gap: 4px;
      margin: 2px 0 8px;
    }
    .zbll-tab {
      flex: 1 1 auto;
      min-width: 50px;
      padding: 5px 4px;
      border-radius: 8px;
      background: #23232c;
      border: 1px solid #3a3a48;
      color: #b8b8c4;
      font-size: 11px;
      font-weight: 800;
      line-height: 1.2;
      cursor: pointer;
      touch-action: manipulation;
    }
    .zbll-tab .zbll-tab-n {
      display: block;
      font-size: 8px;
      font-weight: 700;
      color: #78788a;
    }
    .zbll-tab.on {
      border-color: #ffd24a;
      color: #ffd24a;
      background: #2f2a1b;
    }
    .zbll-tab.on .zbll-tab-n {
      color: #c9a13a;
    }
    .zbll-filter-row {
      display: flex;
      gap: 6px;
      margin-bottom: 6px;
    }
    .zbll-input {
      flex: 1 1 auto;
      min-width: 0;
      background: #15151b;
      border: 1px solid #3a3a48;
      border-radius: 8px;
      color: #e6e6ee;
      /* iOS Safari zooms the whole page in when a focused input's font-size
         is under 16px, and with user-scalable=no (set on this page) there
         is no pinch gesture left to zoom back out -- the page gets stuck
         zoomed in. 16px is the minimum that keeps Safari from zooming at
         all, so the row below is sized to fit that real size rather than
         faking a smaller one. */
      font-size: 16px;
      padding: 6px 9px;
      font-family: inherit;
    }
    .zbll-input:focus {
      outline: none;
      border-color: #ffd24a;
    }
    .zbll-input::placeholder {
      color: #6a6a78;
    }
    .zbll-num {
      flex: 0 0 86px;
    }
    .zbll-clear {
      flex: 0 0 auto;
      padding: 0 11px;
      background: #23232c;
      border: 1px solid #3a3a48;
      border-radius: 8px;
      color: #b8b8c4;
      font-size: 11px;
      font-weight: 700;
      cursor: pointer;
      touch-action: manipulation;
    }
    .zbll-result {
      font-size: 10px;
      color: #8a8a99;
      text-align: left;
      margin-bottom: 6px;
    }
    #zbll-grid {
      display: grid;
      grid-template-columns: repeat(auto-fill, minmax(74px, 1fr));
      gap: 8px;
      /* overflow-y は付けない: スクロールはパネル全体(.alg-panel)側で
         まとめて行う。 */
      padding: 4px 2px 12px;
      margin-bottom: 12px;
    }
    .zbll-case-name {
      font-size: 9px;
      font-weight: 800;
      color: #ddd;
      line-height: 1.1;
    }
    .zbll-case-cp {
      font-size: 8px;
      color: #7d7d8c;
      line-height: 1.1;
    }
    .oll-case.zbll-case:active {
      border-color: #ffd24a;
      background: #2f2a1b;
    }
    /* Same 5x5 recognition grid as OLL/PLL: the top face in the middle, the
       12 side stickers as tabs. Every cell is painted through a --alg-*
       variable, so the whole sheet stays colour-neutral. */
    .zbll-diagram {
      display: grid;
      grid-template-columns: repeat(5, 1fr);
      grid-template-rows: repeat(5, 1fr);
      gap: 1px;
      width: 46px;
      height: 46px;
    }
    .zbll-cell {
      background: #33333f;
      border-radius: 1px;
    }
    .zbll-cell.blank {
      background: transparent;
    }
    .zbll-empty {
      grid-column: 1 / -1;
      padding: 20px 8px;
      color: #8a8a99;
      font-size: 12px;
    }
    .record-badge.zbll {
      background: rgba(255, 210, 74, 0.15);
      color: #ffd24a;
      border: 1px solid rgba(255, 210, 74, 0.45);
    }
    @keyframes size-rainbow-shift {
      0% { background-position: 0% 50%; }
      100% { background-position: 300% 50%; }
    }
    @keyframes size-rainbow-pulse {
      0%, 100% { filter: brightness(1) drop-shadow(0 0 3px rgba(255, 255, 255, 0.25)); }
      50% { filter: brightness(1.7) drop-shadow(0 0 9px rgba(255, 255, 255, 0.75)); }
    }
    #pll-overlay {
      display: none;
      position: fixed;
      inset: 0;
      /* 背面が透けないよう完全不透明の背景に変更 */
      background: #08080b;
      z-index: 30;
      align-items: center;
      justify-content: center;
    }
    #pll-overlay.show {
      display: flex;
    }
    /* PLL は全21種と少なく、OLL(57種)やZBLL(493種)と違って本来
       スクロールせずに1画面へ収めるのが現実的なリスト。#pll-overlay に
       スコープしたこれらの上書きで見出し・余白・カードを詰めて、端末の
       画面に収まりやすくする。.alg-panel-scroll のスクロールは万一
       画面が小さい端末向けの保険として残す。 */
    #pll-overlay h2 {
      margin-bottom: 6px;
    }
    #pll-overlay .alg-cn-note {
      margin-bottom: 8px;
    }
    #pll-overlay .oll-mode-row {
      margin-bottom: 2px;
    }
    #pll-overlay .oll-mode-hint {
      margin-bottom: 8px;
      font-size: 11px;
    }
    #pll-grid {
      display: grid;
      grid-template-columns: repeat(auto-fill, minmax(56px, 1fr));
      gap: 6px;
      /* overflow-y は付けない: スクロールはパネル全体(.alg-panel)側で
         まとめて行う。 */
      padding: 4px 2px 8px;
      margin-bottom: 8px;
    }
    #pll-overlay .oll-case {
      padding: 4px 2px 2px;
      gap: 2px;
    }
    .pll-case-name {
      font-size: 10px;
      font-weight: 800;
      color: #ddd;
    }
    /* PLL is recognised from the side sticker colours around the top face,
       so the diagram shows the solved top plus those 12 coloured tabs. */
    .pll-diagram {
      display: grid;
      grid-template-columns: repeat(5, 1fr);
      grid-template-rows: repeat(5, 1fr);
      gap: 1px;
      width: 36px;
      height: 36px;
    }
    .pll-cell {
      border-radius: 1px;
      background: var(--alg-U, #FFD500);
    }
    .pll-cell.blank {
      background: transparent;
    }
    .oll-cell.blank {
      background: transparent;
    }

    /* Colour-neutral banner: says out loud which colour the diagrams below
       are currently drawn for, with a swatch of that exact colour. */
    .alg-cn-note {
      display: flex;
      align-items: center;
      gap: 6px;
      margin: 2px 0 10px;
      text-align: left;
    }
    .alg-cn-swatch {
      width: 13px;
      height: 13px;
      border-radius: 3px;
      flex: 0 0 auto;
      background: var(--alg-U, #FFD500);
      border: 1px solid rgba(255, 255, 255, 0.35);
    }

    /* Tap-cube mode: the rotation buttons come off the screen entirely so
       the cube can take the space, but OLL/PLL stay reachable. */
    /* 操作説明の行は画面には出さない。文言はスクリーンリーダー向けに残すので、
       display:none ではなく「視覚的にだけ隠す」書き方にしてある。 */
    .tap-hint,
    body.tap-mode .tap-hint {
      position: absolute;
      width: 1px;
      height: 1px;
      margin: -1px;
      padding: 0;
      border: 0;
      overflow: hidden;
      clip-path: inset(50%);
      white-space: nowrap;
    }
    body.tap-mode .controls-label:not(.tap-hint),
    body.tap-mode #face-grid {
      display: none;
    }

    /* --- First-launch mode picker ---
       The very first thing a new visitor sees. The app has two completely
       different ways to turn a layer, and which one you want depends on
       whether you already know notation -- something the app can't guess.
       So instead of silently picking one and hoping the user finds the
       setting, we ask once, up front.
       Each choice is a card with a small looping animation of the actual
       gesture, so the difference is understood by watching rather than by
       reading: one shows a finger dragging across the cube itself, the
       other shows a finger tapping a lettered button. The choice is
       pre-selected (never a dead end) and one big button starts the app. */
    /* ============================================================
       言語選択（いちばん最初の画面）
       9言語を「キューブの1面 (3×3)」に見立てて並べる。見た目の語彙は
       この画面より後ろの UI と完全に共通で、背景 #121214 / カードは
       #1c1c22 + 2px #3a3a48 / 選択色は var(--tc)（既定 #00ffcc）。新しい色は足さない。
       枠の中のすき間だけ暗くして、9枚のカードが「1つの面」に見えるよう
       にしている。
       ============================================================ */
    #lang-overlay {
      display: none;
      position: fixed;
      inset: 0;
      background: #121214;
      /* welcome (50) より必ず手前。言語 → 回し方 の順に進むため。 */
      z-index: 60;
      align-items: center;
      justify-content: center;
      overflow-y: auto;
      -webkit-overflow-scrolling: touch;
      padding: calc(18px + env(safe-area-inset-top)) 14px calc(18px + env(safe-area-inset-bottom));
    }
    #lang-overlay.show {
      display: flex;
    }
    #lang-panel {
      width: min(94vw, 400px);
      margin: auto;
      text-align: center;
    }
    .lang-title {
      font-size: 19px;
      font-weight: 800;
      line-height: 1.4;
    }
    .lang-sub {
      font-size: 12.5px;
      font-weight: var(--ui-weight-light);
      font-family: var(--ui-font-soft);
      letter-spacing: 0.03em;
      color: #b8b8c8;
      margin: 7px 0 16px;
      line-height: 1.6;
    }
    .lang-face {
      display: grid;
      grid-template-columns: repeat(3, 1fr);
      gap: 8px;
      padding: 10px;
      border-radius: 20px;
      background: #0b0b0d;
      border: 2px solid #26262f;
      transform-style: preserve-3d;
      transition: transform 0.45s cubic-bezier(0.7, 0, 0.2, 1), opacity 0.35s ease;
    }
    /* 1マス選ぶと面がひと回りして、回し方の画面に入れ替わる。 */
    .lang-face.turn {
      transform: rotateY(-90deg) scale(0.9);
      opacity: 0;
    }
    .lang-cell {
      position: relative;
      aspect-ratio: 1;
      display: flex;
      flex-direction: column;
      align-items: center;
      justify-content: center;
      gap: 4px;
      padding: 4px;
      background: #1c1c22;
      border: 2px solid #3a3a48;
      border-radius: 14px;
      color: #fff;
      cursor: pointer;
      touch-action: manipulation;
      transition: border-color 0.15s, background 0.15s, transform 0.1s;
    }
    .lang-cell:active {
      transform: scale(0.96);
    }
    .lang-cell.selected {
      border-color: var(--tc);
      background: rgba(var(--tc-rgb), 0.09);
    }
    .lang-name {
      font-size: 13px;
      font-weight: 800;
      line-height: 1.2;
      text-align: center;
      word-break: keep-all;
    }
    .lang-foot {
      font-size: 11.5px;
      color: #7d7d90;
      line-height: 1.6;
      margin-top: 12px;
    }
    /* 横向きは高さが足りないので、正方形をやめて横長のマスにする。 */
    @media (orientation: landscape) and (max-height: 500px) {
      #lang-panel { width: min(94vw, 480px); }
      .lang-title { font-size: 16px; }
      .lang-sub { margin: 5px 0 10px; }
      .lang-face { gap: 6px; padding: 8px; }
      .lang-cell { aspect-ratio: 1.9; border-radius: 11px; }
      .lang-name { font-size: 12px; }
    }
    @media (prefers-reduced-motion: reduce) {
      .lang-face { transition: none; }
    }

    /* パネル左上のアイコン。✕ と対になる位置に置き、見た目は完全に共通。 */
    .panel-icon-left {
      right: auto;
      left: 10px;
      font-size: 16px;
    }
    /* 言語画面の ✕ は、設定から開いたときだけ出す。初回起動時は
       「選ばずに閉じる」先が無いので、出すとむしろ迷わせる。 */
    #lang-close-x {
      display: none;
      top: calc(10px + env(safe-area-inset-top));
    }
    #lang-overlay.dismissable #lang-close-x {
      display: flex;
    }

    /* ============================================================
       確認ダイアログ（言語切り替えなど、取り消せない操作の直前に出す）
       ============================================================ */
    #confirm-overlay {
      display: none;
      position: fixed;
      inset: 0;
      z-index: 80;
      /* 背面のテキストが透けて重なって見える不具合の修正:
         半透明(rgba)だった背景を完全不透明な単色に変更。 */
      background: #08080b;
      -webkit-backdrop-filter: blur(3px);
      backdrop-filter: blur(3px);
      align-items: center;
      justify-content: center;
      padding: calc(18px + env(safe-area-inset-top)) 18px calc(18px + env(safe-area-inset-bottom));
    }
    #confirm-overlay.show {
      display: flex;
    }
    #confirm-panel {
      width: min(92vw, 340px);
      background: #1c1c22;
      border: 2px solid #3a3a48;
      border-radius: 16px;
      padding: 18px 16px 14px;
      text-align: center;
    }
    .confirm-title {
      font-size: 16px;
      font-weight: 800;
      line-height: 1.4;
    }
    .confirm-body {
      font-size: 12.5px;
      font-weight: var(--ui-weight-light);
      font-family: var(--ui-font-soft);
      color: #b8b8c8;
      line-height: 1.7;
      margin: 9px 0 15px;
    }
    .confirm-actions {
      display: flex;
      gap: 9px;
    }
    .confirm-btn {
      flex: 1 1 0;
      min-width: 0;
      min-height: 46px;
      padding: 12px 10px;
      border-radius: 12px;
      font-size: 14px;
      font-weight: 800;
      cursor: pointer;
      touch-action: manipulation;
      border: 2px solid #3a3a48;
      background: transparent;
      color: #c8c8d4;
    }
    .confirm-btn:active {
      background: rgba(255, 255, 255, 0.06);
    }
    /* 実行側だけを塗る。「やめる」が同じ強さで並ぶと、
       止めたいときほど押し間違えるため。 */
    .confirm-btn.primary {
      border-color: var(--tc);
      background: var(--tc);
      color: #04120f;
    }
    .confirm-btn.primary:active {
      background: #00d9ad;
      border-color: #00d9ad;
    }

    #welcome-overlay {
      display: none;
      position: fixed;
      inset: 0;
      background: #121214;
      z-index: 50;
      align-items: center;
      justify-content: center;
      overflow-y: auto;
      -webkit-overflow-scrolling: touch;
      padding: calc(18px + env(safe-area-inset-top)) 14px calc(18px + env(safe-area-inset-bottom));
    }
    #welcome-overlay.show {
      display: flex;
    }
    #welcome-panel {
      width: min(94vw, 400px);
      margin: auto;
      text-align: center;
    }
    .welcome-logo {
      font-family: 'Orbitron', 'Segoe UI', sans-serif;
      font-size: 30px;
      letter-spacing: 1.5px;
      font-weight: 900;
      text-transform: uppercase;
      margin-bottom: 14px;
      line-height: 1.15;
      background: linear-gradient(90deg, var(--tc), #4da6ff 45%, #ff3d81 100%);
      -webkit-background-clip: text;
      background-clip: text;
      -webkit-text-fill-color: transparent;
      /* text-shadow instead of filter:drop-shadow() -- the latter, combined
         with background-clip:text + transparent text-fill-color, hits a
         WKWebView compositing bug that's specific to standalone/Home-Screen
         launches (regular Safari tabs render it fine): the gradient title
         comes out jagged/aliased on the very first screen the app shows.
         text-shadow gives the same glow and isn't affected by the
         transparent fill, so it sidesteps the bug entirely. */
      text-shadow: 0 0 10px rgba(var(--tc-rgb), 0.45), 0 0 22px rgba(77, 166, 255, 0.25);
    }
    .welcome-title {
      font-size: 19px;
      font-weight: 800;
      line-height: 1.4;
    }
    .welcome-sub {
      font-size: 12.5px;
      font-weight: var(--ui-weight-light);
      font-family: var(--ui-font-soft);
      letter-spacing: 0.03em;
      color: #b8b8c8;
      margin: 7px 0 16px;
      line-height: 1.6;
    }
    /* Whole card is the tap target -- no small radio dot to hit. */
    .mode-card {
      position: relative;
      display: flex;
      align-items: center;
      gap: 12px;
      width: 100%;
      text-align: left;
      background: #1c1c22;
      border: 2px solid #3a3a48;
      border-radius: 16px;
      padding: 14px 40px 14px 14px;
      margin-bottom: 11px;
      color: #fff;
      cursor: pointer;
      touch-action: manipulation;
      transition: border-color 0.15s, background 0.15s, transform 0.1s;
    }
    .mode-card:active {
      transform: scale(0.985);
    }
    .mode-card.selected {
      border-color: var(--tc);
      background: rgba(var(--tc-rgb), 0.09);
    }
    .mode-check {
      position: absolute;
      top: 50%;
      right: 13px;
      transform: translateY(-50%);
      width: 22px;
      height: 22px;
      border-radius: 50%;
      border: 2px solid #55556a;
      display: flex;
      align-items: center;
      justify-content: center;
      font-size: 12px;
      font-weight: 900;
      color: #000;
      transition: background 0.15s, border-color 0.15s;
    }
    .mode-check::after {
      content: '✓';
      opacity: 0;
      transition: opacity 0.15s;
    }
    .mode-card.selected .mode-check {
      background: var(--tc);
      border-color: var(--tc);
    }
    .mode-card.selected .mode-check::after {
      opacity: 1;
    }
    .mode-body {
      display: block;
      flex: 1 1 auto;
      min-width: 0;
    }
    .mode-card-title {
      display: block;
      font-size: 15px;
      font-weight: 800;
      line-height: 1.35;
    }
    .mode-badge {
      display: inline-block;
      font-size: 10px;
      font-weight: 800;
      color: #04120f;
      background: var(--tc);
      border-radius: 999px;
      padding: 2px 7px;
      margin-top: 5px;
    }
    /* 「はじめての方におすすめ」バッジの横に添える★1つ表示。
       レベル1=★が1つだけ塗りつぶし、という直感的な表現。
       既存の mode-badge（塗りつぶしピル）と並んだときにうるさく
       ならないよう、こちらは輪郭だけの控えめな器にしている。 */
    .mode-badge-row {
      display: flex;
      align-items: center;
      flex-wrap: wrap;
      gap: 6px;
      margin-top: 5px;
    }
    .mode-badge-row .mode-badge {
      margin-top: 0;
    }
    .beginner-star-badge {
      display: inline-flex;
      align-items: center;
      /* 中の★は fill/stroke を currentColor にしてあるので、
         ここの color がテーマカラーの切り替えにそのまま追従する。 */
      color: var(--tc);
      gap: 2px;
      padding: 3px 7px 3px 6px;
      border-radius: 999px;
      background: rgba(var(--tc-rgb), 0.10);
      border: 1.2px solid rgba(var(--tc-rgb), 0.45);
    }
    .beginner-star-badge svg {
      display: block;
    }
    /* タップ操作モードのカードにだけ 💡ヘルプの紹介を出す。ここだけは
       説明文というより「声かけ」なので、丸ゴシック系のやわらかい書体に
       する。丸ゴシックは iOS/Mac には標準で入っていて、無い環境では
       ふつうのゴシックに落ちるだけなので、Webフォントは読み込まない
       （日本語Webフォントは重く、最初の画面の表示を遅らせてしまうため）。 */
    .mode-help {
      display: flex;
      gap: 7px;
      align-items: center;
      text-align: left;
      margin-top: 8px;
      padding: 8px 10px;
      border-radius: 13px;
      background: linear-gradient(180deg, rgba(255, 213, 74, 0.17), rgba(255, 213, 74, 0.07));
      border: 1px solid rgba(255, 213, 74, 0.34);
      font-family: var(--ui-font-soft);
    }
    .mode-help-icon {
      flex: 0 0 auto;
      font-size: 16px;
      line-height: 1.35;
    }
    .mode-help-text {
      font-size: 13px;
      font-weight: 800;
      line-height: 1.45;
      color: #ffd54a;
      letter-spacing: 0.01em;
    }
    @media (max-height: 700px) {
      .mode-help { padding: 6px 9px; margin-top: 6px; }
      .mode-help-text { font-size: 12px; }
    }
    .mode-desc {
      display: block;
      font-size: 12px;
      color: #b9b9c8;
      line-height: 1.6;
      margin-top: 6px;
      overflow-wrap: break-word;
      text-wrap: balance;
    }
    .mode-desc b {
      color: #fff;
      font-weight: 700;
    }
    /* --- The two little animated demos --- */
    .mode-demo {
      position: relative;
      flex: 0 0 auto;
      width: 74px;
      height: 74px;
      border-radius: 12px;
      background: #101014;
      border: 1px solid #33333f;
      display: flex;
      align-items: center;
      justify-content: center;
      overflow: hidden;
      /* Forces WebKit to recompute the rounded-corner clip as a real
         anti-aliased mask instead of the plain overflow:hidden clip path.
         Needed because this box clips an animated, filtered child
         (.demo-finger) -- without this, WKWebView promotes that child to
         its own GPU layer and the corners it clips against come out
         jagged/stair-stepped. */
      -webkit-mask-image: -webkit-radial-gradient(white, black);
    }
    .demo-cube {
      display: block;
      width: 48px;
      border-radius: 4px;
      overflow: hidden;
    }
    .demo-row {
      display: flex;
      gap: 2px;
      margin-bottom: 2px;
    }
    .demo-row:last-child {
      margin-bottom: 0;
    }
    .demo-row i {
      display: block;
      flex: 1 1 0;
      height: 14px;
      border-radius: 2px;
      background: #4a4a58;
    }
    .demo-row.swipe {
      animation: demoSwipe 2.6s ease-in-out infinite;
    }
    .demo-row.swipe i {
      background: var(--tc);
    }
    @keyframes demoSwipe {
      0%, 22% { transform: translateX(0); }
      48%, 70% { transform: translateX(16px); }
      100% { transform: translateX(0); }
    }
    /* Notation demo: two stacked mini buttons in real sticker colours, with
       the ‹ › halves that the real cards use, so the picker looks like the
       thing it is describing. */
    .demo-keys {
      display: block;
      width: 52px;
    }
    .demo-key {
      display: flex;
      align-items: center;
      justify-content: space-between;
      height: 21px;
      border-radius: 5px;
      padding: 0 4px;
      margin-bottom: 5px;
      font-size: 11px;
      font-weight: 800;
      line-height: 1;
    }
    .demo-key:last-child {
      margin-bottom: 0;
    }
    .demo-key s {
      text-decoration: none;
      opacity: 0.55;
      font-size: 10px;
    }
    .demo-key.u { background: #ffffff; color: #141416; }
    .demo-key.r { background: #b71234; color: #ffffff; }
    .demo-key.press {
      animation: demoPress 2.6s ease-in-out infinite;
    }
    @keyframes demoPress {
      0%, 30% { transform: scale(1); box-shadow: 0 0 0 0 rgba(var(--tc-rgb), 0); }
      42% { transform: scale(0.9); box-shadow: 0 0 0 3px rgba(var(--tc-rgb), 0.55); }
      60%, 100% { transform: scale(1); box-shadow: 0 0 0 0 rgba(var(--tc-rgb), 0); }
    }
    /* One finger sprite, positioned per-demo, moving in time with the
       layer/button animation above so the gesture reads as cause + effect. */
    .demo-finger {
      position: absolute;
      font-size: 19px;
      line-height: 1;
      pointer-events: none;
      filter: drop-shadow(0 1px 2px rgba(0, 0, 0, 0.8));
    }
    .demo-finger.drag {
      left: 11px;
      top: 36px;
      animation: demoFingerDrag 2.6s ease-in-out infinite;
    }
    @keyframes demoFingerDrag {
      0%, 22% { transform: translateX(0); opacity: 1; }
      48%, 70% { transform: translateX(16px); opacity: 1; }
      71% { opacity: 0; }
      86% { opacity: 0; transform: translateX(0); }
      100% { opacity: 1; transform: translateX(0); }
    }
    .demo-finger.tap {
      left: 34px;
      top: 34px;
      animation: demoFingerTap 2.6s ease-in-out infinite;
    }
    @keyframes demoFingerTap {
      0%, 30% { transform: translateY(3px); }
      42% { transform: translateY(-1px); }
      60%, 100% { transform: translateY(3px); }
    }
    @media (prefers-reduced-motion: reduce) {
      .demo-row.swipe,
      .demo-key.press,
      .demo-finger {
        animation: none;
      }
    }
    .welcome-start {
      width: 100%;
      margin-top: 7px;
      padding: 15px 20px;
      min-height: 54px;
      border: none;
      border-radius: 12px;
      background: var(--tc);
      color: #04120f;
      font-size: 16px;
      font-weight: 800;
      cursor: pointer;
      touch-action: manipulation;
    }
    .welcome-start:active {
      background: #00d9ad;
    }
    .welcome-start:disabled {
      opacity: 0.4;
      cursor: not-allowed;
    }
    .welcome-start:disabled:active {
      background: var(--tc);
    }
    .welcome-foot {
      font-size: 11.5px;
      color: #7d7d90;
      line-height: 1.6;
      margin-top: 11px;
    }
    /* Landscape phones are short: shrink the demos and tighten the spacing
       so the two cards and the start button still fit without scrolling. */
    @media (orientation: landscape) and (max-height: 500px) {
      #welcome-panel {
        width: min(94vw, 560px);
      }
      .welcome-title { font-size: 16px; }
      .welcome-sub { margin: 5px 0 10px; }
      .mode-demo { width: 56px; height: 56px; }
      .demo-cube { width: 38px; }
      .demo-row i { height: 11px; }
      .demo-finger { font-size: 15px; }
      .demo-finger.drag { left: 6px; top: 27px; }
      .demo-finger.tap { left: 26px; top: 25px; }
      .mode-card { padding: 10px 36px 10px 10px; margin-bottom: 8px; }
      .welcome-start { min-height: 46px; padding: 11px 18px; }
    }

    /* --- Landscape (phone rotated sideways) ---
       Stacking header/cube/controls vertically -- the portrait layout --
       doesn't fit a short landscape screen: the controls alone are taller
       than the whole screen, pushing the rotation buttons off the bottom
       edge where they can't be reached at all. Landscape has width to
       spare instead, so here the cube and the controls sit side by side:
       the header stays a slim bar across the top, the cube takes the wide
       left area, and the controls become a sidebar on the right. If the
       sidebar's contents are still taller than the screen, it scrolls on
       its own rather than ever pushing a button out of reach.
       The gate is height, not just orientation, so a landscape tablet
       (which has plenty of vertical room) keeps the normal layout. */
    @media (orientation: landscape) and (max-height: 500px) {
      body {
        display: grid;
        grid-template-columns: 1fr minmax(240px, 320px);
        grid-template-rows: auto 1fr;
        grid-template-areas:
          "header header"
          "canvas controls";
      }
      #header {
        grid-area: header;
        padding-top: calc(6px + env(safe-area-inset-top));
        padding-bottom: 6px;
        padding-left: calc(12px + env(safe-area-inset-left));
        padding-right: calc(12px + env(safe-area-inset-right));
      }
      #timer {
        font-size: 20px;
      }
      /* 横画面ではヘッダーが薄い帯になり、その右端にぶら下がるこの
         スライダーは操作パネル(右カラム)の上端へかかりやすい。丈だけ
         詰めてかぶる範囲を最小限にする。持ち上げ量の判定はトラックの
         高さから毎回計算しているので、短くしても操作感は変わらない。
         top はここでは指定しない -- ドロワーの実測の下端に合わせる
         --shuffle-top は縦横どちらの向きでも共通の JS
         (positionShuffleSlider) が入れ直すので、そちらに任せる。 */
      .shuffle-slider {
        height: 88px;
        /* Android Chrome ではヘッダーの実測高さが他ブラウザよりわずかに
           大きく出ることがあり、operationパネル(右カラム)の上端の
           OLL/ZBLL/PLL ボタンにシャッフルのつまみがわずかにかぶって
           見えることがあった。--shuffle-top(JS計算値)はそのままに、
           ここで少しだけ上へ逃がして重なりを避ける。 */
        top: calc(var(--shuffle-top, 210px) - 10px);
      }
      #canvas-container {
        grid-area: canvas;
        min-width: 0;
      }
      /* The rotation buttons (face-grid) are the whole reason this sidebar
         exists, so nothing here should just scroll them out of view on a
         short screen. Every size below is a clamp() tied to vh: on a
         tall-enough landscape screen it sits at its normal (portrait) size,
         and on a short one (small/older phones, or a landscape keyboard/nav
         bar eating into the height) it shrinks smoothly so the whole ring
         of buttons still fits with nothing cut off. overflow-y stays only
         as a safety net, not the main plan. */
      #controls {
        grid-area: controls;
        min-height: 0;
        display: flex;
        flex-direction: column;
        justify-content: center;
        gap: clamp(4px, 1.5vh, 6px);
        overflow-y: auto;
        padding-top: clamp(4px, 1.5vh, 8px);
        padding-bottom: calc(clamp(4px, 1.5vh, 12px) + env(safe-area-inset-bottom));
        padding-right: calc(12px + env(safe-area-inset-right));
        border-left: 1px solid rgba(255, 255, 255, 0.06);
      }
      #controls .alg-row {
        margin-bottom: 0;
        padding-top: 0;
        gap: clamp(6px, 2vw, 10px);
      }
      #controls .alg-btn {
        width: clamp(38px, 11vh, 56px);
        height: clamp(38px, 11vh, 56px);
      }
      #controls .alg-btn .alg-name {
        font-size: clamp(10px, 3.2vh, 13px);
      }
      #controls .alg-btn .alg-sub {
        font-size: clamp(7px, 2vh, 8px);
      }
      #controls .mode-indicator {
        margin-bottom: 0;
        font-size: clamp(9px, 2.4vh, 11px);
      }
      #controls .face-grid {
        grid-template-rows:
          clamp(34px, 11vh, 52px)
          clamp(50px, 18vh, 84px)
          clamp(34px, 11vh, 52px);
        gap: clamp(4px, 1.2vh, 8px);
      }
      #controls .face-letter {
        font-size: clamp(14px, 4vh, 19px);
      }
      #controls .edge-hint {
        font-size: clamp(9px, 2.2vh, 12px);
      }
      /* With the rotation buttons hidden, the sidebar only needs to hold the
         OLL/PLL pair -- stack them and let the cube take the rest of the
         width, the same way it takes the freed height in portrait. */
      body.tap-mode {
        grid-template-columns: 1fr auto;
      }
      body.tap-mode .alg-row {
        grid-template-columns: 1fr;
        gap: 8px;
      }
      body.tap-mode .alg-spacer {
        display: none;
      }
      body.tap-mode .tap-hint {
        display: none;
      }
      body.all-alg-hidden:not(.tap-mode) #controls .face-grid {
        grid-template-rows:
          clamp(40px, 13vh, 62px)
          clamp(60px, 20vh, 100px)
          clamp(40px, 13vh, 62px);
        gap: clamp(6px, 1.5vh, 10px);
      }
      body.all-alg-hidden:not(.tap-mode) #controls .face-letter {
        font-size: clamp(16px, 4.5vh, 23px);
      }
      body.all-alg-hidden:not(.tap-mode) #controls .edge-hint {
        font-size: clamp(10px, 2.5vh, 14px);
      }
    }

    /* --- Tablets (iPad, etc.) ---
       Every size in .face-grid / .face-card / .alg-btn above is a fixed px
       value tuned for a phone-width screen. On an iPad the viewport is far
       wider (and taller) than a phone, but those px values never scale up,
       so the rotation buttons end up looking tiny relative to how much
       screen they actually have to work with. Gated on both min-width and
       min-height (rather than just width) so this only matches an actual
       tablet-sized viewport -- a phone in landscape has a wide viewport too,
       but a short one, and already gets its own layout from the "Landscape
       (phone rotated sideways)" rule above; this rule intentionally leaves
       that case alone. */
    @media (min-width: 700px) and (min-height: 700px) {
      #controls {
        padding: 14px 22px 18px;
        padding-bottom: calc(18px + env(safe-area-inset-bottom));
        gap: 10px;
      }
      .face-grid {
        grid-template-rows: 78px 128px 78px;
        gap: 14px;
        max-width: 640px;
        width: 100%;
        margin: 0 auto;
      }
      .face-letter {
        font-size: 30px;
      }
      .edge-hint {
        font-size: 16px;
      }
      .alg-row {
        max-width: 640px;
        margin: 0 auto 16px;
        gap: 16px;
      }
      .alg-btn {
        width: 76px;
        height: 76px;
      }
      .alg-btn .alg-name {
        font-size: 17px;
      }
      .alg-btn .alg-sub {
        font-size: 10px;
      }
      .controls-label {
        font-size: 14px;
      }
      .mode-indicator {
        font-size: 13px;
      }
      body.all-alg-hidden:not(.tap-mode) .face-grid {
        grid-template-rows: 94px 154px 94px;
        gap: 18px;
        max-width: 720px;
      }
      body.all-alg-hidden:not(.tap-mode) .face-letter {
        font-size: 36px;
      }
      body.all-alg-hidden:not(.tap-mode) .edge-hint {
        font-size: 19px;
      }
    }

    /* ============================================================
       横向き誘導オーバーレイ (Orientation Guard)
       - 「禁止」ではなく「縦の方が快適だよ」というポジティブな導線。
       - JS 側で isMobileLike() && landscape のときだけ .show を付与する。
    ============================================================ */
    #orientation-overlay {
      display: none;
      position: fixed;
      inset: 0;
      z-index: 999999;
      align-items: center;
      justify-content: center;
      /* 背面のテキストが透けて重なって見える不具合の修正:
         半透明(rgba)だった背景を完全不透明な単色に変更。 */
      background: #08080c;
      -webkit-backdrop-filter: blur(16px) saturate(140%);
      backdrop-filter: blur(16px) saturate(140%);
      padding: 24px calc(24px + env(safe-area-inset-right)) 24px calc(24px + env(safe-area-inset-left));
      text-align: center;
      opacity: 0;
      pointer-events: none;
      transition: opacity 0.35s ease;
    }
    #orientation-overlay.show {
      display: flex;
      opacity: 1;
      pointer-events: auto; /* 背後のゲームUIへのタップを完全ブロック */
    }
    #orientation-panel {
      width: min(92vw, 380px);
      margin: auto;
      animation: orientationPanelIn 0.4s ease;
    }
    @keyframes orientationPanelIn {
      from { opacity: 0; transform: translateY(10px) scale(0.97); }
      to   { opacity: 1; transform: translateY(0) scale(1); }
    }
    .device-rotate-anim {
      position: relative;
      width: 140px;
      height: 140px;
      margin: 0 auto 22px;
    }
    .device-rotate-anim .phone {
      position: absolute;
      top: 50%;
      left: 50%;
      width: 56px;
      height: 96px;
      margin: -48px 0 0 -28px;
      background: linear-gradient(160deg, #2c2c36, #1a1a20);
      border: 3px solid #5c5c6e;
      border-radius: 15px;
      box-shadow:
        0 0 26px rgba(var(--tc-rgb), 0.28),
        inset 0 0 0 2px rgba(255, 255, 255, 0.04);
      transform-origin: center;
      animation: phoneFlip 2.8s cubic-bezier(.65, 0, .35, 1) infinite;
    }
    .device-rotate-anim .phone::before {
      content: '';
      position: absolute;
      inset: 8px 6px;
      border-radius: 8px;
      background: linear-gradient(135deg, rgba(var(--tc-rgb), 0.22), rgba(77, 166, 255, 0.14));
    }
    .device-rotate-anim .phone::after {
      content: '';
      position: absolute;
      bottom: 6px;
      left: 50%;
      width: 18px;
      height: 3px;
      margin-left: -9px;
      background: rgba(255, 255, 255, 0.35);
      border-radius: 2px;
    }
    .device-rotate-anim .rotate-arrow {
      position: absolute;
      top: -2px;
      right: -6px;
      width: 40px;
      height: 40px;
      color: var(--tc);
      filter: drop-shadow(0 0 6px rgba(var(--tc-rgb), 0.55));
      transform-origin: 55% 55%;
      animation: arrowSwing 2.8s cubic-bezier(.65, 0, .35, 1) infinite;
    }
    @keyframes phoneFlip {
      0%   { transform: rotate(-90deg); }
      38%  { transform: rotate(0deg); }
      75%  { transform: rotate(0deg); }
      100% { transform: rotate(-90deg); }
    }
    @keyframes arrowSwing {
      0%   { transform: rotate(0deg); opacity: 0.85; }
      38%  { transform: rotate(100deg); opacity: 1; }
      75%  { transform: rotate(100deg); opacity: 1; }
      100% { transform: rotate(190deg); opacity: 0.85; }
    }
    .orientation-title {
      font-family: 'Orbitron', 'Segoe UI', sans-serif;
      font-size: 21px;
      font-weight: 900;
      letter-spacing: 0.5px;
      line-height: 1.4;
      background: linear-gradient(90deg, var(--tc), #4da6ff 55%, #ff3d81 100%);
      -webkit-background-clip: text;
      background-clip: text;
      -webkit-text-fill-color: transparent;
      text-shadow: 0 0 10px rgba(var(--tc-rgb), 0.35), 0 0 22px rgba(77, 166, 255, 0.2);
      margin-bottom: 10px;
    }
    .orientation-sub {
      font-size: 13.5px;
      font-weight: var(--ui-weight-light);
      font-family: var(--ui-font-soft);
      color: #b8b8c8;
      line-height: 1.7;
      letter-spacing: 0.02em;
    }
    .orientation-sub b {
      color: var(--tc);
      font-weight: 700;
    }
    /* prefers-reduced-motion のユーザーには回転アニメを止め、
       静止イラスト + テキストだけで意図を伝える。 */
    @media (prefers-reduced-motion: reduce) {
      .device-rotate-anim .phone,
      .device-rotate-anim .rotate-arrow {
        animation: none;
        transform: rotate(-90deg);
      }
    }

/* =========================================================================
   === CRYSTAL REWARD FX : CSS ここから ===================================
   ========================================================================= */
:root {
  --cr-core:   #ffffff;   /* コア（いちばん明るい面） */
  --cr-ice:    #d9fbff;   /* 氷白 */
  --cr-cyan:   #5fe3ff;   /* クリスタル主色 */
  --cr-azure:  #22a7ff;   /* 影側 */
  --cr-deep:   #0a4d8c;   /* 最暗部 */
  --cr-violet: #a98bff;   /* 分光のアクセント（面のきらめき） */
}

/* ---- 所持通貨ヘッダーUI ----------------------------------------------
   シャッフルが縦スライダーとしてドロワーの下へ移ったので、ヘッダーの
   「タイマーとメニューの間」がまるごと空いた。所持クリスタルはそこへ
   収める。以前はキューブに重ねる HUD だったため、右上が「背景をなぞって
   全体回転」の効かない死角になりがちだったが、ヘッダーの中に入れたことで
   その問題ごと無くなる。
   指のイベントは通さないまま(pointer-events:none)にしてある。触って
   反応するものではないので、押せそうに見せない方が誤解が少ない。 */
#crystal-wallet {
  /* 中の衝撃波リング(.crystal-wallet-ring)が left/top 50% で位置決め
     しているので、静的配置ではなく relative の含みブロックにしておく。 */
  position: relative;
  top: auto;
  right: auto;
  z-index: auto;
  flex: 0 0 auto;
  pointer-events: none;
}
.crystal-wallet {
  position: absolute;
  display: inline-flex;
  align-items: center;
  gap: 7px;
  height: 34px;
  padding: 0 12px 0 9px;
  border-radius: 999px;
  background: linear-gradient(180deg, rgba(40,64,86,.55), rgba(16,22,32,.75));
  border: 1px solid rgba(95,227,255,.30);
  box-shadow: inset 0 1px 0 rgba(255,255,255,.12), 0 2px 10px rgba(0,0,0,.45);
  transform: translateZ(0);
  will-change: transform;
}
.crystal-wallet-count {
  font-family: "Orbitron", monospace;
  font-size: 16px; font-weight: 900;
  line-height: 1;
  color: var(--cr-ice);
  font-variant-numeric: tabular-nums;
  letter-spacing: .04em;
  text-shadow: 0 0 8px rgba(95,227,255,.55);
  transition: color .18s ease, text-shadow .18s ease;
}
/* カウント中だけ数字を白熱させる（パラパラ感の強調） */
.crystal-wallet-count.is-rolling {
  color: #ffffff;
  text-shadow: 0 0 10px rgba(255,255,255,.95), 0 0 22px rgba(95,227,255,.9);
}
/* 1個到達ごとの小さな手応え */
.crystal-wallet.is-tick { animation: crystal-wallet-tick .2s ease-out; }
@keyframes crystal-wallet-tick {
  0%   { transform: scale(1); }
  40%  { transform: scale(1.07); }
  100% { transform: scale(1); }
}
/* 最後の1個が到達した瞬間のドン！（拡大＋発光） */
.crystal-wallet.is-hit { animation: crystal-wallet-hit .52s cubic-bezier(.2,1.5,.4,1); }
@keyframes crystal-wallet-hit {
  0%   { transform: scale(1);    border-color: rgba(95,227,255,.30);
         box-shadow: inset 0 1px 0 rgba(255,255,255,.12), 0 2px 10px rgba(0,0,0,.45); }
  30%  { transform: scale(1.26); border-color: rgba(255,255,255,.95);
         box-shadow: inset 0 0 14px rgba(255,255,255,.7), 0 0 30px rgba(95,227,255,.85), 0 0 60px rgba(34,167,255,.5); }
  60%  { transform: scale(.97); }
  100% { transform: scale(1);    border-color: rgba(95,227,255,.30);
         box-shadow: inset 0 1px 0 rgba(255,255,255,.12), 0 2px 10px rgba(0,0,0,.45); }
}
/* 着弾の衝撃波リング（JSが必要なときだけ生成して自動で消す） */
.crystal-wallet-ring {
  position: absolute;
  left: 50%; top: 50%;
  width: 46px; height: 46px;
  margin: -23px 0 0 -23px;
  border-radius: 999px;
  border: 2px solid rgba(150,240,255,.9);
  pointer-events: none;
  animation: crystal-ring-out .5s ease-out forwards;
}
.crystal-wallet-ring.is-final {
  border-width: 3px;
  animation-duration: .72s;
}
@keyframes crystal-ring-out {
  0%   { transform: scale(.45); opacity: .95; }
  100% { transform: scale(2.6);  opacity: 0; }
}

/* ---- 飛翔レイヤー ------------------------------------------------------ */
#crystal-fx-layer {
  position: fixed;
  inset: 0;
  overflow: hidden;
  pointer-events: none;
  z-index: 9999;          /* 完成時の白フラッシュ(9998)より前面 */
  contain: layout style paint;
}

/* ---- 飛んでいくクリスタル1個 ------------------------------------------
   .crystal-shard          … 位置だけをJSが動かす殻（transformのみ更新）
     .crystal-shard-trail  … 進行方向の逆に伸びる残光
     .crystal-shard-glow   … 周囲のにじみ（filterを使わず軽量に）
     .crystal-shard-tumble … CSSアニメで回り続ける入れ物
       .crystal-gem        … ★見た目の本体。自作3Dクリスタルに差し替える箇所
   -------------------------------------------------------------------- */
.crystal-shard {
  position: absolute;
  left: 0; top: 0;
  width: 26px; height: 30px;
  margin: -15px 0 0 -13px;   /* 中心を基準点にする */
  will-change: transform;
  backface-visibility: hidden;
}
.crystal-shard-trail {
  position: absolute;
  left: 50%; top: 50%;
  width: 46px; height: 6px;
  margin-top: -3px;
  border-radius: 3px;
  transform-origin: 0 50%;
  background: linear-gradient(90deg, rgba(140,238,255,.85), rgba(95,227,255,0));
  opacity: 0;
  will-change: transform, opacity;
}
.crystal-shard-glow {
  position: absolute;
  left: 50%; top: 50%;
  width: 54px; height: 54px;
  margin: -27px 0 0 -27px;
  border-radius: 50%;
  background: radial-gradient(circle, rgba(190,250,255,.75) 0%, rgba(95,227,255,.35) 38%, rgba(34,167,255,0) 70%);
}
.crystal-shard-tumble {
  position: absolute;
  inset: 0;
  display: grid;
  place-items: center;
  animation: crystal-tumble .8s linear infinite;
  transform-style: preserve-3d;
}
@keyframes crystal-tumble {
  0%   { transform: rotate3d(.4, 1, .2, 0deg); }
  100% { transform: rotate3d(.4, 1, .2, 360deg); }
}

/* ★ここが差し替えポイント：カット済みクリスタル（SVG）のアイコン */
.crystal-gem {
  position: relative;
  width: 22px; height: 22px;
  display: inline-flex;
  align-items: center;
  justify-content: center;
}
.crystal-gem svg {
  width: 100%;
  height: 100%;
  display: block;
  filter: drop-shadow(0 0 5px rgba(95,227,255,.55));
}
/* 絵文字モード（CrystalReward.setIconFactory 未使用でも切替できる簡易版） */
.crystal-gem-emoji {
  font-size: 24px;
  line-height: 1;
  text-shadow: 0 0 10px rgba(95,227,255,.9);
}

/* ---- クリア時のリザルトポップアップ ------------------------------------ */
#crystal-popup {
  position: fixed;
  left: 50%; top: 44%;      /* JS が #msg の下へ合わせ直す */
  z-index: 10001;
  display: flex;
  align-items: center;
  gap: 10px;
  padding: 4px 8px;
  white-space: nowrap;
  pointer-events: none;
  opacity: 0;
  visibility: hidden;
  transform: translate(-50%, -50%) scale(.3);
}
#crystal-popup .crystal-popup-flare {   /* 背面の光芒 */
  position: absolute;
  left: 50%; top: 50%;
  width: 300px; height: 300px;
  margin: -150px 0 0 -150px;
  border-radius: 50%;
  background: radial-gradient(circle, rgba(255,255,255,.55) 0%, rgba(95,227,255,.32) 26%, rgba(34,167,255,0) 62%);
  opacity: 0;
}
#crystal-popup .crystal-popup-text {
  position: relative;
  font-family: "Orbitron", "Hiragino Kaku Gothic ProN", sans-serif;
  font-size: clamp(26px, 8.4vw, 46px);
  font-weight: 900;
  letter-spacing: .02em;
  background: linear-gradient(180deg, #ffffff 0%, #d9fbff 38%, #5fe3ff 66%, #22a7ff 100%);
  -webkit-background-clip: text;
  background-clip: text;
  color: transparent;
  filter: drop-shadow(0 0 10px rgba(95,227,255,.9)) drop-shadow(0 0 30px rgba(34,167,255,.6));
}
#crystal-popup .crystal-popup-gem {
  -webkit-text-fill-color: initial;
  color: initial;
  display: inline-flex;
  vertical-align: -0.12em;
}
#crystal-popup .crystal-popup-gem svg {
  width: 1em;
  height: 1em;
  display: block;
  filter: drop-shadow(0 0 12px rgba(150,240,255,.9));
}
#crystal-popup.is-show {
  visibility: visible;
  animation: crystal-popup-in .78s cubic-bezier(.16,1.3,.3,1) forwards;
}
#crystal-popup.is-show .crystal-popup-flare { animation: crystal-flare .8s ease-out forwards; }
#crystal-popup.is-hide {
  visibility: visible;
  animation: crystal-popup-out .42s ease-in forwards;
}
/* バウンド：飛び出し → 行き過ぎ → 弾んで着地 */
@keyframes crystal-popup-in {
  0%   { opacity: 0; transform: translate(-50%, -50%) scale(.25) rotate(-9deg); }
  38%  { opacity: 1; transform: translate(-50%, -50%) scale(1.26) rotate(3deg); }
  56%  { transform: translate(-50%, -50%) scale(.9)  rotate(-2deg); }
  72%  { transform: translate(-50%, -50%) scale(1.1) rotate(1deg); }
  86%  { transform: translate(-50%, -50%) scale(.97); }
  100% { opacity: 1; transform: translate(-50%, -50%) scale(1) rotate(0deg); }
}
@keyframes crystal-popup-out {
  0%   { opacity: 1; transform: translate(-50%, -50%) scale(1); }
  100% { opacity: 0; transform: translate(-50%, -110%) scale(.86); visibility: hidden; }
}
@keyframes crystal-flare {
  0%   { opacity: 0;  transform: scale(.3); }
  25%  { opacity: 1;  transform: scale(1); }
  100% { opacity: 0;  transform: scale(1.5); }
}

/* ---- モーション軽減の設定を尊重 --------------------------------------- */
@media (prefers-reduced-motion: reduce) {
  #crystal-popup.is-show { animation: crystal-popup-in-soft .3s ease-out forwards; }
  #crystal-popup.is-show .crystal-popup-flare { animation: none; opacity: .5; }
  .crystal-wallet.is-hit, .crystal-wallet.is-tick { animation: none; }
  .crystal-shard-tumble { animation: none; }
  @keyframes crystal-popup-in-soft {
    from { opacity: 0; transform: translate(-50%, -50%) scale(.95); }
    to   { opacity: 1; transform: translate(-50%, -50%) scale(1); }
  }
}
/* === CRYSTAL REWARD FX : CSS ここまで ================================= */

/* =========================================================================
   === SHARD REWARD FX : CSS ここから =======================================
   ヘッダーの2段ウォレット（上=虹 / 下=水色）と、大当たり演出の見た目。
   依存ライブラリなし。既存の CSS のいちばん後ろに貼るだけで効きます
   （同じ詳細度なら後ろが勝つので、旧 .crystal-wallet の指定を上書きします）。
   ========================================================================= */

/* 円錐グラデーションの角度をアニメーションさせるためのカスタムプロパティ。
   未対応ブラウザでは回転しないだけで、縁取り自体は表示されます。 */
@property --shard-spin { syntax: '<angle>'; inherits: false; initial-value: 0deg; }

:root{
  --jp-gold:  #ffd76a;
  --jp-hot:   #fff3c4;
}

/* -------------------------------------------------------------------------
   1. ヘッダーの所持数UI（縦2段）
      上段 = 虹色シャード / 下段 = 水色シャード
   ------------------------------------------------------------------------- */
.wallet-stack{
  display:flex; flex-direction:column; align-items:flex-end;
  gap:5px; flex:0 0 auto;
  pointer-events:none;              /* 触るものではないので指は素通し */
}
.wallet-pill{
  position:relative;
  display:inline-flex; align-items:center; justify-content:flex-end; gap:7px;
  min-width:44px; width:max-content; height:27px;
  padding:0 12px 0 9px;
  border-radius:999px;
  background:#0c0d10;
  isolation:isolate;                /* ::before/::after を内側に閉じ込める */
  transform:translateZ(0);
  will-change:transform;
  transition:min-width .18s ease; /* 桁数変化でも滑らかに幅が追従 */
}
.wallet-pill .pill-icon{ width:15px; height:19px; display:block; flex:0 0 auto; }
.wallet-pill .pill-num{
  font-family:"Orbitron", ui-monospace, "SF Mono", monospace;
  font-size:15px; font-weight:900; line-height:1;
  letter-spacing:.04em; font-variant-numeric:tabular-nums;
}

/* ---- 下段：水色（静かな単色グロー）---- */
.wallet-pill.cyan::after{
  content:""; position:absolute; inset:0; border-radius:999px;
  border:1.5px solid #2e5f6e;
  box-shadow:0 0 16px rgba(95,216,245,.22), inset 0 0 10px rgba(95,216,245,.05);
  z-index:-1;
}
.wallet-pill.cyan .pill-num{
  color:#5eeac2;
  text-shadow:0 0 8px rgba(95,227,255,.45);
  transition:color .18s ease, text-shadow .18s ease;
}
.wallet-pill.cyan .pill-num.is-rolling{
  color:#ffffff;
  text-shadow:0 0 10px rgba(255,255,255,.95), 0 0 22px rgba(95,227,255,.9);
}

/* ---- 上段：虹色（ゆっくり回る縁取り＋流れる文字色）---- */
.wallet-pill.rainbow::before{
  content:""; position:absolute; inset:0; border-radius:999px; padding:1.5px;
  background:conic-gradient(from var(--shard-spin),
    #6fb8c9,#8ab89f,#b8ac6f,#c48a8a,#9a8ac4,#6f9ac4,#6fb8c9);
  -webkit-mask:linear-gradient(#000 0 0) content-box, linear-gradient(#000 0 0);
          mask:linear-gradient(#000 0 0) content-box, linear-gradient(#000 0 0);
  -webkit-mask-composite:xor; mask-composite:exclude;
  animation:shard-ring-spin 10s linear infinite;
  z-index:-2;
}
.wallet-pill.rainbow::after{
  content:""; position:absolute; inset:0; border-radius:999px;
  box-shadow:0 0 16px rgba(150,170,200,.16);
  z-index:-1;
  transition:box-shadow .25s ease;
}
@keyframes shard-ring-spin{ to{ --shard-spin:360deg; } }

.wallet-pill.rainbow .pill-num{
  background:linear-gradient(90deg,#8fd8ff,#8fffc0,#ffe58f,#ff9fd0,#c58fff,#8fd8ff);
  background-size:300% 100%;
  -webkit-background-clip:text; background-clip:text; color:transparent;
  animation:shard-hue-shift 6s linear infinite;
}
@keyframes shard-hue-shift{ to{ background-position:300% 0; } }

/* 大当たり中だけ、上段が“覚醒”する（縁取り高速回転＋強発光） */
.wallet-pill.rainbow.is-awake::before{ animation-duration:1.1s; }
.wallet-pill.rainbow.is-awake::after{
  box-shadow:0 0 26px rgba(255,220,140,.75), 0 0 60px rgba(255,170,220,.45);
}
.wallet-pill.rainbow.is-awake .pill-num{ animation-duration:.9s; font-size:16px; }

/* カウントアップ中の激しい振動（Screen Shake の局所版） */
.wallet-pill.is-quake{ animation:shard-quake .09s linear infinite; }
@keyframes shard-quake{
  0%{transform:translate3d(-2px,1px,0) rotate(-.6deg)}
  25%{transform:translate3d(2px,-2px,0) rotate(.5deg)}
  50%{transform:translate3d(-1px,-1px,0) rotate(.7deg)}
  75%{transform:translate3d(2px,2px,0) rotate(-.4deg)}
  100%{transform:translate3d(-2px,1px,0) rotate(-.6deg)}
}
/* 1個到達ごとの小さな手応え／最後のドン！ */
.wallet-pill.is-tick2{ animation:shard-tick .2s ease-out; }
@keyframes shard-tick{ 0%,100%{transform:scale(1)} 40%{transform:scale(1.07)} }
.wallet-pill.is-hit2{ animation:shard-hit .6s cubic-bezier(.2,1.5,.4,1); }
@keyframes shard-hit{
  0%{transform:scale(1)} 30%{transform:scale(1.34)} 60%{transform:scale(.96)} 100%{transform:scale(1)}
}
/* 着弾の衝撃波リング（JSが必要なときだけ生成して自動で消す） */
.wallet-pill .pill-ring{
  position:absolute; left:50%; top:50%;
  width:46px; height:46px; margin:-23px 0 0 -23px;
  border-radius:999px; border:2px solid rgba(255,235,190,.9);
  pointer-events:none;
  animation:shard-ring-out .5s ease-out forwards;
}
.wallet-pill .pill-ring.is-final{ border-width:3px; animation-duration:.78s; }
@keyframes shard-ring-out{
  0%{ transform:scale(.45); opacity:.95 } 100%{ transform:scale(3); opacity:0 }
}

/* -------------------------------------------------------------------------
   2. 大当たり演出：暗転／フラッシュ／カットイン
      #jackpot-fx は JS が必要になった瞬間に body 直下へ作ります。
   ------------------------------------------------------------------------- */
#jackpot-fx{
  position:fixed; inset:0; z-index:10050;
  pointer-events:none; overflow:hidden;
  display:none;
  contain:layout style paint;
}
#jackpot-fx.is-on{ display:block; }

/* --- 暗転（タメ）--- */
.jp-dim{
  position:absolute; inset:0;
  background:radial-gradient(circle at 50% 46%, rgba(0,0,0,.62) 0%, rgba(0,0,0,.92) 70%);
  opacity:0;
  transition:opacity .22s ease-out;
}
#jackpot-fx.is-dim .jp-dim{ opacity:1; }
#jackpot-fx.is-out .jp-dim{ opacity:0; transition:opacity .5s ease-in; }

/* --- タメ中に中心へ吸い寄せられる集中線リング --- */
.jp-charge{
  position:absolute; left:50%; top:46%;
  width:520px; height:520px; margin:-260px 0 0 -260px;
  border-radius:50%;
  border:2px solid rgba(255,225,150,.0);
  opacity:0;
}
#jackpot-fx.is-dim .jp-charge{ animation:jp-charge .3s cubic-bezier(.5,0,.9,.6) 2; }
@keyframes jp-charge{
  0%{ transform:scale(1); opacity:0; border-color:rgba(255,225,150,0) }
  35%{ opacity:.9; border-color:rgba(255,225,150,.85) }
  100%{ transform:scale(.06); opacity:0; border-color:rgba(255,255,255,1) }
}

/* --- 画面全体の白フラッシュ --- */
.jp-flash{
  position:absolute; inset:0; background:#fff; opacity:0;
}
#jackpot-fx.is-flash .jp-flash{ animation:jp-flash .5s ease-out forwards; }
@keyframes jp-flash{
  0%{ opacity:0 } 6%{ opacity:1 } 30%{ opacity:.55 } 100%{ opacity:0 }
}

/* --- 背後で回る虹色の後光 --- */
.jp-rays{
  position:absolute; left:50%; top:46%;
  width:180vmax; height:180vmax; margin:-90vmax 0 0 -90vmax;
  opacity:0;
  background:conic-gradient(from 0deg,
    rgba(255,255,255,.20) 0 6deg, rgba(255,255,255,0) 6deg 18deg);
  mask-image:radial-gradient(circle, #000 8%, rgba(0,0,0,.35) 42%, transparent 70%);
  -webkit-mask-image:radial-gradient(circle, #000 8%, rgba(0,0,0,.35) 42%, transparent 70%);
}
#jackpot-fx.is-cutin .jp-rays{ opacity:1; animation:jp-rays 9s linear infinite; }
#jackpot-fx.is-out .jp-rays{ opacity:0; transition:opacity .5s ease; }
@keyframes jp-rays{ to{ transform:rotate(360deg) } }

/* --- カットイン：上下から挟み込む黒帯＋斬り込む文字 --- */
.jp-band{
  position:absolute; left:-10%; width:120%; height:132px;
  background:linear-gradient(90deg, rgba(0,0,0,0), rgba(0,0,0,.92) 18%, rgba(0,0,0,.92) 82%, rgba(0,0,0,0));
  opacity:0;
}
.jp-band.top{ top:calc(46% - 132px); transform:translateX(-110%) skewY(-4deg); }
.jp-band.bot{ top:46%;              transform:translateX(110%)  skewY(-4deg); }
#jackpot-fx.is-cutin .jp-band.top{ animation:jp-band-in-l .5s cubic-bezier(.1,.9,.2,1) forwards; }
#jackpot-fx.is-cutin .jp-band.bot{ animation:jp-band-in-r .5s cubic-bezier(.1,.9,.2,1) forwards; }
@keyframes jp-band-in-l{ to{ transform:translateX(0) skewY(-4deg); opacity:1 } }
@keyframes jp-band-in-r{ to{ transform:translateX(0) skewY(-4deg); opacity:1 } }
#jackpot-fx.is-cutout .jp-band.top{ animation:jp-band-out-l .4s ease-in forwards; }
#jackpot-fx.is-cutout .jp-band.bot{ animation:jp-band-out-r .4s ease-in forwards; }
@keyframes jp-band-out-l{
  from{ transform:translateX(0) skewY(-4deg); opacity:1 }
  to  { transform:translateX(-115%) skewY(-4deg); opacity:0 }
}
@keyframes jp-band-out-r{
  from{ transform:translateX(0) skewY(-4deg); opacity:1 }
  to  { transform:translateX(115%) skewY(-4deg); opacity:0 }
}

.jp-cutin{
  position:absolute; left:0; right:0; top:46%;
  transform:translateY(-50%);
  text-align:center;
  opacity:0;
}
#jackpot-fx.is-cutin .jp-cutin{ animation:jp-slam .62s cubic-bezier(.12,1.5,.3,1) forwards; }
#jackpot-fx.is-cutout .jp-cutin{ animation:jp-cutin-out .42s ease-in forwards; }
@keyframes jp-slam{
  0%   { opacity:0; transform:translateY(-50%) scale(3.2) rotate(-14deg); filter:blur(14px) }
  55%  { opacity:1; transform:translateY(-50%) scale(.92) rotate(2deg);   filter:blur(0) }
  75%  { transform:translateY(-50%) scale(1.06) rotate(-1deg) }
  100% { opacity:1; transform:translateY(-50%) scale(1) rotate(0) }
}
@keyframes jp-cutin-out{
  0%{ opacity:1; transform:translateY(-50%) scale(1) }
  100%{ opacity:0; transform:translateY(-50%) scale(1.5) translateX(28px); filter:blur(10px) }
}

.jp-title{
  position:relative;
  font-family:"Orbitron", ui-monospace, "SF Mono", monospace;
  font-weight:900; font-style:italic;
  font-size:clamp(38px, 13vw, 96px);
  line-height:1; letter-spacing:.02em;
  background:linear-gradient(178deg, #fffdf2 6%, #ffe27a 34%, #ffb42e 52%, #fff6c9 66%, #ffcf4d 84%);
  -webkit-background-clip:text; background-clip:text; color:transparent;
  filter:drop-shadow(0 2px 0 #7a4a00) drop-shadow(0 0 18px rgba(255,196,60,.95))
         drop-shadow(0 0 46px rgba(255,120,200,.5));
  -webkit-text-stroke:1.5px rgba(90,50,0,.55);
}
/* 文字の上を走る白いハイライト（斬られた光） */
.jp-title::after{
  content:attr(data-text);
  position:absolute; left:0; right:0; top:0;
  background:linear-gradient(105deg, transparent 38%, rgba(255,255,255,.95) 50%, transparent 62%);
  background-size:260% 100%;
  -webkit-background-clip:text; background-clip:text; color:transparent;
  -webkit-text-stroke:0;
  animation:jp-shine 1.4s ease-in-out .35s 2;
}
@keyframes jp-shine{ 0%{background-position:180% 0} 100%{background-position:-80% 0} }

.jp-sub{
  margin-top:10px;
  font-family:"Orbitron", ui-monospace, monospace;
  font-size:clamp(13px, 4.2vw, 22px); font-weight:900; letter-spacing:.18em;
  background:linear-gradient(90deg,#8fd8ff,#8fffc0,#ffe58f,#ff9fd0,#c58fff,#8fd8ff);
  background-size:300% 100%;
  -webkit-background-clip:text; background-clip:text; color:transparent;
  animation:shard-hue-shift 2.4s linear infinite;
  text-shadow:0 0 24px rgba(255,255,255,.25);
}

/* --- 中央から広がる衝撃波（フラッシュと同時）--- */
.jp-shock{
  position:absolute; left:50%; top:46%;
  width:120px; height:120px; margin:-60px 0 0 -60px;
  border-radius:50%; border:3px solid rgba(255,240,200,.9);
  opacity:0;
}
#jackpot-fx.is-flash .jp-shock{ animation:jp-shock .75s cubic-bezier(.1,.8,.2,1) forwards; }
@keyframes jp-shock{
  0%{ transform:scale(.2); opacity:1; border-width:14px }
  100%{ transform:scale(11); opacity:0; border-width:1px }
}

/* --- 画面全体を叩く揺れ（カットインの瞬間だけ）---
   完成時の揺れと同じ理由で、縦の動きと回転はやめて左右だけにしてある
   （画面が縦にずれると端末のスクロールと噛み合ってガタつくため）。 */
.jp-screen-shake{ animation:jp-screen-shake .42s cubic-bezier(.36,.07,.19,.97); }
@keyframes jp-screen-shake{
  10%{transform:translate3d(-9px,0,0)}
  30%{transform:translate3d(8px,0,0)}
  50%{transform:translate3d(-6px,0,0)}
  70%{transform:translate3d(5px,0,0)}
  90%{transform:translate3d(-2px,0,0)}
  100%{transform:none}
}

/* -------------------------------------------------------------------------
   3. 飛翔レイヤーと虹シャード1個
   ------------------------------------------------------------------------- */
#jackpot-shard-layer{
  position:fixed; inset:0; overflow:hidden; pointer-events:none;
  z-index:10060;                     /* カットイン(10050)より前 */
  contain:layout style paint;
}
.jp-shard{
  position:absolute; left:0; top:0;
  width:26px; height:32px; margin:-16px 0 0 -13px;   /* 中心を基準点に */
  will-change:transform; backface-visibility:hidden;
}
.jp-shard-trail{
  position:absolute; left:50%; top:50%;
  width:54px; height:6px; margin-top:-3px; border-radius:3px;
  transform-origin:0 50%;
  background:linear-gradient(90deg, rgba(255,240,200,.9), rgba(255,180,240,0));
  opacity:0; will-change:transform,opacity;
}
.jp-shard-glow{
  position:absolute; left:50%; top:50%;
  width:58px; height:58px; margin:-29px 0 0 -29px; border-radius:50%;
  background:radial-gradient(circle, rgba(255,250,230,.7) 0%, rgba(255,200,120,.32) 40%, rgba(255,140,220,0) 72%);
}
.jp-shard-tumble{
  position:absolute; inset:0; display:grid; place-items:center;
  animation:jp-tumble .7s linear infinite; transform-style:preserve-3d;
}
@keyframes jp-tumble{
  0%{ transform:rotate3d(.35,1,.25,0deg) } 100%{ transform:rotate3d(.35,1,.25,360deg) }
}
.jp-shard svg{ width:21px; height:27px; display:block;
  filter:drop-shadow(0 0 6px rgba(255,215,160,.8)); }

/* 水色シャード（通常演出）を、ヘッダーと同じ結晶の形に揃えるための追加。
   CrystalReward.setIconFactory() で差し込みます。 */
.crystal-gem.shard-gem{ width:20px; height:26px; }

/* -------------------------------------------------------------------------
   3.5 背の低い画面・横向きレイアウトでは2段ぶんの高さが効いてくるので詰める
   ------------------------------------------------------------------------- */
@media (orientation: landscape) and (max-height: 500px), (max-height: 620px){
  .wallet-stack{ gap:4px; }
  .wallet-pill{ height:23px; min-width:38px; padding:0 10px 0 8px; }
  .wallet-pill .pill-icon{ width:13px; height:16px; }
  .wallet-pill .pill-num{ font-size:13px; }
}

/* -------------------------------------------------------------------------
   4. モーション軽減設定への配慮
   ------------------------------------------------------------------------- */
@media (prefers-reduced-motion: reduce){
  .wallet-pill.rainbow::before,
  .wallet-pill.rainbow .pill-num,
  .jp-shard-tumble,
  #jackpot-fx.is-cutin .jp-rays{ animation:none !important; }
  .wallet-pill.is-quake{ animation:none !important; }
  #jackpot-fx.is-flash .jp-flash{ animation:jp-flash-soft .4s ease-out forwards; }
  @keyframes jp-flash-soft{ 0%{opacity:.35} 100%{opacity:0} }
}
/* === SHARD REWARD FX : CSS ここまで ====================================== */

/* =========================================================================
   タップキューブ チュートリアル (Tutorial / Coach marks)
   -------------------------------------------------------------------------
   構成は4つのレイヤーだけ:
     #tut-scrim  … 画面を暗くする幕。要素にスポットライトを当てるステップ
                    でのみ出す。キューブを触らせるステップでは出さない
                    （キューブが暗くなると何を回しているか分からなくなる）。
     #tut-hole   … box-shadow で 9999px の影を外側に落とすことで「穴」を
                    作る。実測した矩形へ毎フレーム追従させる。
     #tut-ghost  … 👆 のゴースト。3D 側から実測したスクリーン座標を
                    --gx/--gy → --gx2/--gy2 の間で往復させる。
     #tut-card   … 画面下部の説明カード。
   どのレイヤーも pointer-events:none。幕でタップを塞ぐと
   「ドロワーの外側タップで閉じる」「背景なぞりで全体回転」といった
   既存のジェスチャーを軒並み壊してしまうため、視覚的な誘導だけに徹する。
   ========================================================================= */
#tut-root {
  position: fixed;
  inset: 0;
  z-index: 4000;
  pointer-events: none;
  display: none;
}
#tut-root.show { display: block; }

#tut-scrim {
  position: absolute;
  inset: 0;
  background: rgba(8, 8, 14, 0.72);
  opacity: 0;
  transition: opacity 0.28s ease;
}
#tut-root.spotlight #tut-scrim { opacity: 1; }
/* 穴を開けるときは幕そのものは消し、#tut-hole の巨大な影が幕の役を担う。
   二重に暗くならないようにするため。 */
#tut-root.spotlight.has-hole #tut-scrim { opacity: 0; }

#tut-hole {
  position: absolute;
  left: 0; top: 0;
  width: 0; height: 0;
  border-radius: 999px;
  box-shadow: 0 0 0 9999px rgba(8, 8, 14, 0.72);
  border: 2px solid var(--tc);
  opacity: 0;
  transition: opacity 0.28s ease, transform 0.28s ease;
  will-change: transform;
}
#tut-root.has-hole #tut-hole { opacity: 1; }
#tut-hole::after {
  content: '';
  position: absolute;
  inset: -8px;
  border-radius: inherit;
  border: 2px solid rgba(var(--tc-rgb), 0.55);
  animation: tut-pulse 1.6s ease-out infinite;
}
@keyframes tut-pulse {
  0%   { transform: scale(0.94); opacity: 0.9; }
  70%  { transform: scale(1.18); opacity: 0; }
  100% { transform: scale(1.18); opacity: 0; }
}

/* --- 👆 ゴースト -------------------------------------------------------
   --gx/--gy が始点、--gx2/--gy2 が終点。3D 側で実測したキューブ上の
   ステッカー位置を毎フレーム流し込むので、視点を回してもゴーストは
   正しい面に貼りついたまま動く。 */
#tut-ghost {
  position: absolute;
  left: 0; top: 0;
  font-size: 34px;
  line-height: 1;
  opacity: 0;
  filter: drop-shadow(0 2px 6px rgba(0, 0, 0, 0.7));
  transform: translate(-50%, -30%);
  transition: opacity 0.2s ease;
}
#tut-root.has-ghost #tut-ghost {
  opacity: 1;
  animation: tut-ghost-move 1.9s ease-in-out infinite;
}
@keyframes tut-ghost-move {
  0%,  12%  { transform: translate(calc(var(--gx) - 50%), calc(var(--gy) - 30%)) scale(0.9); opacity: 0; }
  22%       { transform: translate(calc(var(--gx) - 50%), calc(var(--gy) - 30%)) scale(1);   opacity: 1; }
  62%       { transform: translate(calc(var(--gx2) - 50%), calc(var(--gy2) - 30%)) scale(1); opacity: 1; }
  80%, 100% { transform: translate(calc(var(--gx2) - 50%), calc(var(--gy2) - 30%)) scale(0.9); opacity: 0; }
}
/* ゴーストがなぞる道すじ。線があるほうが「どこからどこまで」が一目で分かる。 */
#tut-trail {
  position: absolute;
  left: 0; top: 0;
  height: 6px;
  border-radius: 3px;
  background: linear-gradient(90deg, rgba(var(--tc-rgb),0), rgba(var(--tc-rgb),0.75));
  transform-origin: 0 50%;
  opacity: 0;
  transition: opacity 0.2s ease;
}
#tut-root.has-ghost #tut-trail { opacity: 0.85; }

/* --- 説明カード -------------------------------------------------------- */
#tut-card {
  position: absolute;
  left: 50%;
  bottom: max(14px, env(safe-area-inset-bottom, 0px));
  transform: translateX(-50%);
  width: min(440px, calc(100vw - 24px));
  box-sizing: border-box;
  padding: 14px 16px 12px;
  border-radius: 18px;
  background: rgba(20, 20, 28, 0.96);
  border: 1.5px solid #3a3a4e;
  box-shadow: 0 10px 30px rgba(0, 0, 0, 0.55);
  /* カード本体は透過させ、押せるのはボタンだけにする。カードは画面下部に
     居座るので、ここを auto にすると「キューブの下の段をなぞる」操作を
     カードが横取りしてしまう（ステップ1のD面がまさにその位置）。 */
  pointer-events: none;
  transition: opacity 0.2s ease, transform 0.24s cubic-bezier(.2,.9,.3,1);
}
/* スポットライトが画面下部にあるステップでは、カードが対象に
   かぶらないよう上へ逃がす。 */
#tut-root.card-top #tut-card {
  bottom: auto;
  /* ヘッダー(タイマー・所持クリスタル・≡)の下に収まる高さ。76px だと
     ≡ の下半分にカードが重なる。 */
  top: calc(118px + env(safe-area-inset-top, 0px));
}
#tut-card.swap { opacity: 0; transform: translateX(-50%) translateY(8px); }

.tut-step-label {
  font-size: 11px;
  font-weight: 800;
  letter-spacing: 1px;
  color: var(--tc);
  margin: 0 0 4px;
}
#tut-title {
  margin: 0 0 6px;
  font-size: 17px;
  font-weight: 800;
  color: #fff;
  line-height: 1.35;
}
#tut-body {
  margin: 0;
  font-size: 14px;
  line-height: 1.6;
  color: #cfcfdd;
}
#tut-body b { color: #fff; }
#tut-body .tut-em { color: var(--tc); font-weight: 800; }
#tut-body .tut-icon,
#tut-guide .tut-icon { display: inline-flex; vertical-align: -3px; margin: 0 2px; }
#tut-body .tut-icon svg,
#tut-guide .tut-icon svg { display: block; }

/* いま画面のどこを、どちらへなぞればよいか。ここだけは説明文と切り離し、
   毎フレーム「見たまま」に描き替える。キューブを持ち替えたとたんに
   「手前の面の上の段」が別の場所を指してしまう事故を、文言の側でも防ぐ。 */
#tut-guide {
  display: none;
  margin: 8px 0 0;
  padding: 7px 10px;
  border-radius: 10px;
  background: rgba(var(--tc-rgb), 0.09);
  border: 1px solid rgba(var(--tc-rgb), 0.28);
  font-size: 13px;
  line-height: 1.5;
  color: #dff;
}
#tut-guide.on { display: block; }
#tut-guide .tut-em { color: var(--tc); font-weight: 800; }

/* 設定・記録・言語などの画面が前に出ている間は、ガイドをまるごと隠す。
   #tut-root は z-index:4000 でそれらより手前にあるため、隠さないと
   説明カードとスポットライトが設定画面の上に居座ってしまう。 */
#tut-root.tut-hidden { opacity: 0; visibility: hidden; }

/* 「あと何回」のドット。4回まわす体験の進み具合をここで見せる。 */
#tut-dots {
  display: none;
  gap: 7px;
  margin-top: 10px;
  align-items: center;
}
#tut-dots.on { display: flex; }
#tut-dots i {
  width: 11px; height: 11px;
  border-radius: 50%;
  background: #3a3a4e;
  transition: background 0.2s ease, transform 0.2s ease;
}
#tut-dots i.done { background: var(--tc); transform: scale(1.15); }

#tut-actions {
  display: flex;
  align-items: center;
  gap: 10px;
  margin-top: 12px;
}
#tut-skip, #tut-next { pointer-events: auto; }
#tut-skip {
  background: none;
  border: none;
  color: #8a8a9e;
  font-size: 13px;
  font-family: inherit;
  padding: 6px 4px;
  cursor: pointer;
}
#tut-skip:active { color: #cfcfdd; }
#tut-next {
  margin-left: auto;
  border: none;
  border-radius: 999px;
  padding: 10px 22px;
  font-size: 15px;
  font-weight: 800;
  font-family: inherit;
  color: #08121a;
  background: var(--tc);
  cursor: pointer;
  /* 表示されたら「ここを押せばいい」がひと目で分かるよう、しっかり脈打たせる。 */
  animation: tut-next-glow 1.3s ease-in-out infinite;
}
#tut-next:active { transform: scale(0.97); }
#tut-next[hidden] { display: none; }
@keyframes tut-next-glow {
  0%, 100% { box-shadow: 0 0 10px rgba(var(--tc-rgb), 0.5), 0 0 2px rgba(var(--tc-rgb), 0.6); }
  50%      { box-shadow: 0 0 34px rgba(var(--tc-rgb), 1), 0 0 14px rgba(var(--tc-rgb), 0.85), 0 0 4px #fff; }
}
/* 最後の「スタート」だけは赤系にして、それまでの「つぎへ」と見分ける。 */
#tut-next.tut-final {
  color: #fff;
  background: #ff3b3b;
  animation: tut-next-glow-red 1.3s ease-in-out infinite;
}
@keyframes tut-next-glow-red {
  0%, 100% { box-shadow: 0 0 10px rgba(255, 59, 59, 0.55), 0 0 2px rgba(255, 59, 59, 0.65); }
  50%      { box-shadow: 0 0 34px rgba(255, 59, 59, 1), 0 0 14px rgba(255, 59, 59, 0.9), 0 0 4px #fff; }
}

/* 4回まわし切った瞬間の「1周した」フラッシュ。 */
#tut-card.cheer {
  border-color: var(--tc);
  box-shadow: 0 0 0 3px rgba(var(--tc-rgb), 0.25), 0 10px 30px rgba(0, 0, 0, 0.55);
}

@media (max-width: 380px) {
  #tut-title { font-size: 16px; }
  #tut-body  { font-size: 13px; }
  #tut-ghost { font-size: 28px; }
}

/* 動きを減らす設定では、ゴーストの往復もリングの脈打ちも止める。
   位置(始点)だけ残せば「どこを触るか」は伝わる。 */
@media (prefers-reduced-motion: reduce) {
  #tut-root.has-ghost #tut-ghost {
    animation: none;
    transform: translate(calc(var(--gx) - 50%), calc(var(--gy) - 30%));
    opacity: 1;
  }
  #tut-hole::after { animation: none; opacity: 0.6; }
  #tut-card { transition: none; }
  #tut-next { animation: none; box-shadow: 0 0 20px rgba(var(--tc-rgb), 0.75), 0 0 6px rgba(var(--tc-rgb), 0.6); }
  #tut-next.tut-final { animation: none; box-shadow: 0 0 20px rgba(255, 59, 59, 0.75), 0 0 6px rgba(255, 59, 59, 0.6); }
}
/* === タップキューブ チュートリアル : CSS ここまで ======================== */

/* =========================================================================
   === RANK / EXP システム : CSS ここから =================================
   クリア時のリザルト（ランクバッジ＋EXPバー）と、
   「🏆 記録」画面の先頭に出すランクカード。
   色は JS(RankResult.paint) が :root の --bd-* を書き換えて渡してくる。
   ========================================================================= */
:root{
  --bd-main: #ffffff;   /* 現在ランクのバッジ色（明るい面） */
  --bd-deep: #b9c4cf;   /* その影側 */
  --bd-glow: rgba(255,255,255,.55);  /* 発光 */
}

/* ---- リザルト全体 ---------------------------------------------------- */
.rr{
  /* 完成時の白フラッシュ(9998)・大当たり演出(#jackpot-fx 10050 /
     カットイン 10060)より前面に出す必要があるので、この値。
     スプラッシュ(999999)より下なのは起動中に出ないようにするため。 */
  position:fixed; inset:0; z-index:10100;
  display:flex; align-items:center; justify-content:center;
  padding:24px; opacity:0; visibility:hidden;
  transition:opacity .22s ease, visibility .22s;
}
.rr.open{ opacity:1; visibility:visible; }
.rr-scrim{
  position:absolute; inset:0;
  background:radial-gradient(80% 60% at 50% 42%, rgba(10,16,26,.72), rgba(4,6,10,.92));
  backdrop-filter:blur(6px); -webkit-backdrop-filter:blur(6px);
}
.rr-card{
  position:relative; width:min(340px,100%);
  padding:24px 20px 18px;
  border-radius:26px;
  background:linear-gradient(180deg, rgba(38,52,70,.62), rgba(15,19,26,.94));
  border:1px solid rgba(255,255,255,.12);
  box-shadow:0 30px 70px rgba(0,0,0,.62), inset 0 1px 0 rgba(255,255,255,.12);
  text-align:center;
  transform:translateY(18px) scale(.94);
  transition:transform .34s cubic-bezier(.2,1.15,.3,1);
}
.rr.open .rr-card{ transform:none; }
.rr-eyebrow{
  font-size:11px; font-weight:800; letter-spacing:.34em;
  color:#8a95a5; text-transform:uppercase; margin-bottom:2px;
}
.rr-time{
  font-size:13px; font-weight:700; letter-spacing:.06em;
  color:#ffd76a; min-height:19px; margin-bottom:12px;
}

/* ---- バッジ（六角クリスタル） ---------------------------------------- */
.rr-badge-wrap{
  position:relative; width:150px; height:150px; margin:0 auto 4px;
  display:flex; align-items:center; justify-content:center;
}
.rr-badge{
  position:relative; width:112px; height:124px;
  clip-path:polygon(50% 0%, 100% 25%, 100% 75%, 50% 100%, 0% 75%, 0% 25%);
  background:linear-gradient(160deg, var(--bd-main), var(--bd-deep));
  filter:drop-shadow(0 0 18px var(--bd-glow));
  transition:filter .5s ease;
}
.rr-badge-inner{
  position:absolute; inset:5px;
  clip-path:polygon(50% 0%, 100% 25%, 100% 75%, 50% 100%, 0% 75%, 0% 25%);
  background:linear-gradient(150deg, var(--bd-main) 0%, var(--bd-deep) 78%);
  overflow:hidden; display:flex; align-items:center; justify-content:center;
}
/* カット面：上のハイライトと下の影で「削り出した結晶」に見せる */
.rr-badge-inner::before{
  content:""; position:absolute; inset:0;
  background:linear-gradient(180deg, rgba(255,255,255,.55), rgba(255,255,255,0) 52%);
  clip-path:polygon(50% 0%, 100% 25%, 50% 50%, 0% 25%);
}
.rr-badge-inner::after{
  content:""; position:absolute; inset:0;
  background:linear-gradient(0deg, rgba(0,0,0,.42), rgba(0,0,0,0) 60%);
  clip-path:polygon(50% 50%, 100% 75%, 50% 100%, 0% 75%);
}
.rr-rainbow{
  position:absolute; left:50%; top:50%; width:240%; height:240%;
  transform:translate(-50%,-50%);
  background:conic-gradient(from 0deg,#ff5f6d,#ffc371,#8fff8f,#5fe3ff,#a98bff,#ff5fd2,#ff5f6d);
  opacity:0; transition:opacity .5s ease;
  animation:rrSpin 5.5s linear infinite;
}
.rr-badge.rainbow .rr-rainbow{ opacity:1; }
.rr-badge.rainbow{ animation:rrRbGlow 2.6s ease-in-out infinite; }
@keyframes rrSpin{ to{ transform:translate(-50%,-50%) rotate(360deg); } }
@keyframes rrRbGlow{
  0%,100%{ filter:drop-shadow(0 0 16px rgba(255,255,255,.5)) hue-rotate(0deg); }
  50%    { filter:drop-shadow(0 0 34px rgba(255,255,255,.85)) hue-rotate(180deg); }
}
.rr-num{
  position:relative; z-index:2;
  font-size:44px; font-weight:900; font-variant-numeric:tabular-nums;
  letter-spacing:-.02em; line-height:1;
}
.rr-badge.dark-ink .rr-num{ color:#0d1218; text-shadow:0 1px 0 rgba(255,255,255,.55); }
.rr-badge.light-ink .rr-num{ color:#fff; text-shadow:0 2px 6px rgba(0,0,0,.5); }

/* レベルアップの瞬間 */
.rr-badge.levelup{ animation:rrBadgePop .62s cubic-bezier(.2,1.4,.3,1); }
@keyframes rrBadgePop{
  0%  { transform:scale(1); }
  22% { transform:scale(.88) rotate(-4deg); }
  48% { transform:scale(1.34) rotate(3deg); filter:drop-shadow(0 0 44px #fff) brightness(1.7); }
  72% { transform:scale(.98) rotate(-1deg); }
  100%{ transform:scale(1); }
}
.rr-rays{
  position:absolute; width:150px; height:150px; border-radius:50%;
  background:repeating-conic-gradient(from 0deg,
    rgba(255,255,255,.85) 0deg 3deg, rgba(255,255,255,0) 3deg 22deg);
  opacity:0; pointer-events:none;
}
.rr-rays.go{ animation:rrRays .62s ease-out; }
@keyframes rrRays{
  0%  { opacity:0; transform:scale(.4) rotate(0deg); }
  30% { opacity:.9; }
  100%{ opacity:0; transform:scale(2.3) rotate(38deg); }
}
.rr-ring{
  position:absolute; width:112px; height:124px; pointer-events:none;
  clip-path:polygon(50% 0%, 100% 25%, 100% 75%, 50% 100%, 0% 75%, 0% 25%);
  background:var(--bd-main); opacity:0;
}
.rr-ring.go{ animation:rrRing .58s ease-out; }
@keyframes rrRing{
  0%  { opacity:.55; transform:scale(1); }
  100%{ opacity:0;   transform:scale(1.85); }
}
.rr-sparks{ position:absolute; inset:0; pointer-events:none; }
.rr-spark{
  position:absolute; left:50%; top:50%; width:6px; height:6px; margin:-3px 0 0 -3px;
  border-radius:999px; background:#fff; box-shadow:0 0 10px var(--bd-glow);
}

/* ---- EXPバー ---------------------------------------------------------- */
.rr-rank-label{
  font-size:11px; font-weight:800; letter-spacing:.32em;
  color:#8a95a5; text-transform:uppercase; margin:8px 0 12px;
}
.rr-rank-label b{ color:var(--bd-main); font-weight:800; }
.rr-bar{
  position:relative; height:14px; border-radius:999px;
  background:rgba(255,255,255,.09);
  box-shadow:inset 0 1px 3px rgba(0,0,0,.6);
  overflow:hidden;
}
.rr-fill{
  position:absolute; left:0; top:0; bottom:0; width:0%;
  border-radius:999px;
  background:linear-gradient(90deg, var(--bd-deep), var(--bd-main));
  box-shadow:0 0 14px var(--bd-glow);
}
.rr-fill::after{
  content:""; position:absolute; inset:0; opacity:0;
  background:linear-gradient(100deg, transparent 20%, rgba(255,255,255,.85) 50%, transparent 80%);
  transform:translateX(-100%);
}
.rr-bar.surging .rr-fill::after{ opacity:1; animation:rrSheen .8s linear infinite; }
@keyframes rrSheen{ to{ transform:translateX(220%); } }
.rr-comet{
  position:absolute; right:-2px; top:50%; width:16px; height:16px;
  margin-top:-8px; border-radius:999px; background:#fff;
  filter:blur(4px); opacity:0; transition:opacity .18s ease;
}
.rr-bar.surging .rr-comet{ opacity:.95; }
.rr-bar.maxed .rr-fill{
  background:conic-gradient(from 0deg,#ff5f6d,#ffc371,#8fff8f,#5fe3ff,#a98bff,#ff5fd2,#ff5f6d);
  animation:rrHue 4s linear infinite;
}
@keyframes rrHue{ to{ filter:hue-rotate(360deg); } }
.rr-nums{
  display:flex; justify-content:space-between; align-items:baseline;
  margin-top:8px; font-size:12px; color:#8a95a5;
  font-variant-numeric:tabular-nums; letter-spacing:.02em;
}
.rr-nums .cur{ color:#eaf4ff; font-weight:800; font-size:14px; }

/* ---- 獲得EXP ---------------------------------------------------------- */
.rr-gain{
  margin-top:14px; display:inline-flex; align-items:center; gap:8px;
  padding:8px 16px; border-radius:999px;
  background:rgba(95,227,255,.10);
  border:1px solid rgba(95,227,255,.28);
  color:#5fe3ff; font-weight:800; font-size:17px;
  font-variant-numeric:tabular-nums;
  opacity:0; transform:translateY(6px) scale(.9);
}
.rr-gain.show{ animation:rrGainPop .5s cubic-bezier(.2,1.5,.35,1) forwards; }
@keyframes rrGainPop{
  0%  { opacity:0; transform:translateY(8px) scale(.86); }
  60% { opacity:1; transform:translateY(0) scale(1.12); }
  100%{ opacity:1; transform:translateY(0) scale(1); }
}
.rr-close{
  margin-top:16px; width:100%; height:46px; border-radius:14px;
  border:1px solid rgba(255,255,255,.14); background:rgba(255,255,255,.06);
  color:#eaf4ff; font-size:14px; font-weight:700; letter-spacing:.06em;
  font-family:var(--ui-font);
  opacity:0; pointer-events:none; transition:opacity .3s ease;
}
.rr-close.show{ opacity:1; pointer-events:auto; }
.rr-close:active{ transform:translateY(1px); }

/* ---- 画面フラッシュ ---------------------------------------------------- */
.rr-flash{
  position:fixed; inset:0; z-index:10110; pointer-events:none;
  background:#fff; opacity:0;
}
.rr-flash.go{ animation:rrFlash .3s ease-out; }
@keyframes rrFlash{
  0%  { opacity:0; }
  12% { opacity:.92; }
  100%{ opacity:0; }
}

/* ---- 「🏆 記録」画面の先頭に出すランクカード -------------------------- */
.rank-card{
  display:flex; align-items:center; gap:14px;
  padding:12px 14px; margin:0 0 14px;
  border-radius:16px;
  background:linear-gradient(180deg, rgba(40,64,86,.34), rgba(16,22,32,.5));
  border:1px solid rgba(255,255,255,.10);
}
.rank-card .rr-badge{ width:54px; height:60px; flex:0 0 auto; }
.rank-card .rr-badge-inner{ inset:3px; }
.rank-card .rr-num{ font-size:21px; }
.rank-card-body{ flex:1 1 auto; min-width:0; text-align:left; }
.rank-card-title{
  font-size:10px; font-weight:800; letter-spacing:.24em;
  color:#8a95a5; text-transform:uppercase; margin-bottom:6px;
}
.rank-card-title b{ color:var(--bd-main); font-size:15px; letter-spacing:0; }
.rank-card .rr-bar{ height:9px; }
.rank-card-sub{
  margin-top:6px; font-size:11px; color:#8a95a5;
  font-variant-numeric:tabular-nums;
}

@media (prefers-reduced-motion: reduce){
  .rr-badge.levelup, .rr-rays.go, .rr-ring.go, .rr-flash.go,
  .rr-badge.rainbow, .rr-rainbow, .rr-bar.maxed .rr-fill,
  .rr-bar.surging .rr-fill::after{ animation:none !important; }
  .rr-flash{ display:none; }
}
/* === RANK / EXP システム : CSS ここまで ================================= */

/* =========================================================================
   === RANK FX : 節目ランクの演出 CSS ここから ============================
   ランク 2 / 10 / 30 / 50 / 100 に到達した瞬間だけ、通常のランクアップ
   演出に「レイヤーを重ねて」段階的に派手にする。
     tier1 spark  (2)   … RANK UP! ポップ + 小花火
     tier2 prism  (10)  … 全画面フラッシュ + 虹の粒子が横断 + 大テロップ
     tier3 neon   (30)  … スキャンライン + ネオン光輪 + バッジにオーラ
     tier4 ascend (50)  … 暗転 → 光の柱 → 称号カットイン
     tier5 legend (100) … 全画面レインボー + 虹シャードの滝 + 永続オーラ
   粒子はDOMではなく1枚のcanvasに描く（端末が古くてもコマ落ちしない）。
   ========================================================================= */

/* ---- レイヤーの器 ------------------------------------------------------ */
.rank-fx, .rank-fx-canvas{
  position:fixed; inset:0; pointer-events:none;
}
.rank-fx{ z-index:10105; overflow:hidden; }
.rank-fx-canvas{ z-index:10106; width:100%; height:100%; }
.rank-fx > *{ position:absolute; inset:0; opacity:0; }

/* ---- tier4 : 暗転のヴェール -------------------------------------------- */
.rfx-veil{ background:#04060a; }
.rfx-veil.go{ animation:rfxVeil 2.3s ease-in-out forwards; }
@keyframes rfxVeil{
  0%{ opacity:0; } 14%{ opacity:.92; } 72%{ opacity:.72; } 100%{ opacity:0; }
}

/* ---- tier4 : 中央を突き抜ける光の柱 ------------------------------------ */
.rfx-pillar{
  background:
    linear-gradient(90deg, transparent 46%, rgba(255,255,255,.95) 50%, transparent 54%),
    linear-gradient(90deg, transparent 40%, rgba(255,231,160,.55) 50%, transparent 60%);
  filter:blur(1px);
  transform:scaleX(.02) scaleY(0);
  transform-origin:50% 50%;
}
.rfx-pillar.go{ animation:rfxPillar 1.5s cubic-bezier(.15,.9,.2,1) forwards; }
@keyframes rfxPillar{
  0%  { opacity:0; transform:scaleX(.02) scaleY(0); }
  18% { opacity:1; transform:scaleX(.05) scaleY(1); }
  46% { opacity:1; transform:scaleX(1)   scaleY(1); }
  100%{ opacity:0; transform:scaleX(2.6) scaleY(1); }
}

/* ---- tier3 : サイバーなスキャンライン ---------------------------------- */
.rfx-scan{
  background:repeating-linear-gradient(180deg,
    rgba(95,227,255,.16) 0px, rgba(95,227,255,.16) 1px,
    transparent 1px, transparent 4px);
  mix-blend-mode:screen;
}
.rfx-scan.go{ animation:rfxScan 1.25s linear forwards; }
@keyframes rfxScan{
  0%  { opacity:0;  transform:translateY(-14px); }
  12% { opacity:.95; }
  100%{ opacity:0;  transform:translateY(120px); }
}

/* ---- tier3 : ネオンの光輪（3重に広がる） ------------------------------- */
.rfx-halo{ display:flex; align-items:center; justify-content:center; }
.rfx-halo i{
  position:absolute; width:180px; height:180px; border-radius:50%;
  border:2px solid rgba(95,227,255,.9);
  box-shadow:0 0 26px rgba(95,227,255,.85), inset 0 0 26px rgba(95,227,255,.5);
  opacity:0;
}
.rfx-halo.go{ opacity:1; }
.rfx-halo.go i{ animation:rfxHalo 1.05s cubic-bezier(.1,.8,.2,1) forwards; }
.rfx-halo.go i:nth-child(2){ animation-delay:.13s; border-color:rgba(168,139,255,.9); }
.rfx-halo.go i:nth-child(3){ animation-delay:.26s; border-color:rgba(255,215,106,.9); }
@keyframes rfxHalo{
  0%  { opacity:.95; transform:scale(.2); }
  100%{ opacity:0;   transform:scale(7); }
}

/* ---- tier5 : 全画面レインボー ------------------------------------------ */
.rfx-wash{
  background:conic-gradient(from 0deg,
    #ff5f6d,#ffc371,#8fff8f,#5fe3ff,#a98bff,#ff5fd2,#ff5f6d);
  mix-blend-mode:screen;
}
.rfx-wash.go{ animation:rfxWash 3.4s ease-in-out forwards; }
@keyframes rfxWash{
  0%  { opacity:0;   filter:hue-rotate(0deg)   saturate(1.6); transform:scale(1.6) rotate(0deg); }
  16% { opacity:.62; }
  70% { opacity:.34; }
  100%{ opacity:0;   filter:hue-rotate(360deg) saturate(1.6); transform:scale(1) rotate(120deg); }
}

/* ---- テロップ（tier1〜5で大きさと質感が変わる） ------------------------ */
.rfx-telop{
  display:flex; flex-direction:column; align-items:center; justify-content:center;
  gap:6px; text-align:center; padding:0 16px;
}
.rfx-telop.go{ opacity:1; }
.rfx-telop-sub{
  font-size:12px; font-weight:800; letter-spacing:.42em; text-transform:uppercase;
  color:#d9fbff; text-shadow:0 0 14px rgba(95,227,255,.9);
  opacity:0;
}
.rfx-telop-main{
  font-family:var(--ui-font);
  font-weight:900; letter-spacing:-.01em; line-height:1;
  font-size:clamp(34px, 12vw, 68px);
  color:#fff;
  text-shadow:0 4px 24px rgba(0,0,0,.6);
  opacity:0;
}
/* --- tier1: ぽんっと飛び出すだけの軽いポップ --- */
.rfx-telop.t1 .rfx-telop-main{
  font-size:clamp(28px, 9vw, 46px);
  animation:rfxPop .72s cubic-bezier(.2,1.6,.3,1) forwards;
}
@keyframes rfxPop{
  0%  { opacity:0; transform:scale(.4) translateY(14px); }
  55% { opacity:1; transform:scale(1.16) translateY(0); }
  76% { transform:scale(.96); }
  100%{ opacity:1; transform:scale(1); }
}
/* --- tier2: 虹のグラデ文字。リッチに見せるのは色と字間 --- */
.rfx-telop.t2 .rfx-telop-main{
  background:linear-gradient(100deg,#fff 0%,#5fe3ff 28%,#a98bff 52%,#ff5fd2 74%,#ffd76a 100%);
  -webkit-background-clip:text; background-clip:text; color:transparent;
  filter:drop-shadow(0 0 18px rgba(120,200,255,.75));
  animation:rfxPop .8s cubic-bezier(.2,1.5,.3,1) forwards;
}
.rfx-telop.t2 .rfx-telop-sub{ animation:rfxFadeUp .6s ease-out .1s forwards; }
/* --- tier3: ネオン。左からスキューでカットイン --- */
.rfx-telop.t3 .rfx-telop-main{
  color:#eafcff;
  text-shadow:0 0 8px #5fe3ff, 0 0 26px #2ea8ff, 0 0 60px #1b6cff;
  animation:rfxCutIn .78s cubic-bezier(.12,.9,.2,1) forwards;
}
.rfx-telop.t3 .rfx-telop-sub{
  color:#7ff0ff; animation:rfxFadeUp .6s ease-out .12s forwards;
}
@keyframes rfxCutIn{
  0%  { opacity:0; transform:translateX(-38vw) skewX(-16deg) scale(1.1); }
  62% { opacity:1; transform:translateX(0) skewX(-4deg) scale(1); }
  100%{ opacity:1; transform:translateX(0) skewX(0) scale(1); }
}
/* --- tier4: 称号。金〜プラチナで、下線が横に走る --- */
.rfx-telop.t4 .rfx-telop-main{
  /* 金 → プラチナ → 金。中間で一度冷たい銀を挟むと金属に見える */
  background:linear-gradient(180deg,#fffdf3 0%,#ffe9a8 40%,#d7b45a 52%,#e6ebf3 66%,#fff8e0 100%);
  -webkit-background-clip:text; background-clip:text; color:transparent;
  filter:drop-shadow(0 0 26px rgba(255,214,120,.8));
  animation:rfxAwaken 1.1s cubic-bezier(.16,1,.3,1) forwards;
}
.rfx-telop.t4 .rfx-telop-sub{
  color:#ffe9a8; letter-spacing:.5em;
  animation:rfxFadeUp .7s ease-out .28s forwards;
}
@keyframes rfxAwaken{
  0%  { opacity:0; transform:scale(2.4); filter:blur(14px) drop-shadow(0 0 26px #ffd76a); }
  46% { opacity:1; transform:scale(1); filter:blur(0) drop-shadow(0 0 34px #ffd76a); }
  100%{ opacity:1; transform:scale(1); filter:blur(0) drop-shadow(0 0 20px #ffd76a); }
}
/* --- tier5: 伝説。文字自体がレインボーで流れ続ける --- */
.rfx-telop.t5 .rfx-telop-main{
  font-size:clamp(40px, 15vw, 86px); letter-spacing:.06em;
  background:linear-gradient(100deg,#ff5f6d,#ffc371,#8fff8f,#5fe3ff,#a98bff,#ff5fd2,#ff5f6d);
  background-size:300% 100%;
  -webkit-background-clip:text; background-clip:text; color:transparent;
  filter:drop-shadow(0 0 30px rgba(255,255,255,.9));
  animation:rfxAwaken 1.1s cubic-bezier(.16,1,.3,1) forwards,
            rfxFlow 2.4s linear .6s infinite;
}
.rfx-telop.t5 .rfx-telop-sub{
  color:#fff; letter-spacing:.55em;
  animation:rfxFadeUp .7s ease-out .3s forwards;
}
@keyframes rfxFlow{ to{ background-position:300% 0; } }
@keyframes rfxFadeUp{
  0%  { opacity:0; transform:translateY(10px); }
  100%{ opacity:1; transform:translateY(0); }
}

/* ---- バッジのオーラ（tier3以上）＋残像 --------------------------------- */
.rr-badge-wrap.aura::before{
  content:""; position:absolute; left:50%; top:50%;
  width:190px; height:190px; margin:-95px 0 0 -95px;
  border-radius:50%;
  background:conic-gradient(from 0deg,
    transparent 0deg, var(--bd-main) 60deg, transparent 130deg,
    var(--bd-main) 210deg, transparent 300deg);
  filter:blur(16px); opacity:.75;
  animation:rfxAuraSpin 2.4s linear infinite;
}
/* 残像：バッジと同じ六角形が、少し遅れて外へ広がっては消える */
.rr-badge-wrap.aura::after{
  content:""; position:absolute; left:50%; top:50%;
  width:112px; height:124px; margin:-62px 0 0 -56px;
  clip-path:polygon(50% 0%, 100% 25%, 100% 75%, 50% 100%, 0% 75%, 0% 25%);
  background:var(--bd-main); opacity:0;
  mix-blend-mode:screen;
  animation:rfxGhost 1.1s ease-out infinite;
}
@keyframes rfxAuraSpin{ to{ transform:rotate(360deg); } }
@keyframes rfxGhost{
  0%  { opacity:.42; transform:scale(1); }
  100%{ opacity:0;  transform:scale(1.7); }
}
/* 神々しい明滅（tier4/5のバッジ） */
.rr-badge.divine{ animation:rfxDivine 1.4s ease-in-out 2; }
@keyframes rfxDivine{
  0%,100%{ filter:drop-shadow(0 0 20px var(--bd-glow)) brightness(1); }
  30%    { filter:drop-shadow(0 0 60px #fff) brightness(2.4) saturate(1.4); }
  55%    { filter:drop-shadow(0 0 26px #fff) brightness(1.2); }
  78%    { filter:drop-shadow(0 0 70px #fff) brightness(2.1); }
}

/* ---- ランク100以降、UIを永続的に「伝説仕様」へ -------------------------- */
.legend-mode .rr-badge-wrap::before{
  content:""; position:absolute; left:50%; top:50%;
  width:190px; height:190px; margin:-95px 0 0 -95px; border-radius:50%;
  background:conic-gradient(from 0deg,#ff5f6d,#ffc371,#8fff8f,#5fe3ff,#a98bff,#ff5fd2,#ff5f6d);
  filter:blur(20px); opacity:.6;
  animation:rfxAuraSpin 4.5s linear infinite;
}
.legend-mode .rank-card{
  border-color:rgba(255,255,255,.3);
  background:linear-gradient(180deg, rgba(90,70,140,.35), rgba(16,22,32,.55));
  box-shadow:0 0 26px rgba(160,120,255,.28);
}
.legend-mode .rank-card-title b,
.legend-mode #records-panel > h2,
.legend-mode .records-panel-header h2{
  background:linear-gradient(100deg,#ff5f6d,#ffc371,#8fff8f,#5fe3ff,#a98bff,#ff5fd2,#ff5f6d);
  background-size:300% 100%;
  -webkit-background-clip:text; background-clip:text; color:transparent;
  animation:rfxFlow 4s linear infinite;
}
.legend-mode .rr-card{
  border-color:rgba(255,255,255,.28);
  box-shadow:0 30px 70px rgba(0,0,0,.62),
             inset 0 1px 0 rgba(255,255,255,.2),
             0 0 40px rgba(180,140,255,.35);
}

/* ---- 動きを減らす設定の端末では、光量だけ残して動きを止める ------------- */
@media (prefers-reduced-motion: reduce){
  .rfx-veil.go, .rfx-pillar.go, .rfx-scan.go, .rfx-halo.go i, .rfx-wash.go,
  .rr-badge-wrap.aura::before, .rr-badge-wrap.aura::after,
  .rr-badge.divine, .legend-mode .rr-badge-wrap::before,
  .rfx-telop.t1 .rfx-telop-main, .rfx-telop.t2 .rfx-telop-main,
  .rfx-telop.t3 .rfx-telop-main, .rfx-telop.t4 .rfx-telop-main,
  .rfx-telop.t5 .rfx-telop-main{ animation:none !important; }
  .rfx-telop.go .rfx-telop-main, .rfx-telop.go .rfx-telop-sub{ opacity:1; }
  .rank-fx-canvas{ display:none; }
}
/* === RANK FX : 節目ランクの演出 CSS ここまで ============================ */

/* =========================================================================
   === SHOP : CSS ここから =================================================
   ドロワー「🏆 記録」の下の 🛍️ ショップ。
   器の見た目は記録画面(#records-panel)に合わせてある。#1c1c22 の面に
   #3a3a48 の枠、というアプリ共通の作りから外れないようにするため。
   ここだけの新色は使わず、選択状態はテーマカラー(--tc)で示す。
   ========================================================================= */
#shop-overlay {
  display: none;
  position: fixed;
  inset: 0;
  background: #08080b;
  z-index: 30;
  align-items: center;
  justify-content: center;
}
#shop-overlay.show { display: flex; }

#shop-panel {
  background: #1c1c22;
  border: 1px solid #3a3a48;
  border-radius: 14px;
  padding: 18px;
  width: min(90vw, 360px);
  max-height: 80vh;
  max-height: 80dvh;
  display: flex;
  flex-direction: column;
  text-align: center;
  position: relative;
}

/* ---- 見出し＋残高 -------------------------------------------------------
   何を持っているかが分からないまま値段だけ見せても選べないので、
   タイトルの真横に今の残高を出しておく。ヘッダーのピルと同じ数字。 */
.shop-header {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 10px;
  margin-bottom: 4px;
}
.shop-header h2 {
  font-size: 16px;
  margin: 0;
}
.shop-wallets {
  display: flex;
  align-items: center;
  gap: 8px;
}
.shop-wallet {
  display: inline-flex;
  align-items: center;
  gap: 3px;
  padding: 3px 8px 3px 6px;
  border-radius: 999px;
  background: rgba(255, 255, 255, 0.06);
  border: 1px solid #3a3a48;
  font-size: 11px;
  font-weight: 800;
  font-variant-numeric: tabular-nums;
  color: #dfe6f2;
}
.shop-wallet svg { display: block; }

/* 見出し（現在は未使用。品目を1ページに複数並べたくなったとき用に残す） */
.shop-section-title {
  font-size: 11px;
  font-weight: 800;
  letter-spacing: 0.08em;
  text-align: left;
  color: #8a95a5;
  margin: 10px 2px 8px;
}
/* ---- ページ切り替え（メインカラー / キューブスキン）----------------------
   品目が増えても縦に積まず、1ページ1品目にして横で切り替える。タブは
   今つけているテーマカラーで光らせる（ショップの中身と地続きに見せる）。 */
.shop-tabs {
  display: flex;
  gap: 6px;
  margin: 10px 0 8px;
}
.shop-tab {
  flex: 1 1 0;
  min-width: 0;
  padding: 8px 6px;
  border-radius: 10px;
  background: #23232c;
  border: 1.4px solid #3a3a48;
  color: #b8b8c4;
  font-family: inherit;
  font-size: 12px;
  font-weight: 800;
  line-height: 1.2;
  cursor: pointer;
  touch-action: manipulation;
}
.shop-tab.on {
  border-color: var(--tc);
  color: var(--tc);
  background: rgba(var(--tc-rgb), 0.10);
}
.shop-tab:active { transform: scale(0.98); }

.shop-page.is-hidden { display: none; }
/* 横になぞって移ったことが分かるよう、入ってくる側だけ軽く滑らせる。 */
.shop-page.slide-in { animation: shopPageIn 0.2s ease-out; }
@keyframes shopPageIn {
  from { opacity: 0; transform: translateX(10px); }
  to   { opacity: 1; transform: none; }
}
.shop-page-lead { margin-bottom: 8px; }

/* ---- 一覧 ---------------------------------------------------------------
   色そのものが商品なので、名前より先に色の面積が目に入るようにする。
   1行2枚。3枚並べると見本が小さくなりすぎて色の違いが分からない。 */
.shop-scroll {
  flex: 1 1 auto;
  overflow-y: auto;
  -webkit-overflow-scrolling: touch;
  overscroll-behavior: contain;
  margin: 0 -4px;
  padding: 0 4px;
}
.shop-grid {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: 8px;
  /* 最後の行がスクロール端にぴったり張り付くと、下にまだ続くのか
     終わりなのかが読めない。1行ぶんの余白を残して切れ際を作る。 */
  padding-bottom: 6px;
}
.shop-card {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 6px;
  padding: 12px 8px 10px;
  background: #23232c;
  border: 1.4px solid #3a3a48;
  border-radius: 12px;
  color: #fff;
  font-family: inherit;
  cursor: pointer;
  position: relative;
  transition: border-color 0.15s ease, background 0.15s ease,
    transform 0.12s ease;
}
.shop-card:active { transform: scale(0.97); }

/* 買ってあるがつけていない色は、枠だけ少し起こしておく。
   「持っている／持っていない」が値段の有無だけでなく形でも分かるように。 */
.shop-card.owned { border-color: #55556a; }
/* 今つけている色。テーマカラーそのもので囲うので、見本と枠の色が一致する。 */
.shop-card.equipped {
  border-color: var(--tc);
  background: rgba(var(--tc-rgb), 0.10);
}
/* シャードが足りない色は沈める。押せないのではなく、押すと理由が出る。 */
.shop-card.short { opacity: 0.5; }

/* ---- 色見本 -------------------------------------------------------------
   四角ではなくキューブの1マスに見えるよう、角丸＋斜めのハイライトを
   のせてある。--sw に色を差し込むだけで塗り替わる。 */
.shop-swatch {
  width: 44px;
  height: 44px;
  border-radius: 10px;
  background: var(--sw, #00ffcc);
  box-shadow:
    inset 0 -8px 12px rgba(0, 0, 0, 0.28),
    inset 0 6px 10px rgba(255, 255, 255, 0.35),
    0 0 14px rgba(0, 0, 0, 0.35);
  position: relative;
  flex: 0 0 auto;
}
.shop-swatch::after {
  content: '';
  position: absolute;
  left: 6px;
  top: 5px;
  width: 14px;
  height: 8px;
  border-radius: 6px;
  background: rgba(255, 255, 255, 0.5);
  filter: blur(1px);
}
/* プリズムは1色に決まらない枠なので、見本のほうも回して見せる。
   見本そのものを回すとマスが菱形になってしまうので、回すのは中の
   色だけ。角丸で切り抜いた枠の内側でグラデーションを回転させる。 */
/* マークの見本。写真そのものを1マスに切って見せる。背景画像は
   カード生成時にインラインで差し込む（data URI を CSS に書かずに
   済ませて、絵の差し替えを index.html 側だけで完結させるため）。 */
.shop-swatch.is-mark {
  background-size: cover;
  background-position: center;
  overflow: hidden;
}
/* 「なし」の枠。絵が無いので、消してあることが分かる斜線だけ引く。 */
.shop-swatch.is-mark.is-off::before {
  content: '';
  position: absolute;
  inset: 0;
  border-radius: 10px;
  background: linear-gradient(
    135deg, transparent 46%, #6f7889 46%, #6f7889 54%, transparent 54%);
}

.shop-swatch.is-prism { overflow: hidden; }
.shop-swatch.is-prism::before {
  content: '';
  position: absolute;
  /* 回転で角が欠けないよう、対角線ぶんまで広げてから回す。 */
  inset: -45%;
  background: conic-gradient(
    #ff5f6d, #ffc371, #8fff8f, #5fe3ff, #a98bff, #ff5fd2, #ff5f6d);
  animation: shopPrismSpin 6s linear infinite;
}
/* ハイライトは回さない。光の当たり方まで回ると立体感が壊れる。 */
.shop-swatch.is-prism::after { z-index: 1; }
@keyframes shopPrismSpin { to { transform: rotate(360deg); } }

.shop-name {
  font-size: 12px;
  font-weight: 700;
  line-height: 1.2;
  color: #e8edf5;
}

/* ---- 値段と状態 --------------------------------------------------------- */
.shop-price {
  display: inline-flex;
  align-items: center;
  gap: 4px;
  font-size: 12px;
  font-weight: 800;
  font-variant-numeric: tabular-nums;
  color: #b8c2d2;
}
/* シャードのアイコン。画像を増やさずに済むよう、財布のピルと同じ
   結晶の形を CSS の clip-path だけで作る。 */
.shop-shard {
  width: 9px;
  height: 13px;
  clip-path: polygon(50% 0%, 100% 34%, 50% 100%, 0% 34%);
  flex: 0 0 auto;
}
.shop-shard.cy { background: linear-gradient(180deg, #d9fbff, #5fd8f5); }
.shop-shard.rb { background: linear-gradient(180deg, #fdfbff, #ffd7f0 50%, #c9e3ff); }

.shop-state {
  font-size: 11px;
  font-weight: 800;
  color: #9aa5b5;
  line-height: 1.4;
}
.shop-state.on { color: var(--tc); }

/* 引き換えた瞬間だけ、そのカードから光が広がる。 */
.shop-card.just-got::after {
  content: '';
  position: absolute;
  inset: -2px;
  border-radius: 13px;
  border: 2px solid var(--tc);
  animation: shopGotRing 0.9s ease-out forwards;
  pointer-events: none;
}
@keyframes shopGotRing {
  0%   { opacity: 1; transform: scale(1);    box-shadow: 0 0 0 0 rgba(var(--tc-rgb), 0.6); }
  100% { opacity: 0; transform: scale(1.18); box-shadow: 0 0 26px 8px rgba(var(--tc-rgb), 0); }
}

/* ---- 一言だけの通知 -----------------------------------------------------
   足りない・引き換えた、の2つしか出ない。場所を固定しておかないと
   一覧が上下にずれて押し間違いのもとになるので、高さは常に確保する。 */
.shop-toast {
  min-height: 16px;
  margin-top: 8px;
  font-size: 11px;
  font-weight: 700;
  line-height: 16px;
  opacity: 0;
  transition: opacity 0.18s ease;
}
.shop-toast.show { opacity: 1; }
.shop-toast.good { color: var(--tc); }
.shop-toast.warn { color: #ffb45a; }

@media (prefers-reduced-motion: reduce) {
  .shop-swatch.is-prism { animation: none; }
  .shop-page.slide-in { animation: none; }
  .shop-tab:active { transform: none; }
  .shop-card.just-got::after { animation: none; opacity: 0; }
  .shop-card:active { transform: none; }
}
/* === SHOP : CSS ここまで ================================================= */

/* =========================================================================
   === PLL 2側面判断トレーナー : CSS ここから =============================
   既存のダークUI（#08080b の背景 / #1c1c22 のパネル / #3a3a48 の枠 /
   アクセントは var(--tc)）にそろえてある。

   ▼ 速さのための決めごと
     ・画面(設定/実技/リザルト)は data-view の出し分けだけ。DOM は作り直さない
     ・出題ごとに変わるのは側面上段6マスの background だけ。レイアウトは動かない
     ・正誤フラッシュとバナーは opacity / transform のみ = 合成だけで描ける
   ========================================================================= */

#pllt-overlay {
  display: none;
  position: fixed;
  inset: 0;
  background: #08080b;
  /* 確認ダイアログ(#confirm-overlay, z-index:80)より下、
     ほかのオーバーレイ(30)より上。 */
  z-index: 42;
  color: #e6e6ee;
  /* ドリル中に長押しで選択メニューが出ると連打の邪魔になる。 */
  -webkit-user-select: none;
  user-select: none;
  -webkit-tap-highlight-color: transparent;
  touch-action: manipulation;
}
#pllt-overlay.show { display: block; }

.pllt-view { display: none; }
#pllt-overlay[data-view="setup"]  .pllt-setup,
#pllt-overlay[data-view="stats"]  .pllt-stats,
#pllt-overlay[data-view="result"] .pllt-result { display: flex; }
#pllt-overlay[data-view="drill"]  .pllt-drill  { display: flex; }

/* ---- 設定 / リザルトの共通パネル ------------------------------------- */
.pllt-setup, .pllt-result, .pllt-stats {
  position: absolute;
  inset: 0;
  align-items: center;
  justify-content: center;
  padding: 12px;
}
.pllt-panel {
  position: relative;
  background: #1c1c22;
  border: 1px solid #3a3a48;
  border-radius: 14px;
  width: min(94vw, 460px);
  max-height: 92vh;
  max-height: 92dvh;
  display: flex;
  flex-direction: column;
  overflow: hidden;
}
.pllt-panel-scroll {
  flex: 1;
  min-height: 0;
  overflow-y: auto;
  -webkit-overflow-scrolling: touch;
  overscroll-behavior: contain;
  padding: 18px;
  text-align: center;
}
.pllt-panel h2 {
  font-size: 17px;
  margin: 0 0 4px;
  letter-spacing: .02em;
}
.pllt-lead {
  font-size: 11px;
  color: #8b8b9c;
  margin: 0 0 16px;
}

/* ---- 設定の各項目 ----------------------------------------------------- */
/* 計測の止め方は1つだけなので、設定では選ばせず一言で伝える。 */
.pllt-rule {
  font-size: 11px;
  font-weight: 600;
  color: rgba(var(--tc-rgb), .72);
  background: rgba(var(--tc-rgb), .07);
  border: 1px solid rgba(var(--tc-rgb), .22);
  border-radius: 9px;
  padding: 8px 10px;
  margin: 0 0 16px;
  line-height: 1.5;
}
.pllt-field { margin-bottom: 16px; text-align: left; }
.pllt-field-label {
  display: block;
  font-size: 11px;
  font-weight: 700;
  color: #9a9aac;
  margin-bottom: 6px;
  letter-spacing: .04em;
}
/* 問題数は8つあるので、横1列ではなく4列×2段。 */
.pllt-tabs {
  display: grid;
  grid-template-columns: repeat(4, 1fr);
  gap: 6px;
}
.pllt-modes { display: flex; gap: 6px; }
.pllt-tab, .pllt-mode {
  flex: 1 1 0;
  min-width: 0;
  padding: 10px 4px;
  border-radius: 9px;
  background: #23232c;
  border: 1px solid #3a3a48;
  color: #b8b8c4;
  font-size: 13px;
  font-weight: 800;
  cursor: pointer;
  touch-action: manipulation;
  transition: color .12s, border-color .12s, background .12s;
}
.pllt-tab.on, .pllt-mode.on {
  color: var(--tc);
  border-color: var(--tc);
  background: rgba(var(--tc-rgb), 0.12);
}
.pllt-mode { font-size: 12px; }

.pllt-swatches { display: flex; gap: 8px; }
.pllt-swatch {
  flex: 1 1 0;
  height: 38px;
  border-radius: 9px;
  border: 2px solid #3a3a48;
  cursor: pointer;
  touch-action: manipulation;
  position: relative;
  transition: transform .12s, border-color .12s, box-shadow .12s;
}
.pllt-swatch.on {
  border-color: var(--tc);
  box-shadow: 0 0 0 2px rgba(var(--tc-rgb), .35);
  transform: translateY(-2px);
}
/* 複数選べるので、選ばれていない色ははっきり沈ませる。
   選ばれている色にはチェックを重ねて、数がひと目で分かるようにする。 */
.pllt-swatch:not(.on) { opacity: .34; }
.pllt-swatch.on::after {
  content: "";
  position: absolute;
  left: 50%;
  top: 50%;
  width: 34%;
  height: 18%;
  margin: -14% 0 0 -17%;
  border-left: 3px solid rgba(0, 0, 0, .62);
  border-bottom: 3px solid rgba(0, 0, 0, .62);
  border-radius: 1px;
  transform: rotate(-45deg);
}
.pllt-hint {
  font-size: 11px;
  color: #78788a;
  margin: 7px 2px 0;
  line-height: 1.5;
  text-align: left;
}
.pllt-start {
  width: 100%;
  margin-top: 4px;
  padding: 15px;
  border-radius: 11px;
  border: 1px solid var(--tc);
  background: linear-gradient(180deg, rgba(var(--tc-rgb), .22), rgba(var(--tc-rgb), .08));
  color: var(--tc);
  font-size: 15px;
  font-weight: 900;
  letter-spacing: .06em;
  cursor: pointer;
  touch-action: manipulation;
}
.pllt-start:active {
  background: var(--tc);
  color: #04120f;
}
.pllt-sessions {
  font-size: 10px;
  color: #6d6d80;
  margin: 10px 0 0;
}

/* ---- 実技画面 --------------------------------------------------------- */
.pllt-drill {
  position: absolute;
  inset: 0;
  flex-direction: column;
  padding: 8px 8px calc(8px + env(safe-area-inset-bottom, 0px));
  gap: 8px;
}
.pllt-hud {
  flex: 0 0 auto;
  position: relative;
  display: flex;
  align-items: center;
  justify-content: space-around;
  gap: 6px;
  padding: 8px 44px 8px 8px;
  background: #14141a;
  border: 1px solid #2c2c38;
  border-radius: 11px;
}
.pllt-hud-item {
  display: flex;
  flex-direction: column;
  align-items: center;
  line-height: 1.15;
  min-width: 62px;
}
.pllt-hud-label {
  font-size: 9px;
  font-weight: 700;
  color: #71718a;
  letter-spacing: .06em;
}
.pllt-hud-item b {
  font-size: 15px;
  font-weight: 900;
  color: #e6e6ee;
  /* 数字の幅を固定して、1問ごとに桁が揺れないようにする。 */
  font-variant-numeric: tabular-nums;
  font-feature-settings: "tnum";
}
.pllt-hud-time b {
  color: var(--tc);
  min-width: 4.2ch;
  text-align: center;
}
.pllt-quit {
  position: absolute;
  top: 50%;
  right: 8px;
  transform: translateY(-50%);
  width: 32px;
  height: 32px;
  border-radius: 50%;
  border: 1px solid #3a3a48;
  background: rgba(255,255,255,.06);
  color: #c8c8d4;
  font-size: 16px;
  line-height: 1;
  cursor: pointer;
  touch-action: manipulation;
}
.pllt-quit:active { background: var(--tc); color: #04120f; }

/* ---- 3Dキューブ -------------------------------------------------------
   Three.js は使わない。ここで見えている必要があるのは U・F・R の
   27マスだけで、しかも1問で変わるのは6マス。CSS の preserve-3d で
   組めば、出題は background を6回書き換えるだけになり、WebGL の
   描画も本編のシーンへの干渉も起こらない。 */
.pllt-stage-wrap {
  flex: 1 1 auto;
  min-height: 130px;
  position: relative;
  display: flex;
  align-items: center;
  justify-content: center;
}
.pllt-stage {
  /* 立方体の一辺。縦にも横にも収まる範囲でいちばん大きく取る。 */
  --pllt-s: clamp(96px, min(44vw, 23vh), 190px);
  perspective: 900px;
  perspective-origin: 50% 42%;
  /* 傾けた立方体の外接箱は一辺の 1.4 倍弱。枠からはみ出さないよう
     親の大きさでも頭打ちにしておく。 */
  width: calc(var(--pllt-s) * 1.45);
  height: calc(var(--pllt-s) * 1.45);
  max-width: 100%;
  max-height: 100%;
  display: flex;
  align-items: center;
  justify-content: center;
}
.pllt-cube {
  position: relative;
  width: var(--pllt-s);
  height: var(--pllt-s);
  -webkit-transform-style: preserve-3d;
  transform-style: preserve-3d;
  /* 姿勢の式はここ1か所だけ。アングルの切り替えは --pllt-spin を
     差し替えるだけで済み、JS側は角度の計算を持たない。
     FR:-38 / FL:38 / BR:-142 / BL:142 と対称に取ってあるので、
     どのアングルでも2面の見え方の比率が同じになる。 */
  --pllt-spin: -38deg;
  transform: translateY(calc(var(--pllt-s) * 0.10)) rotateX(-26deg) rotateY(var(--pllt-spin));
}
/* 黒いフチ(枠と目地)はやめた。小さく描くと黒がステッカーを痩せさせて
   色が読みにくくなるため、マスどうしは隙間なく敷きつめ、白い細線だけで
   区切る。同じ色が隣り合っても線で分かれて見える。 */
.pllt-face {
  position: absolute;
  inset: 0;
  -webkit-backface-visibility: hidden;
  backface-visibility: hidden;
  display: grid;
  grid-template-columns: repeat(3, 1fr);
  grid-template-rows: repeat(3, 1fr);
  gap: 0;
  padding: 0;
  background: transparent;
  /* overflow と角丸はあえて付けない。3D変形された要素にクリップを
     かけると、ブラウザによっては3Dの文脈が平坦化され、面が本来の
     奥行きを失って重なる（Androidの Chrome で発生）。マスは隙間なく
     敷きつめてあるのではみ出しは起きず、外さないと困る理由もない。 */
}
/* 見える2面はJSが data-angle で名指しする。backface-visibility だけに
   頼ると、裏向きの面が消えないブラウザで手前に突き抜けてしまう。 */
#pllt-overlay[data-angle="FR"] .pllt-face-l,
#pllt-overlay[data-angle="FL"] .pllt-face-r { visibility: hidden; }
/* 正準的な CSS キューブの置き方。translateZ で半辺ぶん押し出す。
   側面は見える可能性のある3つを置き、裏を向いたものは
   backface-visibility で消す。
   これで「見える2面だけが出る」ことをCSSが保証してくれるので、
   JS側は毎問4面ぶん塗るだけでよく、どれが見えているかを判定しない。 */
.pllt-face-f { transform: translateZ(calc(var(--pllt-s) / 2)); }
.pllt-face-u { transform: rotateX(90deg) translateZ(calc(var(--pllt-s) / 2)); }
.pllt-face-r { transform: rotateY(90deg) translateZ(calc(var(--pllt-s) / 2)); }
.pllt-face-l { transform: rotateY(-90deg) translateZ(calc(var(--pllt-s) / 2)); }
.pllt-sticker {
  display: block;
  background: #33333f;
  /* 区切りは白い細線1本だけ。面のフチにも同じ線が回るので、
     立方体の輪郭もこれで出る。 */
  box-shadow: inset 0 0 0 1.5px rgba(255,255,255,.62);
}
/* 立体感は「上から光が当たっている」ぶんの明るさだけで出す。
   影(黒)を重ねると、そのぶん色が沈んで判断しにくくなる。
   左右どちらに出るかはアングルで入れ替わるので、側面には左右差を
   つけず、上からの光だけで奥行きを出している。 */
.pllt-face::after {
  content: "";
  position: absolute;
  inset: 0;
  pointer-events: none;
}
.pllt-face-u::after { background: rgba(255,255,255,.13); }
.pllt-face-f::after, .pllt-face-r::after, .pllt-face-l::after {
  background: linear-gradient(rgba(255,255,255,.06), rgba(255,255,255,0));
}

/* ---- 不正解のとき出る「正解は◯◯」 ----------------------------------- */
.pllt-banner {
  position: absolute;
  left: 50%;
  bottom: 4px;
  transform: translate(-50%, 8px);
  padding: 8px 16px;
  border-radius: 999px;
  background: rgba(214, 58, 74, .16);
  border: 1px solid rgba(255, 106, 122, .55);
  color: #ff9aa6;
  font-size: 13px;
  font-weight: 800;
  white-space: nowrap;
  opacity: 0;
  pointer-events: none;
  transition: opacity .12s ease, transform .12s ease;
}
.pllt-banner.show { opacity: 1; transform: translate(-50%, 0); }

/* ---- 回答ボタン -------------------------------------------------------
   名前が分かっていても、21個を順番に並べると毎回「目で探す」時間が乗る。
   角と辺の入れ替わりかた(CPの種類)で4つに割り、色も置き場所も固定した。
   上級者が頭の中でPLLをしまっている区分けと同じなので、探さずに手が
   伸びる。全体は5行（以前は7行）に収まり、そのぶんボタンを大きく取れる。 */
.pllt-answers {
  flex: 0 0 auto;
  display: flex;
  flex-direction: column;
  gap: 7px;
}
.pllt-group { display: flex; flex-direction: column; gap: 3px; }
.pllt-group.g1 { --g: 255, 200, 97; }   /* 角のみ   */
.pllt-group.g2 { --g: 111, 216, 255; }  /* 辺のみ   */
.pllt-group.g3 { --g: 126, 224, 143; }  /* 隣接交換 */
.pllt-group.g4 { --g: 199, 155, 255; }  /* 対角交換 */
.pllt-group-label {
  font-size: 9px;
  font-weight: 800;
  letter-spacing: .08em;
  padding-left: 3px;
  color: rgba(var(--g), .78);
}
.pllt-group-row {
  display: grid;
  grid-auto-rows: minmax(44px, auto);
  gap: 5px;
}
/* グリッドの子は既定で内容幅より小さくならない。これを外さないと、
   横に詰めたときに最後のボタンが枠から押し出される。 */
.pllt-group-row > .pllt-ans { min-width: 0; }
.pllt-ans {
  border-radius: 10px;
  background: rgba(var(--g), .08);
  border: 1px solid rgba(var(--g), .34);
  color: #e9e9f1;
  font-size: 17px;
  font-weight: 800;
  letter-spacing: .02em;
  cursor: pointer;
  touch-action: manipulation;
  /* 押した瞬間の反応は transform と色だけ = 再レイアウトが起きない。 */
  transition: transform .06s ease, background .06s ease;
}
.pllt-ans:active {
  transform: scale(.93);
  background: rgba(var(--g), .30);
  border-color: rgb(var(--g));
  color: #fff;
}
.pllt-answers.locked .pllt-ans { pointer-events: none; opacity: .4; }
.pllt-answers.locked .pllt-ans.reveal {
  opacity: 1;
  border-color: #4fe0a8;
  color: #4fe0a8;
  background: rgba(79, 224, 168, .16);
}
/* ---- 大きな画面（タブレットなど）---------------------------------------
   幅いっぱいに引き伸ばすと、ボタンが横に痩せるうえ、グリッドの子は
   既定で内容より小さくならないので最後のグループが画面外へはみ出す。
   ①全体を中央に寄せて幅の上限を決める ②グリッドの子に min-width:0 を
   入れてはみ出しを止める ③空いた縦の余白はキューブを大きくして埋める。 */
@media (min-width: 640px) {
  .pllt-drill { padding: 14px 14px calc(14px + env(safe-area-inset-bottom, 0px)); gap: 12px; }
  .pllt-hud,
  .pllt-answers { width: 100%; max-width: 660px; margin: 0 auto; }
  .pllt-stage { --pllt-s: clamp(140px, min(38vw, 27vh), 300px); }
  .pllt-answers { gap: 10px; }
  .pllt-group-row { grid-auto-rows: minmax(54px, auto); gap: 8px; }
  .pllt-group-label { font-size: 11px; }
  .pllt-ans { font-size: 20px; border-radius: 12px; }
  .pllt-hud { padding: 10px 48px 10px 10px; }
  .pllt-hud-item b { font-size: 18px; }
  .pllt-hud-label { font-size: 10px; }
}

/* ---- 正誤フラッシュ ---------------------------------------------------
   opacity だけを動かすので、次の問題の描画をいっさい邪魔しない。 */
.pllt-flash {
  position: absolute;
  inset: 0;
  pointer-events: none;
  opacity: 0;
  z-index: 5;
}
.pllt-flash.ok {
  background: radial-gradient(ellipse at center, rgba(79,224,168,.34), rgba(79,224,168,.05) 70%);
  animation: pllt-flash 260ms ease-out;
}
.pllt-flash.ng {
  background: radial-gradient(ellipse at center, rgba(255,86,104,.38), rgba(255,86,104,.06) 70%);
  animation: pllt-flash 260ms ease-out;
}
@keyframes pllt-flash {
  0%   { opacity: 1; }
  100% { opacity: 0; }
}

/* ---- リザルト --------------------------------------------------------- */
.pllt-summary {
  display: grid;
  grid-template-columns: repeat(3, 1fr);
  gap: 8px;
  margin: 12px 0 18px;
}
.pllt-sum-cell {
  background: #15151b;
  border: 1px solid #2c2c38;
  border-radius: 10px;
  padding: 10px 4px;
}
.pllt-sum-label {
  display: block;
  font-size: 9px;
  font-weight: 700;
  color: #71718a;
  letter-spacing: .04em;
  margin-bottom: 3px;
}
.pllt-sum-cell b {
  font-size: 18px;
  font-weight: 900;
  color: var(--tc);
  font-variant-numeric: tabular-nums;
}
.pllt-sum-cell b i {
  font-style: normal;
  font-size: 10px;
  color: #8b8b9c;
  margin-left: 2px;
}
.pllt-res-title {
  font-size: 12px;
  font-weight: 700;
  color: #9a9aac;
  text-align: left;
  margin: 0 0 6px;
}
.pllt-res-head, .pllt-res-row {
  display: grid;
  grid-template-columns: 1.6fr .7fr 1fr 1fr;
  align-items: center;
  gap: 4px;
  padding: 7px 8px;
}
.pllt-res-head {
  font-size: 9px;
  font-weight: 700;
  color: #71718a;
  letter-spacing: .04em;
  border-bottom: 1px solid #2c2c38;
}
.pllt-res-row {
  font-size: 12px;
  border-bottom: 1px solid #23232c;
  font-variant-numeric: tabular-nums;
}
.pllt-res-row.weak { background: rgba(255, 106, 122, .07); }
.pllt-res-cell { text-align: right; color: #c8c8d4; }
.pllt-res-cell.name {
  text-align: left;
  font-weight: 800;
  color: #e6e6ee;
  display: flex;
  align-items: center;
  gap: 5px;
}
.pllt-weak-tag {
  font-size: 8.5px;
  font-weight: 800;
  padding: 2px 5px;
  border-radius: 5px;
  background: rgba(255, 106, 122, .18);
  border: 1px solid rgba(255, 106, 122, .45);
  color: #ff9aa6;
  white-space: nowrap;
}
.pllt-res-rows { max-height: 38vh; overflow-y: auto; overscroll-behavior: contain; }
.pllt-result-actions {
  display: flex;
  flex-wrap: wrap;
  gap: 8px;
  justify-content: center;
  margin-top: 16px;
}
.pllt-result-actions .action-btn { padding: 12px 14px; font-size: 12px; }
.pllt-result-actions .action-btn.disabled {
  opacity: .38;
  pointer-events: none;
}

/* ---- 通算成績 ---------------------------------------------------------
   設定画面の左上には通算正答率だけを出す（✕の対角）。押すと21種の
   内訳が開く。数字だけを縦に並べても比べにくいので、帯の長さと色でも
   同じことを言う。色は 90%以上=緑 / 75%以上=琥珀 / それ未満=赤。 */
.pllt-stats-btn {
  position: absolute;
  top: 10px;
  left: 10px;
  z-index: 5;
  display: flex;
  align-items: center;
  gap: 4px;
  height: 34px;
  padding: 0 11px;
  border-radius: 999px;
  border: 1px solid #3a3a48;
  background: rgba(255, 255, 255, .06);
  color: #c8c8d4;
  font-size: 12px;
  font-weight: 800;
  font-variant-numeric: tabular-nums;
  cursor: pointer;
  touch-action: manipulation;
}
.pllt-stats-btn:active { border-color: var(--tc); background: rgba(var(--tc-rgb), .16); }
.pllt-stats-btn-ico { font-size: 12px; line-height: 1; }

.pllt-st-group { margin-bottom: 12px; }
.pllt-st-group.g1 { --g: 255, 200, 97; }
.pllt-st-group.g2 { --g: 111, 216, 255; }
.pllt-st-group.g3 { --g: 126, 224, 143; }
.pllt-st-group.g4 { --g: 199, 155, 255; }
.pllt-st-group .pllt-group-label {
  display: block;
  text-align: left;
  margin-bottom: 4px;
}
.pllt-st-row {
  display: grid;
  grid-template-columns: 2.4rem 1fr 2.9rem 2.2rem 2.8rem;
  align-items: center;
  gap: 6px;
  padding: 5px 2px;
  font-size: 11px;
  font-variant-numeric: tabular-nums;
  border-bottom: 1px solid #20202a;
}
.pllt-st-row.none { opacity: .42; }
.pllt-st-name {
  text-align: left;
  font-size: 12px;
  font-weight: 800;
  color: #e6e6ee;
}
.pllt-st-bar {
  height: 6px;
  border-radius: 999px;
  background: #23232c;
  overflow: hidden;
}
.pllt-st-fill {
  display: block;
  height: 100%;
  width: 0;
  border-radius: 999px;
  background: #3a3a48;
  transition: width .2s ease;
}
.pllt-st-row .pllt-st-pct { text-align: right; font-weight: 800; color: #9a9aac; }
.pllt-st-row .pllt-st-n,
.pllt-st-row .pllt-st-avg { text-align: right; color: #8b8b9c; }
.pllt-st-empty { text-align: center; }

/* ---- 記録の消去スライダー ---------------------------------------------
   消したら戻せない操作なので、指を横に長く動かしきったときだけ効く。
   赤い帯は「これは破壊的な操作だ」という合図。動くのは transform と
   width と opacity だけなので、指に貼りついたまま滑る。 */
.pllt-reset-slider {
  position: relative;
  display: flex;
  align-items: center;
  justify-content: center;
  width: 100%;
  height: 52px;
  margin-top: 18px;
  padding: 4px;
  border-radius: 999px;
  border: 1px solid rgba(255, 106, 122, .42);
  background: rgba(255, 60, 80, .09);
  overflow: hidden;
  cursor: grab;
  /* 横に引く操作なので、パネルのスクロールに取られないようにする。 */
  touch-action: none;
  -webkit-user-select: none;
  user-select: none;
  --p: 0;
}
.pllt-reset-slider.dragging { cursor: grabbing; }
.pllt-reset-slider.disabled { opacity: .35; pointer-events: none; }
.pllt-reset-fill {
  position: absolute;
  left: 0;
  top: 0;
  bottom: 0;
  width: 0;
  border-radius: 999px;
  background: linear-gradient(90deg, rgba(214, 58, 74, .30), rgba(255, 86, 104, .55));
}
.pllt-reset-label {
  position: relative;
  z-index: 1;
  font-size: 12px;
  font-weight: 800;
  letter-spacing: .04em;
  color: #ff9aa6;
  pointer-events: none;
  /* 滑らせるほど薄くなる。進み具合が文字でも伝わる。 */
  opacity: calc(1 - var(--p) * 0.9);
}
.pllt-reset-thumb {
  position: absolute;
  left: 4px;
  top: 50%;
  z-index: 2;
  display: flex;
  align-items: center;
  justify-content: center;
  width: 44px;
  height: 44px;
  margin-top: -22px;
  border-radius: 50%;
  background: linear-gradient(180deg, #ff6a7a, #d63a4a);
  box-shadow: 0 2px 8px rgba(214, 58, 74, .45);
  font-size: 18px;
  line-height: 1;
  pointer-events: none;
}
/* 消しきったら緑にして「終わった」ことを見せる。 */
.pllt-reset-slider.done { border-color: rgba(79, 224, 168, .55); }
.pllt-reset-slider.done .pllt-reset-fill {
  background: linear-gradient(90deg, rgba(79, 224, 168, .28), rgba(79, 224, 168, .5));
}
.pllt-reset-slider.done .pllt-reset-label { opacity: 1; color: #4fe0a8; }
.pllt-reset-slider.done .pllt-reset-thumb {
  background: linear-gradient(180deg, #6ef0bd, #2fb98a);
  box-shadow: 0 2px 8px rgba(79, 224, 168, .45);
}


/* ---- チュートリアル本文の中のトレーニングアイコン ----------------------
   本文は innerHTML なので、SVGを直に書くとドロワー側のアイコンと
   グラデーションのidがぶつかる。data URI にすればSVG内のidはその画像の
   中だけで閉じるので、同じ絵を何個置いても干渉しない。 */
.tut-cube-icon {
  display: inline-block;
  width: 1.15em;
  height: 1.15em;
  vertical-align: -0.22em;
  background: url("data:image/svg+xml,%3Csvg%20xmlns=%22http://www.w3.org/2000/svg%22%20viewBox=%220%200%2026%2026%22%20aria-hidden=%22true%22%20focusable=%22false%22%3E%20%3Cdefs%3E%20%3CradialGradient%20id=%22plltIcoGlow%22%20cx=%2250%25%22%20cy=%2252%25%22%20r=%2250%25%22%3E%20%3Cstop%20offset=%220%25%22%20stop-color=%22%23ff2d45%22%20stop-opacity=%22.55%22/%3E%20%3Cstop%20offset=%2255%25%22%20stop-color=%22%23ff2d45%22%20stop-opacity=%22.18%22/%3E%20%3Cstop%20offset=%22100%25%22%20stop-color=%22%23ff2d45%22%20stop-opacity=%220%22/%3E%20%3C/radialGradient%3E%20%3ClinearGradient%20id=%22plltIcoTop%22%20x1=%220%22%20y1=%220%22%20x2=%22.3%22%20y2=%221%22%3E%20%3Cstop%20offset=%220%25%22%20stop-color=%22%23ff8a92%22/%3E%20%3Cstop%20offset=%2255%25%22%20stop-color=%22%23ff4257%22/%3E%20%3Cstop%20offset=%22100%25%22%20stop-color=%22%23ec2338%22/%3E%20%3C/linearGradient%3E%20%3ClinearGradient%20id=%22plltIcoFront%22%20x1=%220%22%20y1=%220%22%20x2=%22.2%22%20y2=%221%22%3E%20%3Cstop%20offset=%220%25%22%20stop-color=%22%23e01f35%22/%3E%20%3Cstop%20offset=%22100%25%22%20stop-color=%22%23a50f22%22/%3E%20%3C/linearGradient%3E%20%3ClinearGradient%20id=%22plltIcoRight%22%20x1=%220%22%20y1=%220%22%20x2=%22.4%22%20y2=%221%22%3E%20%3Cstop%20offset=%220%25%22%20stop-color=%22%23b31324%22/%3E%20%3Cstop%20offset=%22100%25%22%20stop-color=%22%237d0817%22/%3E%20%3C/linearGradient%3E%20%3C/defs%3E%20%3Cellipse%20cx=%2213%22%20cy=%2213.6%22%20rx=%2213%22%20ry=%2212.4%22%20fill=%22url(%23plltIcoGlow)%22/%3E%20%3Cpolygon%20points=%2213,13.4%2023.2,8.3%2023.2,17%2013,22.1%22%20fill=%22url(%23plltIcoRight)%22/%3E%20%3Cpolygon%20points=%222.8,8.3%2013,13.4%2013,22.1%202.8,17%22%20fill=%22url(%23plltIcoFront)%22/%3E%20%3Cpolygon%20points=%2213,3.2%2023.2,8.3%2013,13.4%202.8,8.3%22%20fill=%22url(%23plltIcoTop)%22/%3E%20%3Cpolygon%20points=%2213,3.2%2017.59,5.5%208.41,5.5%22%20fill=%22%23fff%22%20opacity=%22.34%22/%3E%20%3Cg%20fill=%22none%22%20stroke=%22%234a0410%22%20stroke-width=%22.62%22%20stroke-linecap=%22round%22%20opacity=%22.78%22%3E%20%3Cpath%20d=%22M16.4%204.9L6.2%2010M9.6%204.9L19.8%2010M19.8%206.6L9.6%2011.7M6.2%206.6L16.4%2011.7M6.2%2010L6.2%2018.7M2.8%2011.2L13%2016.3M9.6%2011.7L9.6%2020.4M2.8%2014.1L13%2019.2M16.4%2011.7L16.4%2020.4M13%2016.3L23.2%2011.2M19.8%2010L19.8%2018.7M13%2019.2L23.2%2014.1%22/%3E%20%3C/g%3E%20%3Cpath%20d=%22M2.8%208.3L13%203.2L23.2%208.3L23.2%2017L13%2022.1L2.8%2017Z%22%20fill=%22none%22%20stroke=%22%234a0410%22%20stroke-width=%22.7%22%20stroke-linejoin=%22round%22%20opacity=%22.8%22/%3E%20%3Cpath%20d=%22M13%2013.4L13%2022.1%22%20fill=%22none%22%20stroke=%22%234a0410%22%20stroke-width=%22.62%22%20opacity=%22.78%22/%3E%20%3Cpath%20d=%22M21.4%203.6c.28%201.5.6%201.82%202.1%202.1-1.5.28-1.82.6-2.1%202.1-.28-1.5-.6-1.82-2.1-2.1%201.5-.28%201.82-.6%202.1-2.1Z%22%20fill=%22%23fff%22%20opacity=%22.92%22/%3E%20%3Cpath%20d=%22M5%206.2c.16.86.34%201.04%201.2%201.2-.86.16-1.04.34-1.2%201.2-.16-.86-.34-1.04-1.2-1.2.86-.16%201.04-.34%201.2-1.2Z%22%20fill=%22%23fff%22%20opacity=%22.6%22/%3E%20%3C/svg%3E") center / contain no-repeat;
  filter: drop-shadow(0 0 2px rgba(255, 45, 69, .5));
}

/* ---- ドロワーの「トレーニング」のアイコン ------------------------------
   絵文字ではなく、赤いルービックキューブを等角投影で描いたSVG。
   他の行（🏆/🛍️/⚙️）は絵文字1文字＋半角スペースなので、
   アイコン幅と gap をそれに合わせて字下げ位置を揃える。
   光り方は、SVG内の赤いグローに加えて外側にも drop-shadow を重ね、
   ゆっくり明滅させている。動かすのは filter と opacity だけ。 */
#training-btn { gap: 6px; }
.menu-cube-icon {
  width: 22px;
  height: 22px;
  flex: 0 0 auto;
  display: block;
  filter: drop-shadow(0 0 3px rgba(255, 45, 69, .55));
  animation: pllt-icon-shine 2.8s ease-in-out infinite;
}
@keyframes pllt-icon-shine {
  0%, 100% { filter: drop-shadow(0 0 2px rgba(255, 45, 69, .40)); }
  50%      { filter: drop-shadow(0 0 6px rgba(255, 45, 69, .80)); }
}

/* ---- PLL一覧からトレーニングへ入る導線 -------------------------------- */
.pllt-open-btn {
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 8px;
  width: 100%;
  margin: 0 0 12px;
  padding: 12px 10px;
  border-radius: 11px;
  border: 1px solid var(--tc);
  background: linear-gradient(180deg, rgba(var(--tc-rgb), .18), rgba(var(--tc-rgb), .05));
  color: var(--tc);
  font-weight: 800;
  cursor: pointer;
  touch-action: manipulation;
}
.pllt-open-btn:active { background: var(--tc); color: #04120f; }
.pllt-open-btn .pllt-open-name { font-size: 13px; }
.pllt-open-btn .pllt-open-sub {
  font-size: 10px;
  font-weight: 600;
  opacity: .75;
}

/* 横に広くて縦が足りないとき（タブレットの横向きなど）は、キューブと
   回答を左右に分ける。縦に積むとキューブが潰れるか、ボタンが画面外に
   出てしまうため。 */
@media (min-width: 900px) and (min-aspect-ratio: 1/1) {
  .pllt-drill {
    display: grid;
    grid-template-columns: 1fr 1fr;
    grid-template-rows: auto 1fr;
    align-items: center;
    column-gap: 20px;
  }
  .pllt-hud { grid-column: 1 / -1; max-width: none; }
  .pllt-stage-wrap { grid-column: 1; min-height: 0; }
  .pllt-answers { grid-column: 2; max-width: 520px; }
  .pllt-stage { --pllt-s: clamp(140px, min(26vw, 40vh), 320px); }
}

/* 縦の狭い端末では、回答ボタンを詰めてキューブの居場所を確保する。 */
@media (max-height: 640px) {
  .pllt-answers { gap: 5px; }
  .pllt-group-row { grid-auto-rows: minmax(38px, auto); }
  .pllt-group-label { font-size: 8px; }
  .pllt-ans { font-size: 15px; }
  .pllt-stage-wrap { min-height: 100px; }
  .pllt-hud-item b { font-size: 14px; }
}

/* アングルのチップ。FR/FL/BR/BL の4つを等幅で並べる。 */
.pllt-angle { font-size: 12px; letter-spacing: .06em; }

/* ---- 「分かったらタップ」の案内と、止まったことの合図 ----------------
   計測を止める操作なので、押せる場所であることと、止まったことの両方が
   ひと目で分かる必要がある。止まった合図はタイマーの色と、キューブが
   ❓に変わることの2つ。どちらも合成だけで描ける。 */
.pllt-stage-wrap { cursor: pointer; }
.pllt-peek-hint {
  position: absolute;
  left: 50%;
  top: 2px;
  transform: translateX(-50%);
  padding: 4px 12px;
  border-radius: 999px;
  border: 1px solid rgba(var(--tc-rgb), .32);
  background: rgba(var(--tc-rgb), .07);
  font-size: 10px;
  font-weight: 700;
  letter-spacing: .06em;
  color: rgba(var(--tc-rgb), .8);
  pointer-events: none;
}
/* 叩いたあと（❓）と、カウントダウン中は引っ込める。 */
#pllt-overlay.peeked .pllt-peek-hint,
#pllt-overlay.counting .pllt-peek-hint { visibility: hidden; }
/* 計測が止まったことがひと目で分かるよう、タイマーを止め色にする。 */
#pllt-overlay.peeked .pllt-hud-time b { color: #4fe0a8; }
#pllt-overlay.peeked .pllt-hud-time .pllt-hud-label { color: #4fe0a8; opacity: .7; }
/* ---- キューブを隠す2つの場面 ------------------------------------------
   ① スタート直後のカウントダウン中（まだ問題は出ていない）
   ② 「瞬間」モードで叩いたあと（見返せないようにする）
   どちらも同じ仕掛けで、キューブを消してカバーの文字だけを見せる。
   opacity と transform しか動かさないので描画は軽い。 */
.pllt-cover {
  position: absolute;
  inset: 0;
  display: none;
  align-items: center;
  justify-content: center;
  /* 不正解の「正解は◯◯」を読み飛ばすタップを塞がないよう、指は通す。 */
  pointer-events: none;
  z-index: 2;
}
#pllt-overlay.counting .pllt-cover,
#pllt-overlay.peeked   .pllt-cover { display: flex; }
#pllt-overlay.counting .pllt-stage,
#pllt-overlay.peeked   .pllt-stage {
  opacity: 0;
  transform: scale(.94);
  transition: opacity .12s ease, transform .12s ease;
}
.pllt-stage { transition: opacity .12s ease, transform .12s ease; }
.pllt-cover-mark {
  font-size: clamp(48px, 16vw, 88px);
  font-weight: 900;
  line-height: 1;
  color: var(--tc);
  font-variant-numeric: tabular-nums;
}
#pllt-overlay.peeked .pllt-cover-mark {
  /* ❓ は絵文字なので色は乗らない。大きさだけ少し抑える。 */
  font-size: clamp(40px, 13vw, 72px);
  opacity: .85;
}
/* 数字が1つ進むたびに、大きめから素早く落ち着く。 */
.pllt-cover-mark.tick { animation: pllt-tick 380ms cubic-bezier(.2, .9, .3, 1); }
@keyframes pllt-tick {
  0%   { transform: scale(1.5); opacity: 0; }
  35%  { transform: scale(1);   opacity: 1; }
  100% { transform: scale(1);   opacity: 1; }
}

/* 動きを減らす設定の端末では、フラッシュを控えめな一瞬に。 */
@media (prefers-reduced-motion: reduce) {
  .pllt-flash.ok, .pllt-flash.ng { animation-duration: 120ms; }
  .pllt-ans:active { transform: none; }
  .pllt-cover-mark.tick { animation: none; }
  .menu-cube-icon { animation: none; }
}
/* === PLL 2側面判断トレーナー : CSS ここまで ============================= */
