/* 鍵盤奇幻島 —— 版面與動畫
   設計重點：
   1. 世界佔上半，螢幕鍵盤佔下半，兩者永遠同時看得到（孩子要能對照）
   2. 鍵盤用 CSS grid，任何螢幕寬度都不會被裁切
   3. 動畫都在 200–600ms，慢到幼兒看得懂，快到不覺得卡
*/

:root {
  --realm-c1: #a8e6a3;
  --realm-c2: #4a9d5f;
  --ink: #2f2545;
  --paper: #fffdf7;
  --key-h: clamp(44px, 7.2vh, 66px);
  --gap: clamp(3px, 0.5vh, 7px);
  --radius: 14px;
  --font: "Noto Sans TC", "PingFang TC", "Microsoft JhengHei", "Hiragino Sans",
    system-ui, -apple-system, sans-serif;
}

* { box-sizing: border-box; }

html, body {
  margin: 0;
  padding: 0;
  height: 100%;
  overflow: hidden;
  font-family: var(--font);
  background: #1d1730;
  color: var(--ink);
  /* 幼兒會拖來拖去，關掉選字與長按選單 */
  -webkit-user-select: none;
  user-select: none;
  -webkit-tap-highlight-color: transparent;
  touch-action: manipulation;
  overscroll-behavior: none;
}

.app {
  display: flex;
  flex-direction: column;
  height: 100%;
  height: 100dvh;
}

/* ── 上方資訊列 ─────────────────────────────────── */
.hud {
  flex: 0 0 auto;
  display: flex;
  align-items: center;
  gap: 10px;
  padding: 6px 12px;
  min-height: 50px;
  background: linear-gradient(180deg, rgba(255, 255, 255, .96), rgba(255, 255, 255, .86));
  border-bottom: 3px solid rgba(120, 90, 180, .18);
  box-shadow: 0 3px 14px rgba(60, 40, 100, .18);
  z-index: 5;
}

.hud-spacer { flex: 1; }

/* 量出來原本是 55×43.6px —— 高度比 Apple 的 44pt 少 0.4px、比 Material 的
   48dp 少 4.4px，在**每一種**平板上都不合格。這裡不再讓內距去決定尺寸，
   直接把最小值寫死；🏠 回家與 🔊 靜音是孩子自己會按的東西。 */
.hud-btn {
  border: none;
  background: rgba(140, 110, 200, .12);
  border-radius: 12px;
  padding: 7px 12px;
  min-width: 48px;
  min-height: 48px;
  font: 700 1.15rem var(--font);
  line-height: 1;
  cursor: pointer;
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 5px;
  transition: transform .14s, background .2s;
}
.hud-btn:hover { background: rgba(140, 110, 200, .22); }
.hud-btn:active { transform: scale(.92); }
.hud-mute.off { opacity: .55; }

.hud-gear { position: relative; overflow: hidden; }
.hud-gear.holding::after {
  content: "";
  position: absolute;
  inset: 0;
  background: rgba(120, 90, 200, .35);
  animation: fillHold 3s linear forwards;
  transform-origin: left;
}
@keyframes fillHold { from { transform: scaleX(0); } to { transform: scaleX(1); } }

.hud-realm, .hud-env {
  display: flex;
  align-items: center;
  gap: 7px;
  padding: 6px 14px;
  border-radius: 999px;
  font-weight: 800;
  font-size: clamp(.85rem, 1.5vw, 1.05rem);
  white-space: nowrap;
}
.hud-realm {
  background: linear-gradient(135deg, var(--realm-c1), var(--realm-c2));
  color: #fff;
  text-shadow: 0 1px 3px rgba(0, 0, 0, .3);
}
.hud-env {
  background: rgba(255, 214, 102, .3);
  border: 2px solid rgba(230, 170, 40, .45);
}
.hud-realm-emoji, .hud-env-emoji { font-size: 1.35em; }

.hud-env.pulse { animation: envPulse .7s cubic-bezier(.34, 1.56, .64, 1); }
@keyframes envPulse {
  0% { transform: scale(1); }
  35% { transform: scale(1.22) rotate(-3deg); background: rgba(255, 214, 102, .75); }
  100% { transform: scale(1); }
}

.hud-stickers { font-weight: 900; }
.hud-sticker-icon { font-size: 1.3em; }
.hud-stickers.pop { animation: stickerPop .55s cubic-bezier(.34, 1.56, .64, 1); }
@keyframes stickerPop {
  0% { transform: scale(1); }
  40% { transform: scale(1.35) rotate(8deg); }
  100% { transform: scale(1); }
}

/* ── 世界 ───────────────────────────────────────── */
.stage {
  flex: 1 1 auto;
  position: relative;
  min-height: 0;
  background: #87ceeb;
}
#world {
  display: block;
  width: 100%;
  height: 100%;
}

/* ── 螢幕鍵盤 ───────────────────────────────────── */
.console {
  flex: 0 0 auto;
  /* 這一塊自己處理所有手勢，不要讓瀏覽器猜。
     沒有這一行的話 `touch-action: manipulation`（body 上那個）會在手指橫移時
     判定成平移手勢，**把指標事件串流切掉** —— 量出來：一次滑動有 7 個
     touchmove，卻只有 2 個 pointermove，於是「手指滑到隔壁鍵」永遠不會發生。
     鍵盤本來就不該捲動，這裡把決定權收回來。 */
  touch-action: none;
  padding: var(--gap) clamp(6px, 1.2vw, 18px) calc(var(--gap) + 2px);
  background: linear-gradient(180deg, #3a2f5c, #292048);
  border-top: 3px solid rgba(255, 255, 255, .12);
  box-shadow: 0 -4px 18px rgba(0, 0, 0, .28);
  z-index: 4;
}

.keyboard {
  display: flex;
  flex-direction: column;
  gap: var(--gap);
  max-width: 1180px;
  margin: 0 auto;
}

.kb-row {
  display: grid;
  grid-auto-flow: column;
  grid-auto-columns: 1fr;
  gap: var(--gap);
}
/* 第 3、4 排在真鍵盤上是往右偏的，這裡也偏一點，形狀才像 */
.kb-row:nth-child(3) { padding-left: 2.2%; padding-right: 2.2%; }
.kb-row:nth-child(4) { padding-left: 5.5%; padding-right: 5.5%; }

.kb-key {
  position: relative;
  height: var(--key-h);
  border: none;
  border-radius: var(--radius);
  /* 只用一層很淡的白做「鍵帽反光」。原本 .96/.78 幾乎把顏色洗白了，
     實測截圖裡每顆鍵都變成一樣的米白色 —— 對幼兒來說顏色就是分類線索，不能洗掉。*/
  background:
    linear-gradient(180deg, rgba(255, 255, 255, .55), rgba(255, 255, 255, .05) 55%,
      rgba(0, 0, 0, .05)),
    var(--tint, #dcd6f7);
  box-shadow:
    0 3px 0 rgba(0, 0, 0, .22),
    0 4px 10px rgba(0, 0, 0, .2),
    inset 0 1px 0 rgba(255, 255, 255, .9);
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: 0;
  padding: 2px 1px;
  overflow: hidden;
  cursor: pointer;
  font-family: var(--font);
  transition: transform .1s, box-shadow .1s, filter .2s;
}
.kb-key.empty {
  background: rgba(255, 255, 255, .11);
  box-shadow: inset 0 0 0 2px rgba(255, 255, 255, .1);
}
.kb-key.empty .kb-icon { color: rgba(255, 255, 255, .35); font-size: 1rem; }
.kb-key.dim { filter: saturate(.55) brightness(.92); }

.kb-cap {
  position: absolute;
  top: 2px;
  left: 5px;
  font-size: clamp(8px, 1vh, 11px);
  font-weight: 800;
  color: rgba(70, 50, 110, .5);
  letter-spacing: .02em;
  pointer-events: none;
}
.keyboard.hide-caps .kb-cap { display: none; }
.kb-key.empty .kb-cap { color: rgba(255, 255, 255, .38); }

.kb-glyph {
  font-size: clamp(15px, 2.9vh, 27px);
  font-weight: 900;
  line-height: 1.02;
  color: #35284f;
  letter-spacing: -.01em;
  max-width: 100%;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.kb-icon {
  font-size: clamp(15px, 2.7vh, 25px);
  line-height: 1.05;
}
/* 自己畫的圖案（木魚、三角鐵）跟 emoji 一樣大 —— 一起排才不會有一個特別大 */
.kb-icon img,
.announce-icon img,
.sticker-pop-icon img,
.sb-icon img {
  width: 1.15em;
  height: 1.15em;
  vertical-align: -0.16em;
  display: inline-block;
}
/* 同時有大字和圖時，圖縮到右下角當小提示 */
.kb-key.with-glyph .kb-icon {
  position: absolute;
  right: 3px;
  bottom: 2px;
  font-size: clamp(10px, 1.7vh, 15px);
  opacity: .95;
}
.kb-label {
  font-size: clamp(7px, 1.15vh, 11px);
  font-weight: 700;
  color: rgba(55, 40, 90, .82);
  line-height: 1.1;
  max-width: 96%;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
  margin-top: 1px;
}

/* 按下去：像真的鍵帽被壓下 */
.kb-key.hit, .kb-key.good, .kb-key.soft {
  animation: keyHit .26s ease-out;
}
.kb-key.good { animation: keyGood .42s cubic-bezier(.34, 1.56, .64, 1); }
@keyframes keyHit {
  0% { transform: translateY(0); }
  35% { transform: translateY(3px) scale(.96); box-shadow: 0 0 0 rgba(0, 0, 0, .2); }
  100% { transform: translateY(0); }
}
@keyframes keyGood {
  0% { transform: translateY(0) scale(1); }
  22% { transform: translateY(3px) scale(.94); }
  55% { transform: translateY(-4px) scale(1.1); filter: brightness(1.25); }
  100% { transform: translateY(0) scale(1); }
}

/* 提示：呼吸發光。這是「不會卡住」的保險機制。
 *
 * 發光畫在 ::after 這層獨立的元素上，**不是**畫在鍵本身。
 * 原因是實測出來的（回饋第 3 項：「按鈕提示時實際沒有發光提示」）：
 * `animation` 只有一個位置，而 .hit（按過就留著）與 .morph（換環境後留著）
 * 也在搶它，而且它們寫在後面，於是提示的發光被整個蓋掉 ——
 * 孩子按過那顆鍵一次之後，它就再也不會發光了。
 * 分成兩層之後，「按下去的動畫」與「提示的發光」各有自己的位置，不會互相搶。 */
.kb-key.hint {
  z-index: 2;
  /* 外圈光暈畫在**鍵本身**的 box-shadow 上。
     元素自己的 box-shadow 不會被自己的 overflow 裁掉，而 ::after 會 ——
     第一版把光圈畫在 `::after { inset: -4px }`，整圈都在框外面，
     於是被 .kb-key 的 overflow:hidden 切得一乾二淨：動畫在跑，但看不見。 */
  animation: keyHintGlow 1.1s ease-in-out infinite;
}
/* 內側的亮框。inset: 0 ＋ inset 陰影，完全在框內，所以不會被裁掉。
   兩層一起做是刻意的：就算某天外圈又被什麼規則蓋掉，孩子還看得到內框在閃。 */
.kb-key.hint::after {
  content: "";
  position: absolute;
  inset: 0;
  border-radius: inherit;
  pointer-events: none;
  animation: keyHintInner 1.1s ease-in-out infinite;
}
@keyframes keyHintGlow {
  0%, 100% {
    box-shadow: 0 3px 0 rgba(0, 0, 0, .22), 0 0 0 3px rgba(255, 220, 90, .9),
      0 0 18px 7px rgba(255, 210, 60, .6);
    transform: translateY(0) scale(1);
  }
  50% {
    box-shadow: 0 3px 0 rgba(0, 0, 0, .22), 0 0 0 7px rgba(255, 235, 140, .95),
      0 0 34px 15px rgba(255, 210, 60, .85);
    transform: translateY(-2px) scale(1.05);
  }
}
@keyframes keyHintInner {
  0%, 100% { box-shadow: inset 0 0 0 3px rgba(255, 235, 150, .85); }
  50% { box-shadow: inset 0 0 0 6px rgba(255, 248, 200, 1), inset 0 0 16px rgba(255, 210, 60, .9); }
}

/* 已探索過：右上角一顆小星星 */
.kb-key.found::before {
  content: "⭐";
  position: absolute;
  top: 1px;
  right: 3px;
  font-size: clamp(7px, 1.1vh, 10px);
  opacity: .9;
  pointer-events: none;
}
.kb-key.with-glyph.found::before { top: 1px; right: 3px; }

/* 換王國／換環境的翻牌動畫 —— 告訴孩子「意思變了」 */
.kb-key.morph {
  animation: keyMorph .5s ease-out backwards;
  animation-delay: var(--morph-delay, 0ms);
}
@keyframes keyMorph {
  0% { transform: rotateX(-88deg) scale(.9); opacity: 0; filter: brightness(1.6); }
  60% { transform: rotateX(12deg) scale(1.03); opacity: 1; }
  100% { transform: rotateX(0) scale(1); opacity: 1; }
}

/* ── 固定鍵說明 ─────────────────────────────────── */
.legend {
  display: flex;
  justify-content: center;
  flex-wrap: wrap;
  gap: clamp(6px, 1.4vw, 20px);
  padding-top: calc(var(--gap) + 2px);
  max-width: 1180px;
  margin: 0 auto;
}
.legend-item {
  display: flex;
  align-items: center;
  gap: 5px;
  padding: 3px 10px;
  border-radius: 999px;
  background: rgba(255, 255, 255, .1);
  color: #efe9ff;
  font-size: clamp(9px, 1.35vh, 12px);
  font-weight: 700;
  cursor: pointer;
  transition: background .2s, transform .12s;
}
.legend-item:hover { background: rgba(255, 255, 255, .2); }
.legend-item:active { transform: scale(.94); }
.legend-cap {
  background: rgba(255, 255, 255, .92);
  color: #3a2f5c;
  border-radius: 6px;
  padding: 2px 6px;
  font-weight: 900;
  font-size: .95em;
}
.legend-icon { font-size: 1.25em; }
.legend-touch-label { display: none; }

/* 閒置太久的邀請：呼吸發光。
   光打在元素自己的 box-shadow 上 —— 學過一次了，::after 會被祖先的 overflow 切掉。 */
.legend-item.nudge {
  background: rgba(255, 240, 170, .3);
  animation: legendNudge 1.4s ease-in-out infinite;
}
@keyframes legendNudge {
  0%, 100% { box-shadow: 0 0 0 0 rgba(255, 226, 138, .75); transform: scale(1); }
  50% { box-shadow: 0 0 16px 6px rgba(255, 226, 138, .95); transform: scale(1.07); }
}
.legend-item {
  border: none;
  font-family: var(--font);
}

/* ── 覆蓋層 ─────────────────────────────────────── */
.overlay {
  position: fixed;
  inset: 0;
  pointer-events: none;
  z-index: 50;
}
.overlay > * { pointer-events: none; }
.overlay .modal-backdrop { pointer-events: auto; }

/* 傳送門 */
.portal {
  position: absolute;
  inset: 0;
  opacity: 0;
  background: radial-gradient(circle at 50% 50%, var(--c2, #8ecae6), var(--c1, #ffd166));
}
.portal.run { animation: portalSweep .78s ease-in-out; }
@keyframes portalSweep {
  0% { opacity: 0; clip-path: circle(0% at 50% 50%); }
  42% { opacity: 1; clip-path: circle(150% at 50% 50%); }
  58% { opacity: 1; clip-path: circle(150% at 50% 50%); }
  100% { opacity: 0; clip-path: circle(150% at 50% 50%); }
}

/* 中央大公告 */
.announce {
  position: absolute;
  top: 12%;
  left: 50%;
  transform: translateX(-50%) scale(.7);
  opacity: 0;
  transition: opacity .3s, transform .45s cubic-bezier(.34, 1.56, .64, 1);
}
.announce.show { opacity: 1; transform: translateX(-50%) scale(1); }
.announce-card {
  display: flex;
  align-items: center;
  gap: 16px;
  padding: 14px 30px;
  border-radius: 26px;
  background: rgba(255, 253, 247, .96);
  box-shadow: 0 12px 40px rgba(40, 20, 80, .38);
  border: 4px solid rgba(255, 255, 255, .9);
  max-width: min(88vw, 640px);
}
.announce-icon { font-size: clamp(2.2rem, 6vh, 3.4rem); line-height: 1; }
.announce-title {
  font-size: clamp(1.15rem, 3.2vh, 1.8rem);
  font-weight: 900;
  color: #3a2b5e;
}
.announce-sub {
  font-size: clamp(.8rem, 2vh, 1.05rem);
  font-weight: 700;
  color: #6b5a8e;
  margin-top: 2px;
}

/* 新貼紙 */
.sticker-pop {
  position: absolute;
  right: 3%;
  top: 16%;
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 2px;
  padding: 16px 22px;
  border-radius: 22px;
  background: linear-gradient(160deg, #fff8dc, #ffe4a3);
  box-shadow: 0 12px 34px rgba(120, 70, 20, .35);
  border: 4px solid #fff;
  animation: stickerIn .6s cubic-bezier(.34, 1.56, .64, 1);
}
.sticker-pop.out { animation: stickerOut .5s ease-in forwards; }
.sticker-pop-icon { font-size: clamp(2.4rem, 6.5vh, 3.6rem); line-height: 1; }
.sticker-pop-label { font-weight: 900; color: #7a5210; font-size: clamp(.8rem, 2vh, 1rem); }
.sticker-pop-tag {
  font-weight: 900;
  color: #fff;
  background: #ff8fab;
  border-radius: 999px;
  padding: 2px 12px;
  font-size: clamp(.65rem, 1.6vh, .8rem);
  margin-top: 4px;
}
@keyframes stickerIn {
  0% { transform: translateX(120%) rotate(18deg); opacity: 0; }
  100% { transform: translateX(0) rotate(-3deg); opacity: 1; }
}
@keyframes stickerOut {
  0% { transform: translateX(0) rotate(-3deg); opacity: 1; }
  100% { transform: translateX(130%) rotate(20deg); opacity: 0; }
}

/* ── 對話框 ─────────────────────────────────────── */
.modal-backdrop {
  position: absolute;
  inset: 0;
  display: grid;
  place-items: center;
  background: rgba(28, 18, 52, .62);
  backdrop-filter: blur(6px);
  opacity: 0;
  transition: opacity .22s;
  padding: 16px;
}
.modal-backdrop.show { opacity: 1; }
.modal {
  background: var(--paper);
  border-radius: 28px;
  padding: clamp(16px, 3vh, 30px);
  box-shadow: 0 24px 70px rgba(20, 10, 45, .5);
  border: 5px solid #fff;
  /* width 一定要給定值，不能只給 max-width。
     父層是 place-items:center 的 grid，只給 max-width 的話寬度會塌成內容寬，
     裡面的 repeat(auto-fit, minmax(...)) 就退化成一欄 —— 世界大廳會變成一直長條。*/
  width: min(92vw, 560px);
  max-height: 88vh;
  overflow-y: auto;
  transform: scale(.92);
  transition: transform .3s cubic-bezier(.34, 1.56, .64, 1);
}
.modal.wide { width: min(94vw, 940px); }
.modal-backdrop.show .modal { transform: scale(1); }

.modal-head {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 14px;
  font-size: clamp(1.1rem, 3vh, 1.6rem);
  font-weight: 900;
  color: #3a2b5e;
  margin-bottom: 14px;
}
.modal-count {
  font-size: .7em;
  background: #ffe4a3;
  border-radius: 999px;
  padding: 3px 12px;
  color: #7a5210;
}

.big-btn {
  display: block;
  width: 100%;
  margin-top: 16px;
  padding: 14px;
  border: none;
  border-radius: 18px;
  background: linear-gradient(180deg, #8ad6a0, #4fa86c);
  color: #fff;
  font: 900 clamp(1rem, 2.6vh, 1.3rem) var(--font);
  cursor: pointer;
  box-shadow: 0 5px 0 #3c8354, 0 8px 18px rgba(40, 100, 60, .3);
  transition: transform .1s;
}
.big-btn:active { transform: translateY(4px); box-shadow: 0 1px 0 #3c8354; }
.big-btn.start {
  background: linear-gradient(180deg, #ffd166, #f0a92e);
  box-shadow: 0 5px 0 #c2851f, 0 8px 18px rgba(160, 110, 20, .3);
  color: #56380a;
}

.danger-btn {
  width: 100%;
  margin-top: 10px;
  min-height: 48px;
  padding: 11px;
  border: 2px solid #ffb3b3;
  border-radius: 14px;
  background: #fff1f1;
  color: #b23b3b;
  font: 800 .95rem var(--font);
  cursor: pointer;
}

/* 貼紙簿 */
.sb-grid {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(84px, 1fr));
  gap: 10px;
  max-height: 54vh;
  overflow-y: auto;
  padding: 4px;
}
.sb-item {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 3px;
  padding: 10px 4px;
  border-radius: 16px;
  background: linear-gradient(160deg, #fff8dc, #ffeec2);
  border: 3px solid #fff;
  box-shadow: 0 3px 10px rgba(140, 100, 30, .18);
}
.sb-icon { font-size: 2rem; line-height: 1; }
.sb-label {
  font-size: .68rem;
  font-weight: 800;
  color: #7a5210;
  text-align: center;
  line-height: 1.15;
  word-break: break-word;
}
.sb-empty {
  padding: 34px 10px;
  text-align: center;
  font-weight: 700;
  color: #7d6ea3;
  line-height: 1.7;
}

/* 設定。
   這一整片原本是全 App 最小的觸控目標：每一列只有 21px 高、勾選框 22×16px
   （在 iPad mini 上是 2.5mm）。雖然大人才會進來，44pt 的下限對大人一樣成立。
   勾選框包在 <label> 裡，所以只要整列夠高，點文字也算點到勾選框。 */
.settings { display: flex; flex-direction: column; gap: 12px; }
.set-row {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 14px;
  min-height: 48px;
  font-weight: 700;
  color: #4a3a72;
  font-size: .95rem;
}
/* 滑桿的拉柄要抓得到：把整條的高度拉到 44px，拉柄才有上下的容錯 */
.set-row input[type="range"] {
  flex: 0 0 46%;
  height: 44px;
  accent-color: #8a6fd6;
}
.set-row input[type="checkbox"] { width: 30px; height: 30px; accent-color: #8a6fd6; }
/* 設定裡的小按鈕（離線語音的「下載」）。
   不是 .big-btn（那是整列寬的主要動作），也不是 .danger-btn。 */
.pill-btn {
  flex: 0 0 auto;
  min-width: 96px;
  min-height: 44px;
  padding: 8px 18px;
  border: none;
  border-radius: 999px;
  background: linear-gradient(180deg, #a8c8ff, #6f97e8);
  color: #fff;
  font: 900 .95rem var(--font);
  cursor: pointer;
  box-shadow: 0 3px 0 #5578c0;
  transition: transform .1s, filter .2s;
}
.pill-btn:active { transform: translateY(3px); box-shadow: 0 0 0 #5578c0; }
.pill-btn:disabled {
  background: rgba(140, 110, 200, .18);
  color: #6b5a8e;
  box-shadow: none;
  cursor: default;
}
.touch-mode .pill-btn { min-width: 112px; min-height: 56px; font-size: 1.05rem; }

.set-note {
  font-size: .82rem;
  font-weight: 600;
  color: #6b5a8e;
  background: rgba(140, 110, 200, .1);
  border-radius: 12px;
  padding: 10px 14px;
  line-height: 1.7;
}

/* 世界大廳 */
.hub-head { justify-content: center; }
.hub-grid {
  display: grid;
  /* 六個世界，固定三欄兩列 —— 幼兒需要穩定的位置感，
     每次打開大廳「注音森林」都要在同一個地方。*/
  grid-template-columns: repeat(3, 1fr);
  gap: 16px 14px;
  padding-top: 6px;
}
@media (max-width: 620px) {
  .hub-grid { grid-template-columns: repeat(2, 1fr); }
}
.hub-card {
  position: relative;
  border: 5px solid #fff;
  border-radius: 22px;
  padding: 16px 10px 14px;
  background: linear-gradient(160deg, var(--c1), var(--c2));
  color: #fff;
  cursor: pointer;
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 5px;
  box-shadow: 0 6px 18px rgba(40, 25, 80, .28);
  transition: transform .18s cubic-bezier(.34, 1.56, .64, 1), box-shadow .2s;
  font-family: var(--font);
}
.hub-card:hover, .hub-card.focus {
  transform: translateY(-7px) scale(1.05);
  box-shadow: 0 14px 30px rgba(40, 25, 80, .4);
  border-color: #fff6b8;
}
.hub-card.current::after {
  content: "現在在這裡";
  position: absolute;
  top: -12px;
  left: 50%;
  transform: translateX(-50%);
  background: #fff;
  color: #6b5a8e;
  font-size: .62rem;
  font-weight: 900;
  border-radius: 999px;
  padding: 2px 10px;
  white-space: nowrap;
}
.hub-emoji { font-size: clamp(2.2rem, 6vh, 3.2rem); line-height: 1; }
.hub-name {
  font-weight: 900;
  font-size: clamp(.95rem, 2.4vh, 1.2rem);
  text-shadow: 0 2px 5px rgba(0, 0, 0, .3);
}
.hub-teach {
  font-weight: 700;
  font-size: .74rem;
  background: rgba(255, 255, 255, .28);
  border-radius: 999px;
  padding: 2px 10px;
}
.hub-num {
  position: absolute;
  top: 6px;
  left: 10px;
  font-weight: 900;
  font-size: .85rem;
  opacity: .65;
}
.hub-tip {
  margin-top: 16px;
  text-align: center;
  font-weight: 700;
  font-size: .82rem;
  color: #6b5a8e;
}

/* 開場 */
.welcome { text-align: center; }
.welcome-title {
  font-size: clamp(1.8rem, 5.5vh, 2.9rem);
  font-weight: 900;
  background: linear-gradient(100deg, #ff8fab, #ffd166, #8ecae6, #a0e57c);
  -webkit-background-clip: text;
  background-clip: text;
  color: transparent;
  letter-spacing: .04em;
}
.welcome-sub {
  font-weight: 800;
  color: #8a7bb0;
  letter-spacing: .16em;
  font-size: clamp(.7rem, 1.8vh, .9rem);
  margin-top: 2px;
}
.welcome-row {
  font-size: clamp(1.6rem, 4.5vh, 2.4rem);
  letter-spacing: .12em;
  margin: 16px 0 12px;
}
.welcome-text {
  font-weight: 700;
  color: #55467c;
  line-height: 1.9;
  font-size: clamp(.85rem, 2.1vh, 1.05rem);
}
.welcome-foot {
  margin-top: 12px;
  font-size: .74rem;
  font-weight: 700;
  color: #9689b5;
}

/* ── 觸控模式（平板 App 的主要形態） ──────────────────
   手指的接觸面比滑鼠游標大得多，幼兒的手指又特別不精準，
   所以鍵要更大、字要更大、間距要更寬。Apple 的最小建議是 44pt，
   幼兒實際上需要更大。 */
/* 寫成 `:root.touch-mode` 而不是 `.touch-mode` 是有原因的：
   下面那幾支「矮螢幕」的 media query 用 `:root` 調 `--key-h`，兩者特異性相同，
   後者在後面就贏了 —— iPad mini 橫向（744px 高）因此掉進縫裡，
   鍵只有 49.1px（7.7mm），全部平板裡最小的一個。加一個 class 就永遠贏。
   下限 48px＝Material 的 48dp（比 Apple 的 44pt 嚴），是地板不是目標。 */
:root.touch-mode {
  /**
   * 鍵是**寬的**，不是方的。
   *
   * 原本是 clamp(48px, 9.4vh, 88px)，量出來 iPad Pro 上是 91×88（寬高比 1.03，
   * 幾乎是正方形），iPad 直向更是 58×88 —— **比寬還高**。
   * 回饋：「平板上按鈕本身就太高了，手機上非按鈕的顯示區太小東西都擠在一起。」
   * 兩件事是同一個原因：鍵吃掉的高度遠超過它需要的。
   *
   * 上限收到 58px，四排鍵省下來的高度全部還給世界。
   * 48px 的地板不動 —— 那是 Material 的觸控下限。
   */
  --key-h: clamp(48px, 6.4vh, 58px);
  --gap: clamp(4px, 0.8vh, 10px);
}
.touch-mode .kb-key {
  border-radius: 18px;
  /* 觸控沒有 hover，按下的回饋要更誇張才看得出來 */
  box-shadow:
    0 5px 0 rgba(0, 0, 0, .24),
    0 6px 14px rgba(0, 0, 0, .22),
    inset 0 2px 0 rgba(255, 255, 255, .9);
}
/* 字級：鍵有 77px 高的時候，大字 29.5px ＋ 標籤 12.3px 只用掉 43px，
   剩下的高度是白留的。標籤在 iPad 10.9" 上只有 2.4mm —— 那是教學內容，
   不是註腳。往上加到「還放得下」為止：大字 3.9vh、標籤 1.8vh。 */
/**
 * 字級跟著鍵高走，不要各自用 vh 去算。
 *
 * 分開算的話兩邊會不同意：鍵一變矮、字還照舊，就被鍵的 overflow 切掉半截
 * （手機上踩過這個）。綁在一起之後「鍵多高、字多大」永遠是同一個決定。
 *
 * 比例是從高度預算反推的：大字 0.56 ＋ 標籤 0.21（行高 1.1）＋ 內距 4px
 * 大約是鍵高的 0.88，剩下的是呼吸空間。
 */
.touch-mode .kb-glyph { font-size: max(20px, calc(var(--key-h) * 0.56)); }
.touch-mode .kb-icon { font-size: max(20px, calc(var(--key-h) * 0.52)); }
.touch-mode .kb-label { font-size: max(11px, calc(var(--key-h) * 0.21)); }

/* 標籤折兩行。
   長標籤（「Mi Fa 中間沒黑鍵」「和弦 Do Mi Sol」「短一點（3 個）」）本來
   在**每一種**尺寸上都被省略號吃掉半句 —— 掃過 28 個場景，1180×820 上有 92 處。
   鍵是「上面大字、下面標籤」，高度一直有剩，所以讓標籤往下長而不是往旁邊擠。

   用 max-height 封頂，不用 `-webkit-line-clamp`：`.kb-key` 是 flex 容器，
   子元素的 `display: -webkit-box` 會被 blockify 成 `flow-root`，
   clamp 就失效了 —— 實測會露出被切掉一半的第三行。
   `max-height: 2.2em` 配 `line-height: 1.1` 剛好是整整兩行，切得乾淨。 */
.touch-mode .kb-label {
  white-space: normal;
  word-break: break-word;
  /* line-height 要自己寫死。矮螢幕那條 `@media (max-height: 660px)` 把它設成 1，
     而這裡只蓋掉 font-size —— 於是 max-height 的 em 算出來是 2.2 行，
     多出來的 0.2 行就是畫面上那條被切一半的字。 */
  line-height: 1.1;
  max-height: 2.2em;
}

@media (orientation: portrait) {
  /* 兩個字以上的大字（「↘↗」「10」）在 58px 寬的直向鍵上排不下 —— 縮小它。
     只縮這幾顆，不要為了它們把全部的大字都縮小。 */
  .touch-mode .kb-key.wide-glyph .kb-glyph { font-size: clamp(15px, 2.4vh, 24px); }
}

/* 「按空白鍵換世界」這種句子在平板上是完全無效的資訊 —— 那顆鍵根本不存在。
   跟功能列同一套做法：兩句都寫在 HTML 裡，由 CSS 決定看到哪一句。 */
.touch-only { display: none; }
.touch-mode .kb-only { display: none; }
.touch-mode .touch-only { display: inline; }
.touch-mode .kb-key.with-glyph .kb-icon { font-size: max(12px, calc(var(--key-h) * 0.30)); }
.touch-mode .kb-cap { display: none; } /* 平板上沒有實體鍵帽可對照 */
.touch-mode .kb-key:active {
  transform: translateY(4px) scale(.95);
  filter: brightness(1.15);
}
.touch-mode .hud-btn {
  padding: 11px 16px;
  min-width: 56px;
  min-height: 56px;
  font-size: 1.35rem;
}
.touch-mode .hud { min-height: 64px; }
.touch-mode .set-row { min-height: 56px; }
/* 觸控模式：這一排是真的按鈕，不是鍵盤對照表。
   平板上沒有空白鍵／Enter／Esc，寫鍵名對只有手指的孩子毫無意義。 */
.touch-mode .legend { gap: clamp(8px, 1.6vw, 22px); }
.touch-mode .legend-cap { display: none; }
.touch-mode .legend-label { display: none; }
.touch-mode .legend-touch-label { display: inline; }
.touch-mode .legend-item {
  flex-direction: column;
  gap: 1px;
  padding: 7px clamp(12px, 2vw, 26px);
  border-radius: 16px;
  background: rgba(255, 255, 255, .16);
  box-shadow: 0 3px 0 rgba(0, 0, 0, .22);
  font-size: clamp(10px, 1.6vh, 14px);
  font-weight: 800;
  min-width: clamp(66px, 9vw, 104px);
}
.touch-mode .legend-item:active { transform: translateY(3px) scale(.96); }
.touch-mode .legend-icon { font-size: clamp(20px, 3vh, 28px); }
/* 「拖我移動」只是說明，不是按鈕 —— 樣式要看得出來不能按 */
.touch-mode .legend-hint {
  background: none;
  box-shadow: none;
  opacity: .8;
}
.touch-mode .legend-hint:active { transform: none; }
.touch-mode .big-btn { padding: 20px; font-size: clamp(1.15rem, 3vh, 1.5rem); }
.touch-mode .hub-card { padding: 22px 12px 18px; }
/* 手指拖曳的區域要看得出來可以拖 */
.touch-mode .stage { cursor: grab; touch-action: none; }
.touch-mode #world { touch-action: none; }

/* 安全區（iPhone 的圓角與瀏海、iPad 的 home indicator） */
.app {
  padding-top: env(safe-area-inset-top);
  padding-bottom: env(safe-area-inset-bottom);
  padding-left: env(safe-area-inset-left);
  padding-right: env(safe-area-inset-right);
}

/* 設定面板裡的「誰來說話」——家長最常動的設定，做成大按鈕不做下拉 */
.set-packs { align-items: center; }
.pack-pick { display: flex; gap: 10px; flex: 0 0 58%; }
.pack-btn {
  flex: 1;
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 2px;
  padding: 9px 8px;
  border: 3px solid rgba(140, 110, 200, .25);
  border-radius: 14px;
  background: #fff;
  cursor: pointer;
  font-family: var(--font);
  transition: transform .12s, border-color .2s, background .2s;
}
.pack-btn:active { transform: scale(.95); }
.pack-btn.on {
  border-color: #f0a92e;
  background: linear-gradient(160deg, #fff8dc, #ffeec2);
  box-shadow: 0 3px 10px rgba(200, 150, 40, .25);
}
.pack-emoji { font-size: 1.7rem; line-height: 1; }
.pack-label { font-size: .8rem; font-weight: 800; color: #4a3a72; white-space: nowrap; }
.pack-btn.on .pack-label { color: #7a5210; }
.touch-mode .pack-btn { padding: 13px 10px; }
.touch-mode .pack-emoji { font-size: 2.1rem; }

/* 設定面板的小字說明 */
.set-row small { font-weight: 600; opacity: .7; font-size: .76em; }

@media (prefers-reduced-motion: reduce) {
  *, *::before, *::after {
    animation-duration: .01ms !important;
    animation-iteration-count: 1 !important;
    transition-duration: .01ms !important;
  }
}

/* 很矮的螢幕（筆電 768p、小筆電 640p）把鍵盤壓扁，但**絕對不能拿掉標籤** ——
   標籤就是「這顆鍵現在是什麼意思」，是整個遊戲的教學內容本身。
   所以是全部一起縮小，不是隱藏其中一項。 */
@media (max-height: 760px) {
  :root { --key-h: clamp(40px, 6.6vh, 52px); }
  .hud { min-height: 44px; padding: 4px 10px; }
  .kb-glyph { font-size: clamp(14px, 2.5vh, 22px); }
  .kb-icon { font-size: clamp(14px, 2.4vh, 21px); }
  .kb-label { font-size: clamp(7px, 1.05vh, 10px); }
}
@media (max-height: 660px) {
  :root { --key-h: clamp(37px, 6.1vh, 44px); --gap: 3px; }
  .kb-cap { font-size: 7px; top: 1px; left: 3px; }
  .kb-glyph { font-size: clamp(13px, 2.2vh, 19px); line-height: 1; }
  .kb-icon { font-size: clamp(13px, 2.1vh, 18px); line-height: 1; }
  .kb-label { font-size: 7px; line-height: 1; margin-top: 0; }
  .kb-key.with-glyph .kb-icon { font-size: 9px; right: 2px; bottom: 1px; }
  .legend { padding-top: 3px; gap: 5px; }
  .legend-item { padding: 2px 7px; font-size: 8px; }
}
/* 真的矮到不行（例如橫向手機）才犧牲標籤 */
@media (max-height: 520px) {
  :root { --key-h: clamp(30px, 5.6vh, 38px); }
  .kb-label, .kb-cap { display: none; }
  /* ……但觸控模式不犧牲。平板上鍵有 48px 高，塞得下標籤；
     而標籤就是「這顆鍵現在是什麼意思」，是遊戲的教學內容本身。
     iPadOS 26 的小視窗會給到 480px 高，那不是手機，是平板被縮小了。 */
  .touch-mode .kb-label { display: block; }
}

/* 觸控模式的矮螢幕（便宜的 1024×600 Android 平板、iPadOS 26 縮小的視窗）。
   `--key-h` 不在這裡調 —— 上面的 `clamp(48px, 9.4vh, 88px)` 本來就會隨高度縮，
   而且有 48px 的地板；在這裡再蓋一次只會又製造一個特異性打架的縫。
   這一段只做一件事：把周邊（HUD、功能列）壓薄，把省下來的高度留給世界，
   同時替字級設地板 —— 標籤就是教學內容，縮到看不見就等於沒有。

   只用高度當條件，觸控與否交給 JS 加上的 .touch-mode class 判斷 ——
   `(pointer: coarse)` 在某些環境（包含自動化測試的模擬裝置）不會匹配，
   不能拿它當唯一的判斷依據。 */
/* 窄到十二欄排不下的時候（iPad 分割檢視一半＝507px 寬，量出來鍵只有 33.3px
   ＝6.4mm），改成每排折成兩行、固定六欄。
   為什麼是「折」不是「縮」：注音符號的位置就是台灣標準鍵盤的位置，
   縮到按不到就什麼都不是了；折成兩半至少左右手的相對順序還在。
   門檻 630px 是算出來的：12 欄 × 48px ＋ 11 個間隙 ＋ 內距 ≈ 632px。 */
@media (max-width: 630px) {
  .touch-mode .kb-row {
    grid-auto-flow: row;
    grid-template-columns: repeat(6, 1fr);
  }
  /* 功能列在手機上會擠成兩排，多吃掉 74px —— 而世界總共才剩 134px。
     這裡把它壓成一排：字拿掉只留圖示（那五個圖示本來就看得懂：
     🌀 換世界、🌦️ 換天氣、🌟 拖我移動、🏠 回家、🧹 收乾淨），
     min-width 讓給 48px 的觸控下限，一排剛好排得下。 */
  .touch-mode .legend {
    flex-wrap: nowrap;
    gap: 6px;
    padding-top: 3px;
  }
  .touch-mode .legend-item {
    min-width: 48px;
    padding: 6px 4px;
  }
  .touch-mode .legend-touch-label { display: none; }
  .touch-mode .legend-icon { font-size: clamp(22px, 3.4vh, 30px); }

  /* 手機：把「不是按鈕的東西」壓到最小，把高度讓給世界。
     八排鍵有七道縫，每道省 3px 就是 21px。 */
  :root.touch-mode { --gap: 3px; }
  .touch-mode .console { padding-left: 4px; padding-right: 4px; }

  .touch-mode .hud { min-height: 52px; padding: 3px 8px; }
  .touch-mode .hud-btn { min-width: 48px; min-height: 48px; padding: 6px 10px; }

  /* 手機上的鍵只有 48px 高，圖示加上兩行標籤放不下 —— 實測會被鍵的
     `overflow: hidden` 從中間切掉，看起來像字被咬掉半截。
     這裡收回一行，切在字的邊界並補省略號，至少讀得出來是被截斷的。 */
  .touch-mode .kb-label {
    white-space: nowrap;
    max-height: 1.2em;
    text-overflow: ellipsis;
  }
  /* 排數變成兩倍，鍵就不能再長到 88px，不然鍵盤會吃掉整個畫面 */
  :root.touch-mode { --key-h: clamp(48px, 5.4vh, 58px); }
  .touch-mode .kb-row:nth-child(3),
  .touch-mode .kb-row:nth-child(4) { padding-left: 0; padding-right: 0; }
}

@media (max-height: 640px) {
  .touch-mode .hud { min-height: 56px; padding: 3px 8px; }
  .touch-mode .hud-btn { padding: 7px 11px; min-width: 48px; min-height: 48px; font-size: 1.1rem; }
  .touch-mode .legend { padding-top: 2px; gap: 5px; }
  .touch-mode .legend-item { padding: 2px 7px; min-height: 48px; font-size: 11px; }
  .touch-mode .set-row { min-height: 48px; }
}

/**
 * ── 只有手機 ──────────────────────────────────────
 *
 * 回饋：「電腦跟平板的比例非常好，但手機螢幕特別小特別窄，顯示範圍特別小，
 * 相對來說物體的比例就太大。只有偵測到是手機的時候，按鈕再矮一點、
 * 畫面裡的物件小一點、最上面那排系統按鈕短一點。」
 *
 * 量出來的確如此（主控台吃掉的比例／世界剩多高）：
 *
 *     iPhone 12   70%   144px
 *     iPhone SE   82%    45px
 *
 * 而上面那條資訊列量出來是 **459px 寬**，iPhone 12 只有 390px ——
 * 右邊三顆按鈕有兩顆在畫面外，SE 是三顆都在外面。
 *
 * 這一段**不能用 media query**：斷點只看得到視窗有多寬，
 * 而 iPad 的分割檢視也只有 507px（平板測試當場抓到把它當成手機）。
 * 「phone-mode」是 JS 照**實體螢幕的短邊**掛上去的（見 core/touch.js）。
 * 選擇器寫成「:root.touch-mode.phone-mode」是為了贏過 media query 裡的
 * 「:root.touch-mode」，不必去管誰寫在前面（那個坑踩過）。
 */
:root.touch-mode.phone-mode {
  /**
   * 鍵比平板再矮一階。48px 是 Material 的觸控下限，這裡刻意破它，
   * 理由是**面積**：手機上的鍵是 50–61px 寬 × 42px 高 ＝ 2100–2560px²，
   * 比 44×44（Apple 的下限，1936px²）還大，窄邊 42px 也還在手指的尺度上。
   * 換來的是八排鍵少掉 48px，全部給世界。
   */
  --key-h: 42px;
}

/* 資訊列：寬度都花在「王國名」跟「天氣名」兩個字牌上（112 ＋ 87 ＝ 199px），
   而它們的圖示本來就在旁邊，字拿掉還是認得出來。 */
.touch-mode.phone-mode .hud { min-height: 44px; padding: 2px 6px; gap: 6px; }
.touch-mode.phone-mode .hud-btn {
  min-width: 44px;
  min-height: 44px;
  padding: 4px 8px;
  font-size: 1rem;
}
.touch-mode.phone-mode .hud-realm, .touch-mode.phone-mode .hud-env { padding: 3px 8px; }
.touch-mode.phone-mode .hud-realm-name,
.touch-mode.phone-mode .hud-env-name { display: none; }
.touch-mode.phone-mode .legend-item { min-height: 42px; }

/* 尊重「減少動態效果」的系統設定 */
