@font-face {
  font-family: "Baloo 2";
  src: url("vendor/fonts/baloo2.woff2") format("woff2");
  font-weight: 400 800;
  font-display: swap;
}

:root {
  color-scheme: light;
  --ink: #774238;
  --ink-soft: #a36f64;
  --cream: #fff7e9;
  --shadow: #d8aaa1;
  --board-surface: #fff9ef;
  --header-control-size: 60px;
  --tool-control-size: clamp(70px, 21vw, 92px);
  font-family: "Baloo 2", "Arial Rounded MT Bold", "Trebuchet MS", system-ui, sans-serif;
}

* {
  box-sizing: border-box;
}

html,
body {
  margin: 0;
  min-height: 100%;
  overscroll-behavior: none;
  /* 双击放大：viewport 的 user-scalable=no 在部分内核里挡不住它，这里补一道。
     manipulation 只关掉双击缩放，正常点击和滚动都留着。 */
  touch-action: manipulation;
}

body {
  min-height: 100dvh;
  overflow-x: clip;
  color: var(--ink);
  background: #e6ebe8;
}

button {
  font: inherit;
}

.app-loader[hidden],
.game-shell[hidden],
.tutorial[hidden],
.tutorial-result[hidden],
.tutorial-stage[hidden],
.tutorial-dots[hidden] {
  display: none;
}

/* 开屏页。
   玩家看到的第一屏，也是唯一一屏"什么都还没发生"的画面——上面只有一个孤零零的
   进度条时，等待会显得格外长。补上游戏名和工作室署名，等待就变成了品牌露出。
   底色沿用游戏内的奶油色，加载完淡出到主界面不会有色块跳变。 */
.app-loader {
  position: fixed;
  inset: 0;
  z-index: 100;
  display: grid;
  align-content: center;
  justify-items: center;
  gap: 16px;
  color: #774238;
  background: linear-gradient(180deg, #FFFDF7 0%, #FFF7ED 62%, #FAEBDD 100%);
}

.app-loader-brand {
  display: grid;
  justify-items: center;
  gap: 10px;
}

/* 加载进度条：外槽 + 渐变填充 + 一道扫光 */
.app-loader-mascot {
  width: clamp(96px, 26vw, 132px);
  height: auto;
  animation: loader-bob 3s ease-in-out infinite;
}

.app-loader-title {
  font-size: clamp(28px, 8vw, 40px);
  font-weight: 900;
  letter-spacing: 0.02em;
  color: #6B3A31;
  text-shadow: 0 2px 0 rgba(255, 255, 255, 0.9);
}

/* 工作室署名。压得很轻——它是落款不是主角，抢了游戏名就本末倒置了 */
.app-loader-studio {
  display: flex;
  gap: 7px;
  align-items: center;
  margin: 22px 0 0;
  font-size: 12px;
  font-weight: 700;
  letter-spacing: 0.16em;
  color: #B39086;
  text-transform: uppercase;
}

.app-loader-studio-mark {
  width: 7px;
  height: 7px;
  border-radius: 50%;
  background: #EE6E92;
  box-shadow: 0 0 0 3px rgba(238, 110, 146, 0.18);
}

@keyframes loader-bob {
  0%, 100% { transform: translateY(0) rotate(-1.5deg); }
  50% { transform: translateY(-8px) rotate(1.5deg); }
}

.app-loader-bar {
  position: relative;
  width: min(240px, 68vw);
  height: 16px;
  overflow: hidden;
  border: 3px solid #E7C6C9;
  border-radius: 999px;
  background: #F6E3DC;
  box-shadow: inset 0 2px 4px rgba(119, 66, 56, 0.12);
}

.app-loader-fill {
  width: 0%;
  height: 100%;
  border-radius: 999px;
  /* 渐变随填充宽度拉伸，进度越满蓝绿色越明显，一眼能看出走到哪 */
  background: linear-gradient(90deg, #F2879F, #EE6E92 52%, #5FC6DC);
  transition: width 260ms ease-out;
}

/* 扫光让进度条在停顿时也显得在动 */
.app-loader-bar::after {
  position: absolute;
  inset: 0;
  content: "";
  background: linear-gradient(90deg, transparent, rgba(255, 255, 255, 0.42), transparent);
  transform: translateX(-100%);
  animation: loader-sheen 1.6s ease-in-out infinite;
}

@keyframes loader-sheen {
  to { transform: translateX(100%); }
}

.app-loader strong {
  min-width: 190px;
  color: var(--ink-soft);
  font-size: 16px;
  text-align: center;
}

.app-loader button {
  padding: 10px 20px;
  border: 2px solid #774238;
  border-radius: 8px;
  color: #774238;
  background: #ffe17d;
  cursor: pointer;
}

.tutorial {
  position: relative;
  display: grid;
  grid-template-rows: auto minmax(0, 1fr) auto;
  width: min(100%, 620px);
  height: 100dvh;
  min-height: 100dvh;
  margin: 0 auto;
  padding:
    max(18px, env(safe-area-inset-top))
    18px
    max(20px, env(safe-area-inset-bottom));
  overflow: visible;
  /* 麻将主题：引导教的就是麻将玩法，背景跟着走 */
  background-image: url("assets/mahjong/background.svg");
  background-position: center;
  background-repeat: no-repeat;
  background-size: cover;
  isolation: isolate;
}

.tutorial::before {
  position: absolute;
  inset: 0;
  z-index: -1;
  content: "";
  /* 遮罩太薄前景的散牌会抢主体，太厚又看不出牌山，0.56 是平衡点。
     原来还叠了 backdrop-filter: blur(1.5px)，同 .home::before 一起去掉了 */
  background: rgba(255, 249, 241, 0.56);
}

.tutorial-header {
  display: flex;
  min-height: 62px;
  align-items: center;
  justify-content: center;
  gap: 10px;
}

.tutorial-skip {
  position: absolute;
  top: calc(max(18px, env(safe-area-inset-top)) + 31px);
  right: max(22px, env(safe-area-inset-right));
  z-index: 20;
  padding: 2px 0 3px;
  border: 0;
  border-bottom: 1.5px solid currentColor;
  border-radius: 0;
  color: #7a4b42;
  background: transparent;
  font-size: 18px;
  font-weight: 700;
  line-height: 1;
  cursor: pointer;
  touch-action: manipulation;
  transform: translateY(-50%);
}

.tutorial-skip:active {
  transform: translateY(calc(-50% + 1px));
}

.tutorial-skip:focus-visible {
  outline: 3px solid rgba(83, 153, 187, 0.5);
  outline-offset: 4px;
}

.tutorial-mascot {
  position: relative;
  flex: 0 0 58px;
  width: 58px;
  height: 58px;
}

.tutorial-mascot-frame {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  object-fit: contain;
  animation: mascot-frame-cycle 1.2s steps(1, end) infinite;
}

.tutorial-mascot-frame.frame-2 {
  animation-delay: -0.6s;
}

.tutorial-header h1 {
  margin: 0;
  color: var(--ink);
  font-size: 26px;
  line-height: 1;
}

@keyframes mascot-frame-cycle {
  0%,
  49.999% {
    opacity: 1;
  }

  50%,
  100% {
    opacity: 0;
  }
}

.tutorial-stage {
  position: relative;
  min-height: 0;
  overflow: visible;
}

.tutorial-page {
  position: absolute;
  inset: 0;
  display: grid;
  grid-template-rows: minmax(0, 1fr);
  align-items: stretch;
  justify-items: center;
  padding: 0 8px 8px;
  overflow: visible;
  opacity: 1;
  transform: translateX(0);
  transition:
    opacity 420ms ease,
    transform 460ms cubic-bezier(0.3, 0.8, 0.25, 1);
}

.tutorial-operation-group {
  position: relative;
  top: 40%;
  display: grid;
  width: 100%;
  align-self: start;
  justify-items: center;
  gap: 16px;
  transform: translateY(-40%);
}

.tutorial-page.leaving {
  opacity: 0;
  transform: translateX(-100%);
}

.tutorial-page.entering {
  opacity: 0.35;
  transform: translateX(100%);
}

.tutorial-instruction {
  max-width: 480px;
  margin: 0;
  color: #64433d;
  font-size: 24px;
  line-height: 1.45;
  text-align: center;
}

.tutorial-instruction-copy {
  display: grid;
  justify-items: center;
  gap: 4px;
}

.tutorial-instruction-note {
  margin: 0;
  color: rgba(100, 67, 61, 0.72);
  font-size: 15px;
  line-height: 1.35;
  text-align: center;
}

.tutorial-board-stack {
  display: flex;
  width: 100%;
  min-height: 0;
  align-items: center;
  flex-direction: column;
  justify-content: flex-start;
  gap: 52px;
  padding-bottom: 10px;
  overflow: visible;
}

.tutorial-substage {
  position: relative;
  display: grid;
  width: 100%;
  max-height: 430px;
  place-items: center;
  opacity: 1;
  overflow: visible;
  transform: translateY(0);
  transition:
    max-height 360ms ease,
    opacity 300ms ease,
    transform 360ms cubic-bezier(0.2, 0.8, 0.2, 1);
}

.tutorial-substage.completed-before-next {
  z-index: 2;
  transform: translateY(-8px);
}

.tutorial-substage.pending {
  max-height: 0;
  opacity: 0;
  overflow: hidden;
  pointer-events: none;
  transform: translateY(24px);
}

.tutorial-substage.revealed {
  max-height: 250px;
  opacity: 1;
  overflow: visible;
  transform: translateY(0);
}

.tutorial-substage.revealed.revealed-after-completion {
  transform: translateY(32px);
}

.tutorial-board {
  position: relative;
  width: min(var(--tutorial-board-width), 78vw, 100%);
  aspect-ratio: var(--tutorial-board-ratio);
  overflow: visible;
  border: 1.5px solid rgba(162, 126, 116, 0.34);
  border-radius: 15px 13px 16px 12px;
  background: rgba(255, 250, 242, 0.96);
  box-shadow: 0 5px 0 rgba(159, 113, 103, 0.2);
  touch-action: none;
  user-select: none;
}

.tutorial-board.interactive {
  cursor: crosshair;
}

.tutorial-cell-grid,
.tutorial-clue-layer {
  position: absolute;
  inset: 0;
  display: grid;
  grid-template-columns: repeat(var(--tutorial-cols), 1fr);
  grid-template-rows: repeat(var(--tutorial-rows), 1fr);
}

.tutorial-cell-grid {
  z-index: 3;
  pointer-events: none;
}

.tutorial-cell.has-right-line {
  border-right: 1.5px solid rgba(162, 126, 116, 0.34);
}

.tutorial-cell.has-bottom-line {
  border-bottom: 1.5px solid rgba(162, 126, 116, 0.34);
}

.tutorial-tail-layer,
.tutorial-region-layer,
.tutorial-clue-layer {
  pointer-events: none;
}

.tutorial-tail-layer,
.tutorial-region-layer {
  position: absolute;
  inset: 0;
  overflow: visible;
}

.tutorial-tail-layer {
  z-index: 12;
}

.tutorial-region-layer {
  z-index: 4;
}

.tutorial-clue-layer {
  z-index: 12;
}

.tutorial-region,
.tutorial-selection-preview {
  position: absolute;
  border-radius: 12px 10px 13px 11px;
}

/* 拖拽读数：n×m=k。和主玩法的 .selection-info 同一套语言，
   贴在棋盘上沿外侧，不挡格子 */
.tutorial-selection-info {
  position: absolute;
  top: -14px;
  left: 50%;
  z-index: 12;
  padding: 2px 10px 3px;
  border: 2px solid var(--ink);
  border-radius: 9px 8px 10px 7px;
  color: var(--ink);
  background: var(--cream);
  font-size: 13px;
  font-weight: 900;
  white-space: nowrap;
  translate: -50% 0;
  pointer-events: none;
}

/* 面积刚好对上牌面点数时变绿——玩家松手前就知道这一笔是对的 */
.tutorial-selection-info.exact {
  color: #1f6b4a;
  border-color: #1f6b4a;
  background: #e8f6ec;
}

.tutorial-selection-info[hidden] {
  display: none;
}

/* 引导教的就是麻将，所以矩形跟主玩法的麻将模式一样只留纯色——
   原来这里铺的是 assets/axolotl/textures 的水波纹 */
.tutorial-region {
  background-color: var(--region-color);
  box-shadow: inset 0 0 18px rgba(255, 255, 255, 0.16);
}

.tutorial-region.region-committing {
  --tail-reveal-delay: 220ms;
  animation: region-commit-pulse 440ms cubic-bezier(0.2, 0.84, 0.25, 1) both;
}

.tutorial-region-gill-anchor.region-committing {
  --tail-reveal-delay: 220ms;
}

.tutorial-region-gill-anchor.region-committing .tutorial-region-gill {
  animation-delay: var(--tail-reveal-delay);
}

.tutorial-region::before,
.tutorial-selection-preview::before {
  position: absolute;
  inset: -1px;
  z-index: 3;
  border-radius: inherit;
  content: "";
  background:
    repeating-linear-gradient(90deg, rgba(117, 66, 56, 0.72) 0 9px, transparent 9px 15px) top left / 15px 2px repeat-x,
    repeating-linear-gradient(90deg, rgba(117, 66, 56, 0.72) 0 9px, transparent 9px 15px) bottom left / 15px 2px repeat-x,
    repeating-linear-gradient(180deg, rgba(117, 66, 56, 0.72) 0 9px, transparent 9px 15px) top left / 2px 15px repeat-y,
    repeating-linear-gradient(180deg, rgba(117, 66, 56, 0.72) 0 9px, transparent 9px 15px) top right / 2px 15px repeat-y;
  pointer-events: none;
}

.tutorial-region-gill,
.tutorial-region-tail {
  position: absolute;
  z-index: 5;
  object-fit: contain;
  pointer-events: none;
}

.tutorial-region-gill {
  top: -12px;
  width: 30px;
  height: 32px;
  animation: accessory-in 300ms 90ms ease-out both;
}

.tutorial-region-gill.left {
  left: -3px;
}

.tutorial-region-gill.right {
  right: -3px;
}

.tutorial-region-gill-anchor,
.tutorial-region-tail-anchor {
  position: absolute;
}

.tail-motion-lines {
  position: absolute;
  z-index: 8;
  opacity: 0;
  pointer-events: none;
}

.tail-motion-lines.left,
.tail-motion-lines.right {
  top: 50%;
  width: 104px;
  height: 70px;
  transform: translateY(-50%);
}

.tail-motion-lines.left {
  left: -96px;
}

.tail-motion-lines.right {
  right: -96px;
}

.tail-motion-lines.bottom {
  bottom: -92px;
  left: 50%;
  width: 72px;
  height: 94px;
  transform: translateX(-50%);
}

.tail-motion-lines::before,
.tail-motion-lines::after {
  position: absolute;
  width: 16px;
  height: 9px;
  border-top: 3px solid rgba(117, 66, 56, 0.76);
  border-radius: 50%;
  content: "";
}

.tail-motion-lines::before {
  top: 18%;
  left: -4px;
  transform: rotate(-24deg);
}

.tail-motion-lines::after {
  right: -4px;
  bottom: 18%;
  transform: rotate(24deg);
}

.region-tail-anchor.region-committing .tail-motion-lines,
.tutorial-region-tail-anchor.region-committing .tail-motion-lines {
  animation: tail-motion-lines 300ms 280ms ease-in-out both;
}

.tutorial-region-tail {
  animation: accessory-in 340ms 120ms ease-out both;
}

.tutorial-region-tail-anchor.region-committing {
  --tail-reveal-delay: 220ms;
  animation: accessory-in 260ms var(--tail-reveal-delay) ease-out both;
}

.tutorial-region-tail-anchor.region-committing .tutorial-region-tail {
  animation-delay: 280ms;
  animation-duration: 300ms;
  animation-iteration-count: 1;
  animation-timing-function: ease-in-out;
}

.tutorial-region-tail-anchor.region-committing .tutorial-region-tail.bottom {
  animation-name: tail-sway-bottom;
  transform-origin: 50% 12%;
}

.tutorial-region-tail-anchor.region-committing .tutorial-region-tail.left {
  animation-name: tail-sway-left;
  transform-origin: 88% 50%;
}

.tutorial-region-tail-anchor.region-committing .tutorial-region-tail.right {
  animation-name: tail-sway-right;
  transform-origin: 12% 50%;
}

.tutorial-region-tail.bottom {
  bottom: -72px;
  left: 50%;
  width: 58px;
  height: 84px;
  transform: translateX(-50%);
}

.tutorial-region-tail.left {
  top: 50%;
  left: -76px;
  width: 92px;
  height: 60px;
  transform: translateY(-50%);
}

.tutorial-region-tail.right {
  top: 50%;
  right: -76px;
  width: 92px;
  height: 60px;
  transform: translateY(-50%);
}

.tutorial-clue {
  position: relative;
  display: grid;
  min-width: 0;
  min-height: 0;
  place-items: center;
}

.tutorial-clue-stamp {
  position: absolute;
  inset: 10%;
  z-index: 0;
  display: grid;
  place-items: center;
  opacity: 0;
  transform: scale(0.78);
  transition: opacity 180ms ease, transform 220ms ease;
}

.tutorial-clue.completed .tutorial-clue-stamp {
  opacity: 0.94;
  transform: scale(1);
}

.tutorial-clue-stamp-art {
  width: 100%;
  height: 100%;
  object-fit: contain;
}

.tutorial-clue-number,
.tutorial-clue-digit {
  min-width: 0;
  min-height: 0;
  max-width: 100%;
  max-height: 100%;
}

.tutorial-clue-number {
  position: relative;
  z-index: 2;
  display: flex;
  width: 94%;
  height: 72%;
  align-items: center;
  justify-content: center;
}

.tutorial-clue-digit {
  width: auto;
  height: 100%;
  min-width: 0;
  object-fit: contain;
}

.tutorial-clue-number.multi {
  width: 92%;
  height: 72%;
}

.tutorial-clue-number.multi .tutorial-clue-digit {
  width: 47%;
  margin-inline: -9%;
}

.tutorial-selection-preview {
  z-index: 8;
  background-color: var(--preview-color);
  background-image: var(--preview-texture);
  background-position: center;
  background-size: cover;
  box-shadow: inset 0 0 0 3px rgba(255, 255, 255, 0.48);
}

.tutorial-selection-preview.pale {
  opacity: 0.42;
}

.tutorial-selection-preview.exact {
  opacity: 0.94;
}

.tutorial-board.invalid {
  animation: board-shake 240ms ease-in-out;
}

.tutorial-dots {
  display: flex;
  min-height: 42px;
  align-items: center;
  justify-content: center;
  gap: 18px;
}

.tutorial-dot {
  width: 13px;
  height: 13px;
  border: 2px solid rgba(119, 66, 56, 0.18);
  border-radius: 50%;
  background: rgba(119, 66, 56, 0.18);
  transition: background 180ms ease, transform 180ms ease;
}

.tutorial-dot.active {
  background: var(--ink);
  transform: scale(1.08);
}

.tutorial-result {
  position: absolute;
  inset: 0;
  z-index: 20;
  display: grid;
  align-content: center;
  justify-items: center;
  padding: 40px 28px max(40px, env(safe-area-inset-bottom));
  background: rgba(255, 249, 241, 0.94);
  text-align: center;
  opacity: 0;
}

.tutorial-result.visible {
  opacity: 1;
  animation: tutorial-result-in 460ms cubic-bezier(0.16, 0.88, 0.24, 1.2) both;
}

.tutorial-result-mark {
  position: relative;
  width: 190px;
  height: 190px;
  margin-bottom: 22px;
}

.tutorial-result-block {
  position: absolute;
  border: 4px solid var(--ink);
  background-color: #F7BD87;
}

.tutorial-result-block.top {
  top: 16px;
  left: 22px;
  width: 146px;
  height: 58px;
  border-radius: 18px 22px 11px 10px;
  background-color: #f7bd87;
}

.tutorial-result-block.left {
  top: 70px;
  left: 22px;
  width: 58px;
  height: 92px;
  border-radius: 9px 9px 12px 22px;
  background-color: #f49aa4;
}

.tutorial-result-block.right {
  top: 70px;
  left: 76px;
  width: 92px;
  height: 92px;
  border-radius: 9px 9px 22px 10px;
  background-color: #8fafe8;
}

.tutorial-result-check {
  position: absolute;
  top: 59px;
  left: 65px;
  z-index: 3;
  width: 62px;
  height: 62px;
  border: 4px solid var(--ink);
  border-radius: 50%;
  background: var(--cream);
}

.tutorial-result-check::after {
  position: absolute;
  top: 13px;
  left: 20px;
  width: 15px;
  height: 28px;
  border-right: 6px solid var(--ink);
  border-bottom: 6px solid var(--ink);
  content: "";
  transform: rotate(42deg);
}

.tutorial-result h2 {
  margin: 0;
  color: #ec7180;
  font-size: 42px;
}

.tutorial-result p {
  max-width: 420px;
  margin: 14px 0 34px;
  color: #674841;
  font-size: 21px;
  line-height: 1.55;
}

.tutorial-result button {
  width: min(420px, 100%);
  min-height: 62px;
  border: 4px solid var(--ink);
  border-radius: 18px 16px 20px 15px;
  background: #ffe17d;
  color: var(--ink);
  font-size: 22px;
  font-weight: 900;
  box-shadow: 0 7px 0 #d7a454;
  cursor: pointer;
}

@keyframes tutorial-result-in {
  0% { opacity: 0; transform: translateY(12px) scale(0.9); }
  52% { opacity: 1; transform: translateY(-2px) scale(1.05); }
  78% { transform: translateY(1px) scale(0.98); }
  100% { opacity: 1; transform: translateY(0) scale(1); }
}

.game-shell {
  position: relative;
  display: grid;
  grid-template-rows: auto auto minmax(0, 1fr) auto auto;
  width: min(100%, 620px);
  height: 100dvh;
  min-height: 100dvh;
  margin: 0 auto;
  padding: max(12px, env(safe-area-inset-top)) 18px max(16px, env(safe-area-inset-bottom));
  overflow: visible;
  background-image: url("assets/axolotl/background.svg");
  background-position: center;
  background-repeat: no-repeat;
  background-size: cover;
  isolation: isolate;
}

.topbar {
  display: grid;
  grid-template-columns: var(--header-control-size) minmax(0, 1fr) var(--header-control-size);
  grid-template-areas: "back . settings";
  gap: 9px;
  align-items: center;
  width: 100%;
}

.icon-button,
.tool-button {
  position: relative;
  display: grid;
  place-items: center;
  padding: 0;
  border: 0;
  background: transparent;
  cursor: pointer;
  touch-action: manipulation;
  transition: filter 120ms ease, transform 120ms ease;
}

.icon-button {
  width: var(--header-control-size);
  height: var(--header-control-size);
}

.icon-button img {
  width: 100%;
  height: 100%;
  object-fit: contain;
}

.icon-button:hover,
.tool-button:hover {
  filter: brightness(1.025);
  transform: translateY(-2px);
}

.icon-button:active,
.tool-button:active {
  transform: translateY(1px) scale(0.975);
}

.icon-button:focus-visible,
.tool-button:focus-visible,
.completion button:focus-visible {
  outline: 4px solid rgba(83, 153, 187, 0.55);
  outline-offset: 2px;
}

.progress-area {
  display: flex;
  min-height: 94px;
  align-items: center;
  padding: 14px 66px 16px 82px;
}

.progress-track {
  position: relative;
  width: 100%;
  height: 36px;
  --progress-ink: #7a4139;
  /* 边宽和四个外圆角都走变量：填充层要按「外圆角 − 边宽」算出内圆角才能严丝合缝
     贴住描边。以前填充的圆角是手填死的 9/7/10/8，窄屏边宽降到 3px 后内圆角变成
     11/9/12/10，中间就夹出一条 2px 的奶油色月牙——看起来像条里还套了一条。
     track 不能加 overflow: hidden，挑战角标是它的 ::after 且挂在条外面。 */
  --track-border: 4px;
  --track-r1: 14px;
  --track-r2: 12px;
  --track-r3: 15px;
  --track-r4: 13px;
  border: var(--track-border) solid var(--ink);
  border-radius: var(--track-r1) var(--track-r2) var(--track-r3) var(--track-r4);
  background: var(--cream);
  /* 这里不能用 `0 3px 0` 那种贴纸投影。投影是纯向下偏移的，而左右两个圆角有
     13-15px 那么大，阴影的弧和边框的弧在角上错开，会在条外面露出一道独立的
     月牙——玩家看到的就是"多出一层进度条"。改成内阴影：立体感留着，
     元素外面不再有第二个轮廓。 */
  box-shadow: inset 0 -2px 0 rgba(119, 66, 56, 0.1);
}

.progress-fill {
  position: relative;
  overflow: hidden;
  width: 0;
  height: 100%;
  border-radius:
    calc(var(--track-r1) - var(--track-border))
    calc(var(--track-r2) - var(--track-border))
    calc(var(--track-r3) - var(--track-border))
    calc(var(--track-r4) - var(--track-border));
  background: linear-gradient(90deg, #F5B7C8 0%, #E5B8D6 38%, #A9D4E4 72%, #A6DCC9 100%);
  transition: width 320ms cubic-bezier(0.18, 0.82, 0.22, 1);
}

.progress-fill::after {
  position: absolute;
  inset: 0;
  content: "";
  /* 缓缓上浮的气泡群，替代原来刺眼的白色扫光 */
  background-image:
    radial-gradient(circle at 12% 78%, rgba(255, 255, 255, 0.85) 0 2.1px, transparent 2.6px),
    radial-gradient(circle at 34% 86%, rgba(255, 255, 255, 0.62) 0 1.5px, transparent 2px),
    radial-gradient(circle at 57% 74%, rgba(255, 255, 255, 0.78) 0 2.6px, transparent 3.1px),
    radial-gradient(circle at 78% 88%, rgba(255, 255, 255, 0.55) 0 1.7px, transparent 2.2px),
    radial-gradient(circle at 92% 70%, rgba(255, 255, 255, 0.7) 0 2px, transparent 2.5px);
  background-size: 52px 100%, 44px 100%, 68px 100%, 38px 100%, 58px 100%;
  transform: none;
  animation: progress-bubbles 5.2s linear infinite;
}

.progress-mascot,
.progress-food {
  position: absolute;
  z-index: 3;
  object-fit: contain;
  pointer-events: none;
}

.progress-mascot {
  left: 0;
  width: 92px;
  height: auto;
  bottom: 50%;
  transform: translate(-54%, 25px);
  transition: left 240ms cubic-bezier(0.18, 0.82, 0.22, 1);
}

.progress-mascot.eating {
  width: 154px;
  height: auto;
  transform: translate(-50%, 30px);
  transition: none;
}

.progress-track.completion-shimmer {
  animation: progress-track-glow 200ms cubic-bezier(0.2, 0.82, 0.25, 1) both;
}

.progress-track.completion-shimmer .progress-fill::after {
  animation: progress-bubbles-burst 620ms cubic-bezier(0.2, 0.8, 0.3, 1) both;
}

.progress-food {
  top: 50%;
  right: -42px;
  width: 46px;
  height: auto;
  transform: translateY(-50%);
  transition: opacity 180ms ease, transform 220ms ease;
}

.progress-food.eaten {
  opacity: 0;
  transform: translateY(-50%) scale(0.65);
}

.progress-impact {
  position: absolute;
  top: 50%;
  right: -10px;
  z-index: 5;
  width: 54px;
  height: 54px;
  opacity: 0;
  pointer-events: none;
  transform: translate(50%, -50%);
}

.progress-impact::before,
.progress-impact::after {
  position: absolute;
  top: 50%;
  left: 50%;
  width: 42px;
  height: 25px;
  border-top: 4px solid rgba(117, 66, 56, 0.84);
  border-radius: 50%;
  content: "";
  opacity: 0;
}

.progress-impact::before {
  transform: translate(-50%, -50%) rotate(26deg) scale(0.4);
}

.progress-impact::after {
  transform: translate(-50%, -50%) rotate(-28deg) scale(0.4);
}

.progress-impact.visible {
  opacity: 1;
}

.progress-impact.visible::before,
.progress-impact.visible::after {
  animation: progress-impact-lines 180ms ease-out both;
}

.progress-track.impacting .progress-food {
  transform-origin: 18% 50%;
  animation: progress-endpoint-impact 180ms cubic-bezier(0.16, 0.88, 0.24, 1.24) both;
}

.completion-effects {
  position: fixed;
  inset: 0;
  z-index: 24;
  display: none;
  overflow: visible;
  pointer-events: none;
}

.completion-effects.active {
  display: block;
}

.stamp-flight-layer {
  position: absolute;
  inset: 0;
  overflow: visible;
}

.completion-stamp-flight {
  position: fixed;
  z-index: 1;
  display: block;
  object-fit: contain;
  pointer-events: none;
  transform-origin: 50% 50%;
  will-change: transform, opacity;
}

.board.stamps-in-flight .clue.completed .clue-stamp {
  opacity: 0;
  transition: opacity 80ms linear;
}

.stamp-flight-layer.preparing .completion-stamp-flight {
  animation: stamp-launch-prep 140ms cubic-bezier(0.18, 0.9, 0.22, 1.28) both;
}

.stamp-flight-layer.flying .completion-stamp-flight {
  animation: stamp-flight 340ms linear both;
}

.board-frame {
  position: relative;
  width: min(
    100%,
    560px,
    calc((100dvh - 300px) * var(--board-scale, 0.875))
  );
  max-width: 100%;
  aspect-ratio: var(--board-ratio, 7 / 8);
  margin-inline: auto;
  align-self: center;
  container-type: inline-size;
  isolation: isolate;
  overflow: visible;
  border-radius: 20px;
}

.board-frame-art {
  position: absolute;
  inset: 0;
  z-index: 1;
  box-sizing: border-box;
  border: 10.5263cqw solid transparent;
  border-image-source: url("assets/axolotl/board-frame.svg");
  border-image-slice: 64 fill;
  border-image-width: 1;
  border-image-repeat: round;
  pointer-events: none;
}

.board {
  position: absolute;
  top: 6.2%;
  right: 5.4%;
  bottom: 5.4%;
  left: 6.8%;
  z-index: 3;
  overflow: visible;
  border: 1.5px solid rgba(162, 126, 116, 0.34);
  border-radius: 12px;
  background: var(--board-surface);
  touch-action: none;
  user-select: none;
}

.cell-grid,
.clue-layer {
  position: absolute;
  inset: 0;
  display: grid;
  grid-template-columns: repeat(var(--board-cols), 1fr);
  grid-template-rows: repeat(var(--board-rows), 1fr);
}

.cell-grid {
  z-index: 3;
  pointer-events: none;
}

.cell {
  position: relative;
}

.cell.has-right-line {
  border-right: 1.5px solid rgba(162, 126, 116, 0.34);
}

.cell.has-bottom-line {
  border-bottom: 1.5px solid rgba(162, 126, 116, 0.34);
}

.tail-layer,
.region-layer,
.clue-layer {
  pointer-events: none;
}

.region-layer {
  position: absolute;
  inset: 0;
  overflow: visible;
}

.tail-layer {
  position: absolute;
  top: 6.2%;
  right: 5.4%;
  bottom: 5.4%;
  left: 6.8%;
  z-index: 6;
  overflow: visible;
}

.region-layer {
  z-index: 4;
}

.clue-layer {
  z-index: 12;
}

.dismissal-layer {
  position: absolute;
  inset: 0;
  z-index: 11;
  overflow: visible;
  pointer-events: none;
}

.region,
.selection-preview,
.hint-preview {
  position: absolute;
  border-radius: 12px 10px 13px 11px;
}

.region {
  z-index: 2;
  background-color: var(--region-color);
  background-image: var(--region-texture);
  background-position: center;
  background-size: cover;
  box-shadow: inset 0 0 18px rgba(255, 255, 255, 0.16);
}

.region.region-committing {
  --tail-reveal-delay: 220ms;
  animation: region-commit-pulse 440ms cubic-bezier(0.2, 0.84, 0.25, 1) both;
}

.region-gill-anchor.region-committing {
  --tail-reveal-delay: 220ms;
}

.region-gill-anchor.region-committing .region-gill {
  animation-delay: var(--tail-reveal-delay);
}

.region::before,
.selection-preview::before {
  position: absolute;
  inset: -1px;
  z-index: 3;
  border-radius: inherit;
  content: "";
  background:
    repeating-linear-gradient(90deg, rgba(117, 66, 56, 0.72) 0 9px, transparent 9px 15px) top left / 15px 2px repeat-x,
    repeating-linear-gradient(90deg, rgba(117, 66, 56, 0.72) 0 9px, transparent 9px 15px) bottom left / 15px 2px repeat-x,
    repeating-linear-gradient(180deg, rgba(117, 66, 56, 0.72) 0 9px, transparent 9px 15px) top left / 2px 15px repeat-y,
    repeating-linear-gradient(180deg, rgba(117, 66, 56, 0.72) 0 9px, transparent 9px 15px) top right / 2px 15px repeat-y;
  pointer-events: none;
}

.region-gill,
.region-tail {
  position: absolute;
  z-index: 5;
  object-fit: contain;
  pointer-events: none;
}

.region-gill-anchor,
.region-tail-anchor {
  position: absolute;
}

.region-gill {
  top: -26px;
  width: 62px;
  height: 53px;
  animation: accessory-in 300ms 90ms ease-out both;
}

.region-gill.left {
  left: -4px;
  object-position: left bottom;
}

.region-gill.right {
  right: -4px;
  object-position: right bottom;
}

.region-tail {
  animation: accessory-in 340ms 120ms ease-out both;
}

.region-tail-anchor.region-committing {
  --tail-reveal-delay: 220ms;
  animation: accessory-in 260ms var(--tail-reveal-delay) ease-out both;
}

.region-tail-anchor.region-committing .region-tail {
  animation-delay: 280ms;
  animation-duration: 300ms;
  animation-iteration-count: 1;
  animation-timing-function: ease-in-out;
}

.region-tail-anchor.region-committing .region-tail.bottom {
  animation-name: tail-sway-bottom;
  transform-origin: 50% 12%;
}

.region-tail-anchor.region-committing .region-tail.left {
  animation-name: tail-sway-left;
  transform-origin: 88% 50%;
}

.region-tail-anchor.region-committing .region-tail.right {
  animation-name: tail-sway-right;
  transform-origin: 12% 50%;
}

.region-tail.bottom {
  bottom: -96px;
  left: 50%;
  width: 72px;
  height: 106px;
  transform: translateX(-50%);
}

.region-tail.left {
  top: 50%;
  left: -101px;
  width: 120px;
  height: 78px;
  transform: translateY(-50%);
}

.region-tail.right {
  top: 50%;
  right: -101px;
  width: 120px;
  height: 78px;
  transform: translateY(-50%);
}

.clue {
  position: relative;
  z-index: 1;
  display: grid;
  place-items: center;
  min-width: 0;
  min-height: 0;
}

.clue-stamp {
  position: absolute;
  /* 缩到格子右下角，避免盖住数字 */
  inset: auto 6% 6% auto;
  z-index: 0;
  display: grid;
  width: 38%;
  height: 38%;
  place-items: center;
  opacity: 0;
  transform: scale(0.78);
  transition: opacity 180ms ease, transform 220ms ease;
}

.clue.completed .clue-stamp {
  opacity: 0.72;
  transform: scale(1);
}

.clue-stamp-art {
  width: 100%;
  height: 100%;
  /* 不关掉 grid item 的自动最小尺寸，竖长的图会把 height:100% 顶掉并溢出格子 */
  min-width: 0;
  min-height: 0;
  max-width: 100%;
  max-height: 100%;
  object-fit: contain;
}

.clue-number {
  position: relative;
  z-index: 2;
  display: flex;
  width: 94%;
  height: 74%;
  /* 关闭 grid item 的自动最小尺寸，否则数字 SVG 的固有尺寸会顶开百分比约束 */
  min-width: 0;
  min-height: 0;
  align-items: center;
  justify-content: center;
  gap: 0;
}

.clue-digit {
  width: auto;
  height: 100%;
  min-width: 0;
  max-width: 100%;
  max-height: 100%;
  object-fit: contain;
}

.clue-number.multi .clue-digit {
  width: 74%;
  height: 100%;
  margin-inline: -20%;
}

.clue-number.multi {
  width: 94%;
  height: 80%;
}

.clue.completed .clue-number {
  height: 64%;
}

.clue.completed .clue-number.multi {
  height: 68%;
}

.board-frame.dense-board .region,
.board-frame.dense-board .selection-preview,
.board-frame.dense-board .hint-preview {
  border-radius: 7px 6px 8px 6px;
}

.board-frame.dense-board .region-gill,
.board-frame.dense-board .region-tail,
.board-frame.dense-board .tail-motion-lines {
  display: none;
}

.board-frame.dense-board .clue-number {
  width: 78%;
  height: 60%;
}

.board-frame.dense-board .clue-number.multi {
  width: 84%;
  height: 64%;
}

.board-frame.dense-board .clue.completed .clue-number {
  height: 54%;
}

.board.celebrating .clue-number {
  animation: clue-fade 260ms ease-out both;
}

.region.celebrating {
  animation: region-celebrate 240ms cubic-bezier(0.2, 0.85, 0.25, 1) var(--celebration-delay) both;
}

.region-gill-anchor.celebrating {
  animation: region-celebrate 240ms cubic-bezier(0.2, 0.85, 0.25, 1) var(--celebration-delay) both;
}

.region-tail-anchor.celebrating .region-tail.bottom {
  animation: tail-sway-bottom 240ms ease-in-out var(--celebration-delay) both;
  transform-origin: 50% 12%;
}

.region-tail-anchor.celebrating .region-tail.left {
  animation: tail-sway-left 320ms ease-in-out var(--celebration-delay) both;
  transform-origin: 88% 50%;
}

.region-tail-anchor.celebrating .region-tail.right {
  animation: tail-sway-right 320ms ease-in-out var(--celebration-delay) both;
  transform-origin: 12% 50%;
}

.selection-preview {
  z-index: 8;
  background-color: var(--preview-color);
  background-image: var(--preview-texture);
  background-position: center;
  background-size: cover;
  box-shadow: inset 0 0 0 3px rgba(255, 255, 255, 0.45);
  transition: opacity 100ms ease;
}

.selection-preview.pale {
  opacity: 0.42;
}

.selection-preview.exact {
  opacity: 0.94;
}

.selection-info {
  position: absolute;
  top: -34px;
  right: 2px;
  z-index: 18;
  min-width: 74px;
  padding: 5px 9px 6px;
  border: 2px solid var(--ink);
  border-radius: 11px 9px 12px 8px;
  color: var(--ink);
  background: rgba(255, 247, 233, 0.96);
  font-size: 14px;
  font-weight: 900;
  line-height: 1;
  text-align: center;
  box-shadow: 0 3px 0 rgba(117, 66, 56, 0.18);
  pointer-events: none;
}

.selection-info.exact {
  color: #326c70;
  border-color: #4d9ba3;
}

.selection-info.pale {
  color: #a65862;
  border-color: #dc7580;
}

.selection-dismissal {
  z-index: 1;
  pointer-events: none;
  transform-origin: var(--dismiss-origin-x) var(--dismiss-origin-y);
  animation: block-dismiss 380ms cubic-bezier(0.2, 0.85, 0.25, 1) both;
  will-change: scale, opacity;
}

.region.region-pressing,
.tutorial-region.region-pressing {
  z-index: 10;
  animation: region-press 180ms cubic-bezier(0.16, 0.86, 0.24, 1.22) both;
  will-change: transform;
}

.region-gill-anchor.accessory-pressing .region-gill,
.region-tail-anchor.accessory-pressing .region-tail,
.tutorial-region-gill-anchor.accessory-pressing .tutorial-region-gill,
.tutorial-region-tail-anchor.accessory-pressing .tutorial-region-tail,
.clue-stamp.clue-stamp-pressing,
.tutorial-clue-stamp.clue-stamp-pressing {
  animation: accessory-press 180ms cubic-bezier(0.16, 0.86, 0.24, 1.22) both;
  will-change: scale;
}

.region.region-removal-hold,
.tutorial-region.region-removal-hold,
.clue-stamp.clue-stamp-removal-hold,
.tutorial-clue-stamp.clue-stamp-removal-hold {
  scale: 1.08;
}

.region-gill-anchor.accessory-removing .region-gill,
.region-tail-anchor.accessory-removing .region-tail,
.tutorial-region-gill-anchor.accessory-removing .tutorial-region-gill,
.tutorial-region-tail-anchor.accessory-removing .tutorial-region-tail {
  animation: accessory-dismiss 260ms cubic-bezier(0.22, 0.72, 0.3, 1) both;
  will-change: scale, opacity;
}

.region-tail-anchor.accessory-removing .tail-motion-lines {
  opacity: 0;
}

.region.region-removing {
  z-index: 10;
  transform-origin: var(--dismiss-origin-x) var(--dismiss-origin-y);
  animation: block-dismiss 380ms cubic-bezier(0.2, 0.85, 0.25, 1) both;
  will-change: scale, opacity;
}

.tutorial-region.region-removing {
  z-index: 10;
  transform-origin: var(--dismiss-origin-x) var(--dismiss-origin-y);
  animation: block-dismiss 380ms cubic-bezier(0.2, 0.85, 0.25, 1) both;
  will-change: scale, opacity;
}

.clue-stamp.clue-stamp-removing {
  transform-origin: var(--dismiss-origin-x) var(--dismiss-origin-y);
  animation: block-dismiss 380ms cubic-bezier(0.2, 0.85, 0.25, 1) both;
  will-change: scale, opacity;
}

.tutorial-clue-stamp.clue-stamp-removing {
  transform-origin: var(--dismiss-origin-x) var(--dismiss-origin-y);
  animation: block-dismiss 380ms cubic-bezier(0.2, 0.85, 0.25, 1) both;
  will-change: scale, opacity;
}

.tutorial-dismissal-layer {
  z-index: 14;
}

.color-splash {
  position: absolute;
  z-index: 2;
  width: 0;
  height: 0;
  pointer-events: none;
}

.color-splash::before {
  position: absolute;
  top: -11px;
  left: -15px;
  width: 30px;
  height: 22px;
  border: 2px solid rgba(117, 66, 56, 0.32);
  border-radius: 58% 42% 62% 38%;
  content: "";
  background: var(--splash-color);
  animation: splash-center 360ms cubic-bezier(0.18, 0.82, 0.25, 1) both;
}

.color-splash > span {
  position: absolute;
  top: calc(var(--splash-size) / -2);
  left: calc(var(--splash-size) / -2);
  width: var(--splash-size);
  height: calc(var(--splash-size) * 1.2);
  border: 1.5px solid rgba(117, 66, 56, 0.3);
  border-radius: 60% 40% 65% 35%;
  background: var(--splash-color);
  animation: color-splash 500ms cubic-bezier(0.16, 0.78, 0.25, 1) both;
}

.hint-preview {
  z-index: 9;
  border: 5px solid #f7c64e;
  background: rgba(255, 225, 125, 0.22);
  box-shadow: 0 0 0 3px rgba(255, 255, 255, 0.75);
  animation: hint-pulse 680ms ease-in-out infinite alternate;
  pointer-events: none;
}

.hint-preview.solution-hint {
  border-color: #f2b936;
  background: rgba(255, 225, 125, 0.28);
}

.board-frame.invalid {
  animation: board-shake 240ms ease-in-out;
}

/* 提示条挂在 body 直属层，不再是 .game-shell 的一个网格行。
   原来在 shell 里，而 .game-shell[hidden] { display: none } 会把它一起藏掉——
   主界面、皮肤抽屉、排行榜弹层都在 shell 外面，于是「差N关才能上榜」
   「先起个昵称」「已加入，当前第 N 名」这些提示玩家一条都看不到。
   搬出来之后要自带底色，因为它现在会浮在任意背景上。 */
/*
 * 广告期间的交互遮罩。节点由 platform/portal/ad-pause.mjs 运行时建，
 * 只有平台版会用到——TWA 包里这条规则是死的，几十字节，不值得为它分文件。
 *
 * 【为什么必须有】
 * 平台规矩：玩家不能在**请求或播放**广告期间推进游戏，要么禁用按钮，
 * 要么盖一层挡住。禁按钮要挨个改，还容易漏掉棋盘本身的拖拽；盖一层
 * 一次到位。请求广告要几百毫秒，这期间玩家还能连点"下一关"——
 * 广告刚起来，关卡已经跳过去两关了。
 *
 * 全透明：广告是平台画在我们上层的，这一层只负责吃掉指针事件。
 * z-index 要压过所有弹层（现有最高是排行榜那层的 90）。
 */
.ad-block {
  position: fixed;
  inset: 0;
  z-index: 100;
  display: none;
  background: transparent;
}

.ad-block.is-on {
  display: block;
}

.toast {
  position: fixed;
  left: 50%;
  bottom: max(18px, env(safe-area-inset-bottom));
  z-index: 70;
  display: flex;
  max-width: min(92vw, 420px);
  min-height: 42px;
  padding: 9px 18px 10px;
  align-items: center;
  justify-content: center;
  gap: 9px;
  border: 3px solid var(--ink);
  border-radius: 15px 13px 16px 12px;
  color: var(--ink);
  background: var(--cream);
  font-size: 15px;
  font-weight: 800;
  text-align: center;
  opacity: 0;
  translate: -50% 8px;
  pointer-events: none;
  transition: opacity 160ms ease, translate 160ms ease;
}

.toast.visible {
  opacity: 1;
  translate: -50% 0;
}

.tool-tray {
  display: grid;
  grid-template-columns: repeat(5, minmax(0, var(--tool-control-size)));
  justify-content: center;
  gap: clamp(2px, 1vw, 6px);
  width: 100%;
  min-height: var(--tool-control-size);
  max-width: 500px;
  margin: 0 auto;
  transform: translateY(-14px);
}

.tool-tray.level-complete .tool-button {
  display: none;
}

.level-jump-button {
  /* 「清档」是同一个类加一个修饰类，靠这个变量把自己叠到跳关上面一格。
     窄屏的媒体查询只改这个变量，两颗按钮就一起跟着走。

     用 absolute 而不是 fixed：fixed 的参照是视口，而要让开的道具栏长在
     .game-shell（高度 100dvh）里。移动端视口高度和 dvh 常常差一截地址栏，
     两个盒子不同源，偏移量就永远对不准——之前跳关正好压在魔法按钮上就是这么来的。
     .game-shell 是 position: relative，改成 absolute 后两者同源。
     左右和上下的安全区也不用再自己算，shell 的 padding 已经含进去了。 */
  --jump-bottom: 14px;
  position: absolute;
  /* absolute 的参照是 .game-shell 的 padding box，左边就是屏幕边，
     所以这里得自己让出一点，不能写 0 */
  left: 14px;
  bottom: var(--jump-bottom);
  z-index: 18;
  padding: 3px 8px 4px;
  border: 1.5px solid rgba(119, 66, 56, 0.5);
  border-radius: 9px;
  color: var(--ink);
  background: rgba(255, 247, 233, 0.75);
  font-size: 11px;
  font-weight: 700;
  opacity: 0.38;
  cursor: pointer;
  touch-action: manipulation;
  transition: opacity 140ms ease;
}

/* 只是测试入口，不属于玩家界面：平时压到 0.38 的半透明，摸上去才亮 */
.level-jump-button:hover,
.level-jump-button:focus-visible {
  opacity: 1;
}

.level-jump-button:active {
  transform: translateY(1px) scale(0.96);
  opacity: 1;
}

.level-jump-dialog {
  position: fixed;
  inset: 0;
  z-index: 45;
  display: grid;
  place-items: center;
  padding: 24px;
  background: rgba(85, 57, 52, 0.32);
  backdrop-filter: blur(4px);
}

.level-jump-dialog[hidden] {
  display: none;
}

.level-jump-panel {
  display: grid;
  width: min(330px, 100%);
  gap: 14px;
  padding: 24px;
  border: 4px solid var(--ink);
  border-radius: 22px 19px 24px 18px;
  background: var(--cream);
  box-shadow: 0 8px 0 var(--shadow);
  animation: region-pop 420ms cubic-bezier(0.16, 0.88, 0.24, 1.2) both;
}

.level-jump-panel h2 {
  margin: 0;
  font-size: 26px;
  text-align: center;
}

.level-jump-panel label {
  font-size: 16px;
  font-weight: 900;
}

.level-jump-panel input {
  width: 100%;
  min-height: 52px;
  padding: 8px 14px;
  border: 3px solid var(--ink);
  border-radius: 13px 11px 14px 10px;
  color: var(--ink);
  background: #fffaf0;
  font-size: 24px;
  font-weight: 900;
  text-align: center;
}

.level-jump-actions {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: 12px;
}

.level-jump-actions button {
  min-height: 48px;
  border: 3px solid var(--ink);
  border-radius: 13px 11px 14px 10px;
  color: var(--ink);
  background: #fff2cf;
  font-weight: 900;
  box-shadow: 0 4px 0 rgba(160, 105, 91, 0.28);
  cursor: pointer;
}

.level-jump-actions button[type="submit"] {
  background: #ffe17d;
}

.level-next-button {
  position: relative;
  grid-column: 1 / -1;
  width: min(280px, calc(100% - 24px));
  min-height: 58px;
  justify-self: center;
  padding: 11px 24px;
  border: 4px solid var(--ink);
  border-radius: 16px 14px 18px 13px;
  background: #ffe17d;
  color: var(--ink);
  font-size: 20px;
  font-weight: 900;
  box-shadow: 0 6px 0 #d7a454;
  overflow: hidden;
  cursor: pointer;
  touch-action: manipulation;
  animation:
    next-level-pop 520ms cubic-bezier(0.16, 0.88, 0.24, 1.2) both,
    next-level-breathe 1.8s ease-in-out 520ms infinite;
}

.level-next-button::after {
  position: absolute;
  top: -45%;
  left: -32%;
  width: 22%;
  height: 190%;
  content: "";
  background: linear-gradient(
    90deg,
    transparent,
    rgba(255, 255, 255, 0.18),
    rgba(255, 255, 255, 0.82),
    rgba(255, 255, 255, 0.18),
    transparent
  );
  pointer-events: none;
  transform: skewX(-18deg) translateX(-220%);
  animation:
    next-level-shimmer 2.2s cubic-bezier(0.2, 0.72, 0.24, 1) 520ms infinite;
}

.level-next-button[hidden] {
  display: none;
}

@keyframes next-level-pop {
  0% { opacity: 0; scale: 0.72; }
  52% { opacity: 1; scale: 1.18; }
  76% { scale: 0.94; }
  100% { opacity: 1; scale: 1; }
}

@keyframes next-level-breathe {
  0%, 100% {
    transform: scale(1);
    box-shadow: 0 6px 0 #d7a454, 0 0 0 rgba(255, 205, 86, 0);
  }
  50% {
    transform: scale(1.035);
    box-shadow: 0 6px 0 #d7a454, 0 0 18px rgba(255, 205, 86, 0.68);
  }
}

@keyframes next-level-shimmer {
  0%, 18% { transform: skewX(-18deg) translateX(-220%); }
  78%, 100% { transform: skewX(-18deg) translateX(680%); }
}

.tool-button {
  width: var(--tool-control-size);
  height: var(--tool-control-size);
  aspect-ratio: 1;
  min-width: 0;
  min-height: 0;
  justify-self: center;
  overflow: visible;
}

.tool-art {
  position: absolute;
  inset: 0;
  display: block;
  width: 100%;
  height: 100%;
  object-fit: contain;
}

.tool-button:disabled {
  cursor: default;
  filter: grayscale(0.55);
  opacity: 0.58;
}

.completion[hidden] {
  display: none;
}

.completion h1 {
  margin: 0;
  font-size: 40px;
}

.completion p {
  margin: 8px 0 20px;
  color: var(--ink-soft);
  font-size: 18px;
}

@keyframes region-pop {
  0% { opacity: 0; transform: scale(0.86); }
  52% { opacity: 1; transform: scale(1.12); }
  76% { transform: scale(0.96); }
  100% { opacity: 1; transform: scale(1); }
}

@keyframes region-commit-pulse {
  0%, 100% { transform: scale(1); }
  46% { transform: scale(1.08); }
  74% { transform: scale(0.97); }
}

@keyframes clue-fade {
  from { opacity: 1; transform: translateY(0) scale(1); }
  to { opacity: 0; transform: translateY(-4px) scale(0.92); }
}

@keyframes region-celebrate {
  0% { transform: translateY(0); }
  40% { transform: translateY(-16px); }
  70% { transform: translateY(3px); }
  86% { transform: translateY(-1px); }
  100% { transform: translateY(0); }
}

@keyframes tail-sway-bottom {
  0%, 100% { transform: translateX(-50%) rotate(0); }
  35% { transform: translateX(-50%) rotate(-16deg); }
  70% { transform: translateX(-50%) rotate(15deg); }
}

@keyframes tail-sway-left {
  0%, 100% { transform: translateY(-50%) rotate(0); }
  35% { transform: translateY(-50%) rotate(14deg); }
  70% { transform: translateY(-50%) rotate(-13deg); }
}

@keyframes tail-sway-right {
  0%, 100% { transform: translateY(-50%) rotate(0); }
  35% { transform: translateY(-50%) rotate(-14deg); }
  70% { transform: translateY(-50%) rotate(13deg); }
}

@keyframes tail-motion-lines {
  0%, 100% { opacity: 0; }
  28%, 72% { opacity: 0.9; }
}

@keyframes accessory-in {
  0% { opacity: 0; scale: 0.68; }
  54% { opacity: 1; scale: 1.14; }
  78% { scale: 0.96; }
  100% { opacity: 1; scale: 1; }
}

@keyframes region-press {
  0% { transform: scale(1); }
  68% { transform: scale(1.12); }
  100% { transform: scale(1.08); }
}

@keyframes accessory-press {
  0% { scale: 1; }
  68% { scale: 1.18; }
  100% { scale: 1.1; }
}

@keyframes progress-shimmer {
  to { transform: translateX(145%); }
}

@keyframes progress-completion-shimmer {
  0% { opacity: 0; transform: translateX(-145%) scaleX(0.72); }
  32% { opacity: 1; }
  100% { opacity: 0; transform: translateX(155%) scaleX(1.15); }
}

/* 收尾闪光。这里的常态阴影必须和 .progress-track 保持一致的 inset 写法——
   写成 `0 3px 0` 会在过关那一刻把圆角外的月牙又请回来 */
@keyframes progress-track-glow {
  0%, 100% { filter: brightness(1); box-shadow: inset 0 -2px 0 rgba(119, 66, 56, 0.1); }
  44% {
    filter: brightness(1.12);
    box-shadow: 0 0 13px rgba(255, 211, 105, 0.92), inset 0 -2px 0 rgba(119, 66, 56, 0.1);
  }
}

@keyframes stamp-launch-prep {
  0% { opacity: 1; transform: translate3d(0, 0, 0) scale(1); }
  34% { opacity: 1; transform: translate3d(0, -4px, 0) scale(1.22); }
  62% { opacity: 1; transform: translate3d(0, 2px, 0) scale(0.9); }
  82% { opacity: 1; transform: translate3d(0, -1px, 0) scale(1.08); }
  100% { opacity: 1; transform: translate3d(0, 0, 0) scale(1); }
}

@keyframes stamp-flight {
  0% {
    opacity: 1;
    transform: translate3d(0, 0, 0) rotate(0) scale(1);
  }
  14% {
    opacity: 1;
    transform:
      translate3d(var(--stamp-curve-1-x), var(--stamp-curve-1-y), 0)
      rotate(var(--stamp-rotation))
      scale(1);
  }
  32% {
    opacity: 1;
    transform:
      translate3d(var(--stamp-curve-2-x), var(--stamp-curve-2-y), 0)
      rotate(var(--stamp-rotation))
      scale(1);
  }
  50% {
    opacity: 1;
    transform:
      translate3d(var(--stamp-curve-3-x), var(--stamp-curve-3-y), 0)
      rotate(var(--stamp-rotation))
      scale(1);
  }
  68% {
    opacity: 1;
    transform:
      translate3d(var(--stamp-curve-4-x), var(--stamp-curve-4-y), 0)
      rotate(var(--stamp-rotation))
      scale(1);
  }
  80% {
    opacity: 1;
    transform:
      translate3d(var(--stamp-curve-5-x), var(--stamp-curve-5-y), 0)
      rotate(var(--stamp-rotation))
      scale(1);
  }
  86% {
    opacity: 1;
    transform:
      translate3d(var(--stamp-target-x), var(--stamp-target-y), 0)
      rotate(var(--stamp-rotation))
      scale(1.22, 0.62);
  }
  92% {
    opacity: 1;
    transform:
      translate3d(var(--stamp-target-x), var(--stamp-target-y), 0)
      translateY(-10px)
      rotate(var(--stamp-rotation))
      scale(0.88, 1.06);
  }
  96% {
    opacity: 1;
    transform:
      translate3d(var(--stamp-target-x), var(--stamp-target-y), 0)
      translateY(-2px)
      rotate(var(--stamp-rotation))
      scale(1.12);
  }
  100% {
    opacity: 0;
    transform:
      translate3d(var(--stamp-target-x), var(--stamp-target-y), 0)
      rotate(var(--stamp-rotation))
      scale(1.45);
  }
}

@keyframes progress-endpoint-impact {
  0%, 100% { transform: translateY(-50%) translateX(0) scale(1); }
  22% { transform: translateY(-50%) translateX(9px) scale(0.66, 1.36); }
  52% { transform: translateY(-50%) translateX(-6px) scale(1.28, 0.8); }
  78% { transform: translateY(-50%) translateX(3px) scale(0.92, 1.09); }
}

@keyframes progress-impact-lines {
  0% { opacity: 0; scale: 0.38; }
  42% { opacity: 0.9; scale: 1.08; }
  100% { opacity: 0; scale: 1.32; }
}

@keyframes heart-pop {
  0% { opacity: 0; transform: translateY(8px) rotate(45deg) scale(0.35); }
  22% { opacity: 1; transform: translateY(0) rotate(45deg) scale(1.18); }
  34% { opacity: 1; transform: translateY(-2px) rotate(45deg) scale(0.92); }
  46% { opacity: 1; transform: translateY(-4px) rotate(45deg) scale(1); }
  100% { opacity: 0; transform: translateY(-22px) rotate(45deg) scale(0.72); }
}

@keyframes mascot-settle {
  0% { opacity: 0.4; scale: 0.82; }
  52% { opacity: 1; scale: 1.1; }
  78% { scale: 0.97; }
  100% { opacity: 1; scale: 1; }
}

@keyframes board-shake {
  0%, 100% { transform: translateX(0); }
  25% { transform: translateX(-6px); }
  75% { transform: translateX(6px); }
}

@keyframes hint-pulse {
  from { opacity: 0.62; transform: scale(0.99); }
  to { opacity: 1; transform: scale(1.015); }
}

@keyframes accessory-dismiss {
  0% { opacity: 1; scale: 1.1; }
  42% { opacity: 0.72; scale: 0.82; }
  100% { opacity: 0; scale: 0; }
}

@keyframes block-dismiss {
  0% { opacity: var(--dismiss-opacity, 1); scale: var(--dismiss-start-scale, 1.08); }
  24% { opacity: var(--dismiss-opacity, 1); scale: 0.92; }
  54% { opacity: 0.9; scale: 0.58; }
  100% { opacity: 0; scale: 0; }
}

@keyframes splash-center {
  0% { opacity: 0; scale: 0.35; }
  35% { opacity: 0.92; scale: 1.12; }
  100% { opacity: 0; scale: 0.72; }
}

@keyframes color-splash {
  0% { opacity: 0; translate: 0 0; scale: 0.4; }
  24% { opacity: 1; scale: 1.08; }
  100% {
    opacity: 0;
    translate: var(--splash-x) var(--splash-y);
    scale: 0.72;
  }
}

@media (min-width: 760px) and (min-height: 900px) {
  .game-shell {
    height: min(960px, 100dvh);
    min-height: min(960px, 100dvh);
    margin-block: max(0px, calc((100dvh - 960px) / 2));
    box-shadow: 0 18px 45px rgba(91, 66, 60, 0.18);
  }
}

@media (min-width: 500px) {
  .board-frame {
    margin-inline: auto;
  }

  .toast {
    min-height: 34px;
  }

  .tool-tray {
    max-width: 400px;
  }
}

@media (max-width: 430px) {
  .game-shell {
    --header-control-size: 52px;
    --tool-control-size: 66px;
    padding-inline: 6px;
  }

  .topbar {
    gap: 6px;
  }

  .icon-button {
    width: var(--header-control-size);
    height: var(--header-control-size);
  }

      .progress-area {
    min-height: 68px;
    padding: 8px 46px 10px 56px;
  }

  .progress-track {
    height: 30px;
    /* 改变量而不是 border-width，填充的内圆角才会跟着一起变 */
    --track-border: 3px;
  }

  .progress-mascot {
    width: 73px;
    height: auto;
    transform: translate(-54%, 22px);
  }

  .progress-mascot.eating {
    width: 128px;
    height: auto;
    transform: translate(-82%, 26px);
  }

  .progress-food {
    right: -34px;
    width: 36px;
    height: auto;
  }

  .region-gill {
    top: -12px;
    width: 28px;
    height: 30px;
  }

  .region-tail.bottom {
    bottom: -46px;
    width: 33px;
    height: 53px;
  }

  .region-tail.left {
    left: -47px;
    width: 57px;
    height: 39px;
  }

  .region-tail.right {
    right: -47px;
    width: 57px;
    height: 39px;
  }

  .toast {
    min-height: 28px;
    font-size: 13px;
  }

  .tool-tray {
    margin-top: -2px;
  }

  .selection-info {
    top: -31px;
    min-width: 68px;
    font-size: 13px;
  }

  .level-jump-button {
    left: 10px;
    /* 窄屏道具栏会横满整行，两颗按钮得整体抬到它上面去：
       道具栏本体（--tool-control-size）+ 它下面那截（shell 的下内边距、
       道具恢复计时那一行、道具栏自己的 translateY(-14)）+ 按钮自身高度。
       计时文案长在道具栏「下面」，它一出现是把道具栏往上顶，间距只会变大，
       所以按没有计时文案的情况卡就够。 */
    --jump-bottom: calc(var(--tool-control-size) + 58px);
    padding: 2px 6px 3px;
    font-size: 10px;
  }

}

@media (orientation: portrait) {
  .board-frame {
    width: min(calc(100% - 2px), 560px, calc((100dvh - 270px) * var(--board-scale, 0.875)));
  }
}

@media (max-height: 720px) and (orientation: portrait) {
  .game-shell {
    padding-top: max(8px, env(safe-area-inset-top));
    padding-bottom: max(8px, env(safe-area-inset-bottom));
  }

  .progress-area {
    padding-block: 8px 9px;
  }

  .board-frame {
    width: min(calc(100% - 2px), calc((100dvh - 230px) * var(--board-scale, 0.875)));
  }

  .toast {
    min-height: 28px;
  }

  .tool-tray {
    margin-top: -4px;
  }
}

@media (max-height: 760px) and (orientation: landscape) {
  /*
   * 横屏（也就是桌面）布局：棋盘在中间，道具栏立到右边。
   *
   * 【为什么左边要留一列空的】
   * 原来是「棋盘列 + 道具列」两列。整个 shell 在视口里是居中的，
   * 但棋盘只占左边那一列——于是棋盘的中心比视口中心偏左整整 95px
   * （1280 宽实测：棋盘中心 545，视口中心 640）。人眼对这个很敏感，
   * 看上去就是"整个游戏歪了"。
   *
   * 加一列和道具栏等宽的空列把它配平：170 + 20 + 内容 + 20 + 170，
   * 左右对称，棋盘中心自然落在视口中心。空列不放任何东西，
   * 纯占位——比给 shell 加 padding 干净，padding 会连带把
   * 顶栏和进度条一起推歪。
   *
   * Poki 桌面端本来就是 16:9 里"可玩区居中、左右留给图形"的形态，
   * 配平之后正好是他们要的那个样子。
   */
  .game-shell {
    width: min(100%, 980px);
    display: grid;
    grid-template-columns: 170px minmax(380px, 1fr) 170px;
    grid-template-rows: auto auto minmax(0, 1fr);
    column-gap: 20px;
  }

  .topbar,
  .progress-area,
  .board-frame {
    grid-column: 2;
  }

  .board-frame {
    width: auto;
    height: 100%;
    max-height: 100%;
    justify-self: center;
  }

  .tool-tray {
    grid-column: 3;
    grid-row: 1 / 4;
    grid-template-columns: repeat(2, 1fr);
    align-content: center;
  }

  .toast {
    display: none;
  }
}

@media (prefers-reduced-motion: reduce) {
  .selection-dismissal,
  .region.region-removing,
  .clue-stamp.clue-stamp-removing,
  .region-gill-anchor.accessory-removing .region-gill,
  .region-tail-anchor.accessory-removing .region-tail,
  .color-splash::before,
  .color-splash > span,
  .completion-stamp-flight,
  .progress-track.impacting .progress-food,
  .progress-impact.visible::before,
  .progress-impact.visible::after {
    animation-duration: 100ms !important;
    animation-timing-function: ease-out !important;
  }

  .stamp-flight-layer.flying .completion-stamp-flight {
    transform: translate3d(var(--stamp-target-x), var(--stamp-target-y), 0) scale(0.2);
  }
}

/* ===== 主界面（模式选择） ===== */

.home[hidden] {
  display: none;
}

.home {
  position: relative;
  display: grid;
  grid-template-rows: auto minmax(0, 1fr);
  width: min(100%, 620px);
  height: 100dvh;
  min-height: 100dvh;
  margin: 0 auto;
  padding:
    max(30px, env(safe-area-inset-top))
    22px
    max(26px, env(safe-area-inset-bottom));
  overflow: visible;
  background-image: url("assets/mahjong/background.svg");
  background-position: center;
  background-repeat: no-repeat;
  background-size: cover;
  isolation: isolate;
}

/* 这里原来还有一层 backdrop-filter: blur(1.5px)。去掉了：
   负 z-index 的 backdrop-filter 伪元素 + 父级 100dvh + 和 .game-shell 靠
   display 硬切换，这套组合在 iOS Safari 上会留旧帧——玩家报的「界面有时候
   出现两层」就是它。1.5px 的模糊本来也几乎看不出来，纯亏 GPU。 */
.home::before {
  position: absolute;
  inset: 0;
  z-index: -1;
  content: "";
  background: rgba(255, 249, 241, 0.5);
}

.home-header {
  display: grid;
  justify-items: center;
  gap: 2px;
  padding-top: clamp(6px, 2vh, 22px);
}

.home-mascot {
  position: relative;
  width: clamp(104px, 27vw, 148px);
  height: clamp(104px, 27vw, 148px);
  animation: home-mascot-bob 3.4s ease-in-out infinite;
}

@keyframes home-mascot-bob {
  0%, 100% { transform: translateY(0) rotate(-1.5deg); }
  50% { transform: translateY(-7px) rotate(1.5deg); }
}

.home-mascot-frame {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  object-fit: contain;
  animation: mascot-frame-cycle 1.2s steps(1, end) infinite;
}

.home-mascot-frame.frame-2 {
  animation-delay: -0.6s;
}

.home-header h1 {
  margin: 0;
  color: var(--ink);
  font-size: clamp(30px, 8.5vw, 42px);
  line-height: 1.1;
  text-shadow: 0 2px 0 rgba(255, 255, 255, 0.65);
}

.home-subtitle {
  margin: 0;
  color: var(--ink-soft);
  font-size: 16px;
  font-weight: 900;
}

.home-modes {
  display: grid;
  gap: clamp(10px, 1.8vh, 16px);
  align-content: safe center;
  width: min(420px, 100%);
  margin: 0 auto;
  padding: 6px 2px clamp(10px, 3vh, 28px);
  overflow-y: auto;
  /* 矮屏上内容常常只溢出十几像素，却会画出一整条贯穿的滚动条轨道，压在卡片右边缘上 */
  scrollbar-width: none;
}

.home-modes::-webkit-scrollbar {
  display: none;
}

/* 横向卡片：图标在左、文字在右，四张卡不必滚动即可看全 */
.home-mode-card {
  display: grid;
  grid-template-columns: clamp(48px, 13vw, 60px) minmax(0, 1fr);
  align-items: center;
  gap: clamp(11px, 3.2vw, 16px);
  padding: clamp(8px, 1.6vh, 12px) clamp(13px, 3.6vw, 18px);
  border: 4px solid var(--ink);
  border-radius: 24px 20px 25px 19px;
  color: var(--ink);
  background: var(--cream);
  box-shadow: 0 6px 0 var(--shadow);
  text-align: left;
  cursor: pointer;
  touch-action: manipulation;
  transition: transform 130ms ease, box-shadow 130ms ease;
  animation: home-card-in 460ms cubic-bezier(0.2, 0.85, 0.3, 1.15) both;
}

/* 依次浮入，进主界面时有层次感 */
.home-mode-row:nth-child(1) .home-mode-card { animation-delay: 40ms; }
.home-mode-row:nth-child(2) .home-mode-card { animation-delay: 110ms; }
.home-mode-row:nth-child(3) .home-mode-card { animation-delay: 180ms; }
.home-mode-row:nth-child(4) .home-mode-card { animation-delay: 250ms; }

@keyframes home-card-in {
  0% { opacity: 0; transform: translateY(16px) scale(0.96); }
  100% { opacity: 1; transform: translateY(0) scale(1); }
}

.home-mode-card:hover {
  transform: translateY(-2px);
  box-shadow: 0 8px 0 var(--shadow);
}

.home-mode-card:active {
  transform: translateY(3px);
  box-shadow: 0 2px 0 var(--shadow);
}

.home-mode-card:focus-visible {
  outline: 4px solid rgba(83, 153, 187, 0.55);
  outline-offset: 3px;
}

.home-mode-text {
  display: grid;
  gap: 2px;
  min-width: 0;
}

.home-mode-name {
  font-size: clamp(18px, 5vw, 21px);
  font-weight: 900;
  line-height: 1.2;
}

.home-mode-desc {
  color: var(--ink-soft);
  font-size: clamp(12px, 3.3vw, 13.5px);
  font-weight: 700;
  line-height: 1.35;
}

/* 图标底托：淡色圆角块，让每种玩法有自己的色彩标识 */
.home-mode-preview {
  display: grid;
  place-items: center;
  aspect-ratio: 1;
  width: 100%;
  border-radius: 16px 13px 17px 12px;
  background: rgba(255, 225, 125, 0.28);
}

.home-mode-card.word .home-mode-preview {
  background: rgba(169, 219, 164, 0.32);
}

.home-mode-card.city .home-mode-preview {
  background: rgba(142, 217, 231, 0.32);
}

.home-mode-card.tower .home-mode-preview {
  background: rgba(185, 160, 233, 0.3);
}

@media (prefers-reduced-motion: reduce) {
  .home-mascot,
  .home-mode-card {
    animation: none;
  }
}

.home-iso {
  position: relative;
  width: 76%;
  aspect-ratio: 60 / 62;
  filter: drop-shadow(0 2px 0 rgba(160, 105, 91, 0.35));
}

.home-iso i {
  position: absolute;
  inset: 0;
}

.home-iso .iso-top {
  background: #ffe17d;
  clip-path: polygon(50% 2%, 96% 27%, 50% 52%, 4% 27%);
}

.home-iso .iso-left {
  background: #e5b97f;
  clip-path: polygon(4% 27%, 50% 52%, 50% 98%, 4% 73%);
}

.home-iso .iso-right {
  background: #c9985f;
  clip-path: polygon(96% 27%, 50% 52%, 50% 98%, 96% 73%);
}

.home-iso-tower {
  position: relative;
  width: 82%;
  aspect-ratio: 60 / 66;
}

.home-iso-tower .home-iso.small {
  position: absolute;
  left: 10%;
  width: 80%;
  height: 76%;
}

.home-iso-tower .home-iso.small:not(.upper) {
  bottom: 0;
}

.home-iso-tower .home-iso.small.upper {
  top: 0;
}

.home-iso .iso-top.mint {
  background: #a9dba4;
}

.home-iso .iso-left.mint {
  background: #83b97f;
}

.home-iso .iso-right.mint {
  background: #679a64;
}

/* 卡片图标：2×2 排布，适配方形小底托 */
.home-mini {
  display: grid;
  grid-template-columns: repeat(2, 1fr);
  gap: 3px;
  width: 76%;
  aspect-ratio: 1;
}

.home-mini-cell {
  display: grid;
  place-items: center;
  border: 2px solid rgba(119, 66, 56, 0.75);
  border-radius: 7px 5px 8px 6px;
  background: var(--board-surface);
  font-size: clamp(11px, 3.2vw, 14px);
  font-weight: 900;
  line-height: 1;
}

.home-mini-cell.clue {
  color: var(--ink);
}

.home-mini-cell.fill {
  background-color: #ffe17d;
  background-image: url("assets/axolotl/textures/yellow.svg");
  background-position: center;
  background-size: cover;
}

.home-mini-cell.letter {
  color: #5c352c;
  background-color: #a9dba4;
  background-image: url("assets/axolotl/textures/mint.svg");
  background-position: center;
  background-size: cover;
  text-shadow: 0 1px 0 rgba(255, 255, 255, 0.55);
}

/* ===== 单词模式：线索格 ===== */

.clue-word {
  position: relative;
  z-index: 2;
  max-width: 96%;
  color: var(--ink);
  font-size: calc(var(--cell-px, 48px) * var(--word-scale, 0.4));
  font-weight: 900;
  line-height: 1.05;
  text-align: center;
  overflow-wrap: anywhere;
}

.clue-count {
  position: absolute;
  top: 5%;
  right: 7%;
  z-index: 2;
  color: var(--ink-soft);
  font-size: max(9px, calc(var(--cell-px, 48px) * 0.2));
  font-weight: 900;
  line-height: 1;
  opacity: 0.75;
}

.clue.completed .clue-word,
.clue.completed .clue-count {
  display: none;
}

/* 单词模式下爪印缩到角落，避免盖住铺开的首字母 */
.game-shell[data-mode="word"] .clue.completed .clue-stamp {
  inset: auto auto 6% 6%;
  width: 38%;
  height: 38%;
  opacity: 0.88;
}

.board.celebrating .clue-word {
  animation: clue-fade 260ms ease-out both;
}

/* ===== 单词模式：字母铺开 ===== */

.region-letters {
  position: absolute;
  inset: 0;
  z-index: 4;
  display: grid;
  grid-template-columns: repeat(var(--letters-cols), 1fr);
  grid-template-rows: repeat(var(--letters-rows), 1fr);
  pointer-events: none;
}

.region-letter {
  display: grid;
  place-items: center;
  color: #5c352c;
  font-size: calc(var(--cell-px, 48px) * 0.46);
  font-weight: 900;
  line-height: 1;
  text-shadow: 0 1px 0 rgba(255, 255, 255, 0.55);
  animation: letter-pop 340ms cubic-bezier(0.2, 0.85, 0.3, 1.25) both;
  animation-delay: var(--letter-delay, 0ms);
}

@keyframes letter-pop {
  0% {
    opacity: 0;
    transform: scale(0.2) translateY(6px);
  }

  62% {
    opacity: 1;
    transform: scale(1.22) translateY(-2px);
  }

  100% {
    opacity: 1;
    transform: scale(1) translateY(0);
  }
}

/* ===== 单词模式：过关单词列表 ===== */

.completion-words[hidden] {
  display: none;
}

.completion-words {
  margin: 2px 0 18px;
  padding: 12px 14px;
  border: 3px solid rgba(119, 66, 56, 0.4);
  border-radius: 16px 13px 17px 12px;
  background: rgba(255, 250, 240, 0.85);
  text-align: left;
}

.completion-words h2 {
  margin: 0 0 8px;
  font-size: 16px;
  text-align: center;
}

.completion-words ul {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: 4px 12px;
  max-height: 148px;
  margin: 0;
  padding: 0 2px;
  overflow-y: auto;
  list-style: none;
}

.completion-words li {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: 8px;
  font-size: 14px;
}

.completion-words li strong {
  font-size: 15px;
}

.completion-words li span {
  color: var(--ink-soft);
  font-weight: 700;
  white-space: nowrap;
}

@media (prefers-reduced-motion: reduce) {
  .region-letter {
    animation-duration: 100ms !important;
  }
}

/* ===== 3D 美西螈街区（three.js） ===== */

.game-shell[data-mode="city"] .board-frame-art,
.game-shell[data-mode="city"] .tail-layer,
.game-shell[data-mode="city"] .board,
.game-shell[data-mode="tower"] .board-frame-art,
.game-shell[data-mode="tower"] .tail-layer,
.game-shell[data-mode="tower"] .board,
.game-shell[data-mode="tower"] .level-jump-button {
  display: none;
}

/* 无尽塔：完成一层的全屏闪光反馈 */
.board-frame.layer-flash::after {
  position: fixed;
  inset: 0;
  z-index: 20;
  content: "";
  background: radial-gradient(circle at 50% 42%, rgba(255, 255, 255, 0.95), rgba(255, 231, 150, 0.5) 55%, transparent 78%);
  animation: layer-flash-burst 700ms ease-out both;
  pointer-events: none;
}

@keyframes layer-flash-burst {
  0% { opacity: 0; }
  18% { opacity: 1; }
  100% { opacity: 0; }
}

.city-canvas[hidden] {
  display: none;
}

.city-canvas {
  position: absolute;
  inset: 0;
  touch-action: none;
  user-select: none;
}

.city-canvas canvas {
  display: block;
  width: 100%;
  height: 100%;
}

/* ===== 通用弹层（设置 / 隐私 / 联系 / 排行榜） ===== */

.sheet[hidden] {
  display: none;
}

.sheet {
  position: fixed;
  inset: 0;
  z-index: 60;
  display: grid;
  place-items: center;
  padding: 22px;
  background: rgba(85, 57, 52, 0.34);
  backdrop-filter: blur(5px);
}

.sheet-panel {
  display: grid;
  width: min(360px, 100%);
  max-height: min(86dvh, 640px);
  gap: 12px;
  padding: 22px 22px 20px;
  overflow-y: auto;
  border: 4px solid var(--ink);
  border-radius: 26px 22px 27px 21px;
  background: var(--cream);
  box-shadow: 0 9px 0 var(--shadow);
  animation: region-pop 380ms cubic-bezier(0.16, 0.88, 0.24, 1.2) both;
}

.sheet-title {
  margin: 0;
  font-size: 25px;
  text-align: center;
}

.setting-row {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 14px;
  min-height: 46px;
  padding: 6px 2px;
  border-bottom: 2px dashed rgba(160, 105, 91, 0.28);
}

.setting-row.column {
  flex-direction: column;
  align-items: stretch;
  gap: 9px;
}

.setting-label {
  display: grid;
  gap: 1px;
  font-size: 16px;
  font-weight: 900;
}

.setting-note {
  color: var(--ink-soft);
  font-size: 11.5px;
  font-weight: 700;
}

/* 开关 */
.switch {
  position: relative;
  flex: 0 0 auto;
  width: 58px;
  height: 32px;
  padding: 0;
  border: 3px solid var(--ink);
  border-radius: 999px;
  background: #e8ded2;
  cursor: pointer;
  touch-action: manipulation;
  transition: background 160ms ease;
}

.switch[aria-checked="true"] {
  background: #a9dba4;
}

.switch:disabled {
  opacity: 0.45;
  cursor: not-allowed;
}

.switch-knob {
  position: absolute;
  top: 50%;
  left: 3px;
  width: 20px;
  height: 20px;
  border: 2.5px solid var(--ink);
  border-radius: 50%;
  background: var(--cream);
  transform: translateY(-50%);
  transition: left 160ms cubic-bezier(0.2, 0.85, 0.3, 1.1);
}

.switch[aria-checked="true"] .switch-knob {
  left: 27px;
}

.switch:focus-visible {
  outline: 4px solid rgba(83, 153, 187, 0.55);
  outline-offset: 3px;
}

/* 语言 */
.lang-group {
  display: grid;
  grid-template-columns: repeat(3, 1fr);
  gap: 7px;
}

.lang-button {
  min-height: 40px;
  padding: 4px 6px;
  border: 3px solid var(--ink);
  border-radius: 12px 10px 13px 9px;
  color: var(--ink);
  background: #fff2cf;
  font-size: 13.5px;
  font-weight: 900;
  cursor: pointer;
  touch-action: manipulation;
}

.lang-button[aria-pressed="true"] {
  background: #ffe17d;
  box-shadow: inset 0 0 0 2px rgba(119, 66, 56, 0.35);
}

/* 链接与关闭 */
.sheet-links {
  display: grid;
  gap: 8px;
  padding-top: 2px;
}

.sheet-link {
  min-height: 42px;
  padding: 6px 12px;
  border: 3px solid rgba(119, 66, 56, 0.55);
  border-radius: 13px 11px 14px 10px;
  color: var(--ink);
  background: rgba(255, 250, 240, 0.9);
  font-size: 15px;
  font-weight: 900;
  text-align: left;
  cursor: pointer;
  touch-action: manipulation;
}

.sheet-version {
  margin: 0;
  color: var(--ink-soft);
  font-size: 12px;
  font-weight: 700;
  text-align: center;
}

.sheet-close {
  min-height: 48px;
  border: 4px solid var(--ink);
  border-radius: 15px 13px 16px 12px;
  background: #ffe17d;
  color: var(--ink);
  font-size: 17px;
  font-weight: 900;
  box-shadow: 0 5px 0 #d7a454;
  cursor: pointer;
  touch-action: manipulation;
}

.sheet-body {
  margin: 0;
  color: var(--ink-soft);
  font-size: 14.5px;
  font-weight: 700;
  line-height: 1.65;
}

.sheet-contact[hidden] {
  display: none;
}

.sheet-contact {
  display: flex;
  flex-wrap: wrap;
  gap: 6px;
  margin: 0;
  font-size: 14.5px;
  font-weight: 900;
}

.sheet-contact a {
  color: #5399bb;
}

/* ===== 道具计数与恢复倒计时 ===== */

.tool-count {
  position: absolute;
  right: 2%;
  bottom: 4%;
  min-width: 22px;
  padding: 1px 5px 2px;
  border: 2.5px solid var(--ink);
  border-radius: 999px;
  color: var(--ink);
  background: #ffe17d;
  font-size: 13px;
  font-weight: 900;
  line-height: 1.15;
  text-align: center;
}

.tool-button:disabled .tool-count {
  background: #e8ded2;
}

.tool-count.empty {
  background: #f0c9c4;
}

.prop-timer {
  margin: 2px 0 0;
  color: var(--ink-soft);
  font-size: 12px;
  font-weight: 700;
  text-align: center;
  min-height: 16px;
}

.prop-timer:empty {
  min-height: 0;
}

/* ===== 无尽塔排行榜 ===== */

.records-button {
  position: fixed;
  right: max(14px, env(safe-area-inset-right));
  bottom: max(16px, env(safe-area-inset-bottom));
  z-index: 18;
  width: 46px;
  height: 46px;
  padding: 0;
  border: 3px solid var(--ink);
  border-radius: 14px 12px 15px 11px;
  background: rgba(255, 247, 233, 0.94);
  font-size: 21px;
  line-height: 1;
  box-shadow: 0 3px 0 rgba(160, 105, 91, 0.3);
  cursor: pointer;
  touch-action: manipulation;
}

.records-button[hidden] {
  display: none;
}

.records-best {
  display: grid;
  justify-items: center;
  gap: 2px;
  padding: 12px;
  border: 3px solid rgba(119, 66, 56, 0.4);
  border-radius: 17px 14px 18px 13px;
  background: rgba(255, 225, 125, 0.35);
}

.records-best-label {
  color: var(--ink-soft);
  font-size: 13px;
  font-weight: 900;
}

.records-best-value {
  font-size: 34px;
  line-height: 1.05;
}

.records-list {
  display: grid;
  gap: 5px;
  margin: 0;
  padding: 0;
  list-style: none;
}

.records-list li {
  display: grid;
  grid-template-columns: minmax(46px, auto) 1fr auto;
  gap: 9px;
  align-items: baseline;
  padding: 7px 10px;
  border-radius: 11px 9px 12px 8px;
  background: rgba(255, 250, 240, 0.86);
  font-size: 14.5px;
  font-weight: 900;
}

.records-list li.is-best {
  background: rgba(255, 225, 125, 0.5);
}

.records-list .records-rank {
  color: var(--ink-soft);
  font-size: 13px;
  white-space: nowrap;
}

.records-list .records-date {
  color: var(--ink-soft);
  font-size: 12px;
  font-weight: 700;
}

.records-empty {
  margin: 0;
  padding: 14px 4px;
  color: var(--ink-soft);
  font-size: 14px;
  font-weight: 700;
  text-align: center;
}

/* 进度条内的气泡缓缓上浮（各层速度不同，制造纵深） */
@keyframes progress-bubbles {
  0% {
    background-position: 0 120%, 0 140%, 0 110%, 0 150%, 0 130%;
  }
  100% {
    background-position: 0 -40%, 0 -60%, 0 -30%, 0 -70%, 0 -50%;
  }
}

/* 通关瞬间：气泡一齐涌上来 */
@keyframes progress-bubbles-burst {
  0% {
    background-position: 0 110%, 0 120%, 0 105%, 0 125%, 0 115%;
    opacity: 1;
  }
  100% {
    background-position: 0 -60%, 0 -80%, 0 -50%, 0 -90%, 0 -70%;
    opacity: 0.35;
  }
}

/* ===== 麻将玩法 ===== */

/* 只覆盖麻将模式的背景，其他玩法仍用 .game-shell 里的美西螈水下图。
   .game-shell 没有遮罩层，这里用一层渐变当遮罩，省一个伪元素。 */
.game-shell[data-mode="mahjong"] {
  background-image:
    linear-gradient(rgba(255, 249, 241, 0.42), rgba(255, 249, 241, 0.42)),
    url("assets/mahjong/background.svg");
}

/* 线索格里的一张筒子牌。尺寸约束照抄 .clue-number/.clue-digit 那一组——
   不关掉 grid item 的自动最小尺寸，SVG 的固有尺寸会把格子顶破。 */
.clue-tile {
  position: relative;
  z-index: 2;
  display: flex;
  /* 跟牌面同比例，叠牌的定位百分比才是相对这张牌算的。
     78% 的时候牌带着投影会顶破格线、四角的牌还压到棋盘圆角外，收到 70% */
  aspect-ratio: 3 / 4;
  height: 70%;
  max-width: 84%;
  min-width: 0;
  min-height: 0;
  align-items: center;
  justify-content: center;
}

.clue-tile-art {
  width: auto;
  height: 100%;
  min-width: 0;
  max-width: 100%;
  max-height: 100%;
  object-fit: contain;
  filter: drop-shadow(0 1px 1px rgba(110, 90, 67, 0.22));
}

/* 叠牌：一副筒子只到 9，更大的数用两张叠着写，读作两张之和。
   这是记数写法不是玩法，所以两张都正常亮度、整体居中——底牌略微偏左下，
   上层压在右上角，只搭一角，两张的点子都要数得清。 */
.clue-tile.stacked {
  /* 上层牌会稍微伸出格子，别被后面的线索盖住 */
  z-index: 3;
}

.clue-tile.stacked .clue-tile-art:first-child {
  translate: -17% 11%;
}

.clue-tile-art.overlay {
  position: absolute;
  top: -5%;
  right: -13%;
  width: auto;
  height: 70%;
  rotate: 8deg;
  filter: drop-shadow(-2px 3px 4px rgba(110, 90, 67, 0.4));
}

.clue.completed .clue-tile {
  height: 66%;
}

/* 完成后线索牌让位给铺开的一筒，这一格才和其他格长得一样 */
.game-shell[data-mode="mahjong"] .clue.completed .clue-tile {
  display: none;
}

/* 麻将不用美西螈的小手印记。但过关动画要把这张图克隆出来做抛物线飞行，
   所以不能 display:none —— 摆到格子正中、跟铺开的一筒同尺寸，再设成透明：
   飞行起点就正好压在那张牌上，动画看着是"牌飞了出去"。 */
.game-shell[data-mode="mahjong"] .clue-stamp {
  inset: 11%;
  width: 78%;
  height: 78%;
  opacity: 0;
}

.game-shell[data-mode="mahjong"] .clue.completed .clue-stamp {
  opacity: 0;
}

/* 棋盘外框整块换成麻将版。
   原来是把美西螈那张压到 0.28 再套滤镜洗成绿的——四角那簇羽状外鳃还在画，
   只是变淡了，凑近看照样认得出。现在换成 assets/mahjong/board-frame.svg，
   九宫格切片契约（slice 64 fill / repeat round）两张图完全一致 */
.game-shell[data-mode="mahjong"] .board-frame-art {
  border-image-source: url("assets/mahjong/board-frame.svg");
}

/* 铺满牌之后底下那层水波纹（美西螈主题的）没必要再露，留纯色当牌桌垫布 */
.game-shell[data-mode="mahjong"] .region {
  background-image: none;
}

/* 完成矩形外面那圈深棕虚线"缝线"是六角龙龙贴纸风格的。
   麻将是糖果色块 + 同色调牌面，深棕缝线压上去像多套了一层——
   换成一道柔和的白描边，只负责把相邻色块分开，不抢戏。
   拖拽预览的缝线保留：画的时候那圈"裁剪线"是有用的语义。 */
.game-shell[data-mode="mahjong"] .region::before {
  background: none;
  border: 2px solid rgba(255, 255, 255, 0.55);
}

/* 已完成矩形逐格铺一筒 */
.region-tiles {
  position: absolute;
  inset: 0;
  z-index: 4;
  display: grid;
  grid-template-columns: repeat(var(--letters-cols), 1fr);
  grid-template-rows: repeat(var(--letters-rows), 1fr);
  pointer-events: none;
}

.region-tile {
  display: grid;
  place-items: center;
  animation: tile-key-pop 240ms cubic-bezier(0.18, 0.9, 0.28, 1.3) both;
  animation-delay: var(--letter-delay, 0ms);
}

/* 一格一拍。节奏是靠「砸下去」那一下读出来的，拖长的回弹只会把拍子糊掉，
   所以 240ms 就走完，比单词那套 letter-pop 短 100ms。
   间隔写在 src/ui/tile-run.mjs 的 TILE_RUN_STAGGER_MS，和音阶共用一个数。 */
@keyframes tile-key-pop {
  0% {
    opacity: 0;
    transform: scale(0.42) translateY(-9px);
  }

  46% {
    opacity: 1;
    transform: scale(1.16) translateY(2px);
  }

  72% {
    transform: scale(0.97) translateY(0);
  }

  100% {
    opacity: 1;
    transform: scale(1) translateY(0);
  }
}

.region-tile-art {
  width: 78%;
  height: 78%;
  object-fit: contain;
  filter: drop-shadow(0 1px 2px rgba(110, 90, 67, 0.28));
}

/* 密盘（>=9 行或列）牌面要跟着缩，否则会糊成一团 */
.dense-board .clue-tile {
  width: 84%;
  height: 64%;
}

.dense-board .clue.completed .clue-tile {
  height: 56%;
}

.dense-board .region-tile-art {
  width: 88%;
  height: 88%;
}

@media (prefers-reduced-motion: reduce) {
  .region-tile {
    animation: none;
  }
}

/* 主界面的麻将卡片预览 */
.home-mode-card.mahjong .home-mode-preview {
  background: linear-gradient(150deg, #E4F0E6, #CFE2D4);
}

.home-mini.mahjong {
  display: grid;
  grid-template-columns: repeat(2, 1fr);
  grid-template-rows: repeat(2, 1fr);
  gap: 2px;
  width: 100%;
  height: 100%;
  padding: 6px;
  place-items: center;
}

.home-mini-tile {
  width: 100%;
  height: 100%;
  /* 牌图的原始尺寸是 120x160，而预览框只有五六十像素见方。
     grid 子项默认 min-height: auto，不肯缩到内容尺寸以下——
     四张牌把格子撑得比框还高，从圆角底边戳出去。
     min 尺寸清零之后 1fr 轨道才真正管得住它们 */
  min-width: 0;
  min-height: 0;
  object-fit: contain;
}


/* ===== 新手引导的麻将牌面 ===== */

.tutorial-clue-tile {
  position: relative;
  z-index: 2;
  display: flex;
  width: 92%;
  height: 78%;
  min-width: 0;
  min-height: 0;
  align-items: center;
  justify-content: center;
}

.tutorial-clue-tile-art {
  width: auto;
  height: 100%;
  min-width: 0;
  max-width: 100%;
  max-height: 100%;
  object-fit: contain;
  filter: drop-shadow(0 1px 1px rgba(110, 90, 67, 0.22));
}

/* 完成后线索牌让位给铺开的一筒 */
.tutorial-clue.completed .tutorial-clue-tile {
  display: none;
}

/* 印记留着但不显形，过关反馈仍能拿到它的位置。
   必须连 .completed 一起写，否则会被上面 0,2,0 的旧规则压过去。 */
.tutorial-clue-stamp,
.tutorial-clue.completed .tutorial-clue-stamp {
  opacity: 0;
}

.tutorial-region-tiles {
  position: absolute;
  inset: 0;
  z-index: 4;
  display: grid;
  grid-template-columns: repeat(var(--letters-cols), 1fr);
  grid-template-rows: repeat(var(--letters-rows), 1fr);
  pointer-events: none;
}

.tutorial-region-tile {
  display: grid;
  place-items: center;
  animation: tile-key-pop 240ms cubic-bezier(0.18, 0.9, 0.28, 1.3) both;
  animation-delay: var(--letter-delay, 0ms);
}

.tutorial-region-tile-art {
  width: 76%;
  height: 76%;
  object-fit: contain;
  filter: drop-shadow(0 1px 2px rgba(110, 90, 67, 0.28));
}

@media (prefers-reduced-motion: reduce) {
  .tutorial-region-tile {
    animation: none;
  }
}


/* ===== 撤销道具与粒子迸发 ===== */

/* 次数用完的道具按钮：不隐藏，压暗表示这关没了 */
.tool-button:disabled {
  cursor: default;
  filter: saturate(0.35) opacity(0.5);
  transform: none;
}

.tool-button:disabled:hover {
  filter: saturate(0.35) opacity(0.5);
  transform: none;
}

/* 粒子层盖在所有界面之上，但不吃点击 */
.particle-layer {
  position: fixed;
  z-index: 90;
  inset: 0;
  overflow: hidden;
  pointer-events: none;
}

.particle {
  position: absolute;
  width: var(--particle-size, 7px);
  height: var(--particle-size, 7px);
  border-radius: 50%;
  background: var(--particle-color, #3F9E78);
  box-shadow: 0 0 6px rgba(255, 255, 255, 0.7);
  animation: particle-fly 720ms cubic-bezier(0.18, 0.7, 0.3, 1) both;
  animation-delay: var(--particle-delay, 0ms);
}

@keyframes particle-fly {
  0% {
    opacity: 0;
    translate: -50% -50%;
    scale: 0.4;
  }

  22% {
    opacity: 1;
    scale: 1.15;
  }

  100% {
    opacity: 0;
    /* 位移在 -50% 的居中基础上叠加，所以要把两段写在一起 */
    translate: calc(-50% + var(--particle-x, 0)) calc(-50% + var(--particle-y, 0));
    scale: 0.55;
  }
}

@media (prefers-reduced-motion: reduce) {
  .particle {
    animation-duration: 1ms;
  }
}


/* ===== 玩法卡右侧的排行入口 ===== */

.home-mode-row {
  position: relative;
}

/* 卡片右侧给排行入口留出位置——入口是压在卡片上层的，不占外面一列，
   否则小屏上文字列会被挤到换行 */
.home-mode-row .home-mode-card {
  /* button 是块级里的行内块，不写宽度不会撑满行 */
  width: 100%;
  padding-right: clamp(58px, 16vw, 72px);
}

/* 卡片本身是 button，排行按钮只能做兄弟节点（button 里套 button 是非法 HTML），
   但视觉上要在卡片里，所以绝对定位压在卡片右侧 */
.home-mode-rank {
  position: absolute;
  top: 50%;
  right: clamp(9px, 2.6vw, 14px);
  z-index: 2;
  display: grid;
  width: clamp(42px, 11.5vw, 50px);
  aspect-ratio: 1;
  translate: 0 -50%;
  padding: 0;
  border: 2.5px solid var(--ink);
  border-radius: 13px 11px 14px 10px;
  background: #FBF6EA;
  box-shadow: 0 2px 0 rgba(119, 66, 56, 0.28);
  cursor: pointer;
  place-items: center;
  gap: 1px;
  transition: transform 120ms ease, box-shadow 120ms ease, filter 120ms ease;
}

.home-mode-rank:active {
  box-shadow: 0 0 0 rgba(119, 66, 56, 0.28);
  translate: 0 calc(-50% + 2px);
}

.home-mode-rank:focus-visible {
  outline: 4px solid rgba(83, 153, 187, 0.55);
  outline-offset: 2px;
}

/* 没解锁：压暗，但条件要看得见——写在图标下面，别只藏在 tooltip 里 */
.home-mode-rank.locked {
  filter: saturate(0.25);
  opacity: 0.72;
}

.home-mode-rank.locked .home-mode-rank-icon {
  width: 14px;
  height: 12px;
}

/* 奖杯用纯 CSS 拼，省一个素材 */
.home-mode-rank-icon {
  position: relative;
  width: 17px;
  height: 15px;
  border: 2.5px solid #C79A3E;
  border-top: 0;
  border-radius: 0 0 9px 9px;
  background: #F2C860;
}

.home-mode-rank-icon::before,
.home-mode-rank-icon::after {
  position: absolute;
  top: 0;
  width: 6px;
  height: 8px;
  border: 2.5px solid #C79A3E;
  border-radius: 0 0 6px 6px;
  content: "";
}

.home-mode-rank-icon::before { left: -8px; }
.home-mode-rank-icon::after { right: -8px; }

.home-mode-rank-text:empty {
  display: none;
}

.home-mode-rank-text {
  color: var(--ink);
  font-size: clamp(11px, 3vw, 12.5px);
  font-weight: 800;
  line-height: 1;
}

.home-mode-rank.joined .home-mode-rank-icon {
  width: 14px;
  height: 12px;
}


/* ===== 排行榜弹层 ===== */

.rank-note {
  margin: 0 0 10px;
  color: #9A7A6E;
  font-size: 12.5px;
  line-height: 1.5;
  text-align: center;
}

/* 作者样式里的 display 会盖过 [hidden] 的 UA 规则，必须显式压回去 */
.rank-form[hidden],
.rank-board[hidden] {
  display: none;
}

.rank-form {
  display: grid;
  gap: 12px;
}

/* 榜单最多五十行，面板要能滚 */
#rankDialog .sheet-panel {
  display: grid;
  max-height: min(78vh, 620px);
  gap: 0;
  grid-template-rows: auto auto minmax(0, 1fr) auto;
}

.rank-board {
  display: grid;
  min-height: 0;
  grid-template-rows: auto minmax(0, 1fr) auto;
}

#rankList {
  min-height: 0;
  margin: 0;
  overflow-y: auto;
}

.rank-field {
  display: grid;
  gap: 5px;
  color: var(--ink-soft);
  font-size: 13px;
  font-weight: 700;
  text-align: left;
}

.rank-field select,
.rank-field input {
  width: 100%;
  padding: 10px 12px;
  border: 2.5px solid #E7C6C9;
  border-radius: 12px;
  color: var(--ink);
  font-family: inherit;
  font-size: 15px;
  background: #FFFDF8;
}

.rank-field select:focus-visible,
.rank-field input:focus-visible {
  outline: 3px solid rgba(83, 153, 187, 0.5);
  outline-offset: 1px;
}

.rank-submit,
.rank-leave {
  padding: 11px 18px;
  border: 3px solid var(--ink);
  border-radius: 14px 12px 15px 11px;
  color: var(--ink);
  font-family: inherit;
  font-size: 15px;
  font-weight: 800;
  background: #ffe17d;
  box-shadow: 0 3px 0 rgba(119, 66, 56, 0.28);
  cursor: pointer;
}

.rank-leave {
  margin-top: 10px;
  background: #F6EDE3;
  font-size: 13px;
}

.rank-my {
  margin: 0 0 10px;
  color: var(--ink);
  font-size: 14px;
  font-weight: 800;
}

.rank-row {
  display: grid;
  grid-template-columns: 46px minmax(0, 1fr) auto;
  align-items: center;
  gap: 8px;
  padding: 7px 10px;
  border-radius: 10px;
  font-size: 13.5px;
}

/* 自己那一行高亮，省得在五十行里找 */
.rank-row.me {
  background: #FFF1C9;
  font-weight: 800;
}

.rank-place {
  color: #B9895F;
  font-weight: 800;
}

.rank-name {
  overflow: hidden;
  color: var(--ink);
  text-overflow: ellipsis;
  white-space: nowrap;
}

.rank-level {
  color: var(--ink-soft);
  font-size: 12.5px;
}


/* ===== 关卡号并进进度条 ===== */

/* 关卡号压在进度条左端，条内显示，省掉原来单独一行的胶囊 */
.progress-label {
  position: absolute;
  top: 50%;
  left: 12px;
  z-index: 4;
  color: #FFFDF6;
  font-size: clamp(14px, 4vw, 17px);
  font-weight: 900;
  letter-spacing: 0.02em;
  text-shadow: 0 1px 2px rgba(94, 55, 47, 0.55);
  transform: translateY(-50%);
  pointer-events: none;
  white-space: nowrap;
}

/* 进度很低时左端还是空槽，文字改用深色才看得清 */
.progress-track.on-empty .progress-label {
  color: var(--ink);
  text-shadow: 0 1px 0 rgba(255, 255, 255, 0.6);
}

/* 挑战关的角标改挂在条的右上角 */
.progress-track.is-challenge::after {
  position: absolute;
  top: -12px;
  right: 6px;
  z-index: 6;
  padding: 2px 8px;
  border: 2px solid var(--ink);
  border-radius: 8px 7px 9px 6px;
  color: #FFF6F2;
  font-size: 11px;
  font-weight: 800;
  background: #ec7180;
  content: attr(data-challenge);
  rotate: 3deg;
}

/* 过关：整块棋盘亮一下，代替原来那个弹窗 */
.board-frame.level-cleared {
  animation: level-cleared-glow 900ms ease-out both;
}

@keyframes level-cleared-glow {
  0% {
    filter: brightness(1);
    scale: 1;
  }

  26% {
    filter: brightness(1.16) saturate(1.12);
    scale: 1.022;
  }

  100% {
    filter: brightness(1);
    scale: 1;
  }
}

/* 「下一关」按钮的入场：收尾动画放完才亮出来 */
.level-next-button {
  opacity: 0;
  transition: opacity 260ms ease;
}

.level-next-button.ready {
  opacity: 1;
  animation: next-level-pop 420ms cubic-bezier(0.2, 0.85, 0.3, 1.3) both;
}

@keyframes next-level-pop {
  0% { scale: 0.7; }
  62% { scale: 1.08; }
  100% { scale: 1; }
}

/* 单词表从弹窗搬到道具栏上方的一条 */
.completion-words {
  width: min(100%, 420px);
  margin: 0 auto 6px;
  padding: 8px 12px;
  border-radius: 14px;
  background: rgba(255, 253, 246, 0.86);
}

.completion-words h2 {
  margin: 0 0 4px;
  color: var(--ink-soft);
  font-size: 12px;
}

.completion-words ul {
  display: flex;
  margin: 0;
  padding: 0;
  flex-wrap: wrap;
  gap: 4px 12px;
  justify-content: center;
  list-style: none;
}

@media (prefers-reduced-motion: reduce) {
  .board-frame.level-cleared,
  .level-next-button.ready {
    animation: none;
  }
}


/* 顶栏三格：返回 | 麻将牌标 | 设置 */
.topbar > #backButton { grid-column: 1; }
.topbar > .topbar-mark { grid-column: 2; }
.topbar > #settingsButton { grid-column: 3; }

/* 过关时收起跳关，底部只留「下一关」 */
.game-shell:has(.tool-tray.level-complete) .level-jump-button {
  opacity: 0;
  pointer-events: none;
}


/* ===== 更多皮肤主题 ===== */

/* 「差N关」角标要压在卡片上，卡片得先成为定位父 */
.home-mode-card {
  position: relative;
}

/* 这张卡右边没有排行按钮，把给排行按钮预留的那块位置收回来 */
.home-mode-row .home-mode-card.skins {
  padding-right: clamp(13px, 3.6vw, 18px);
}

.home-mode-card.skins .home-mode-preview {
  background: linear-gradient(150deg, #F4E8F3, #E1D2EC);
}

/* 三片叠起来的小色卡，一眼看出是「换个样子」而不是又一种玩法 */
.home-skins-stack {
  position: relative;
  display: block;
  width: 100%;
  height: 100%;
}

.home-skins-chip {
  position: absolute;
  top: 50%;
  width: 36%;
  height: 54%;
  border: 2px solid var(--ink);
  border-radius: 6px 5px 6px 5px;
  translate: -50% -50%;
}

/* 三片各占三分之一宽、彼此只搭一点点。挤在一起中间那片会把两边完全盖住，
   看上去就是一块白板 */
.home-skins-chip.one {
  left: 25%;
  z-index: 1;
  background: #F6A8BC;
  rotate: -15deg;
}

.home-skins-chip.two {
  left: 50%;
  z-index: 3;
  background: #FAD98D;
}

.home-skins-chip.three {
  left: 75%;
  z-index: 2;
  background: #A9D6BC;
  rotate: 15deg;
}

/* 抽屉里那两张卡直接复用主界面的卡片样式，只需要把外层的滚动/居中收掉。
   面板左右内边距要比别的弹层小——卡片本身已经有 4px 边框和自己的内边距，
   再套一层 22px，窄屏上「矩形面积等于数字」就被挤成两行了 */
.skins-sheet .sheet-panel {
  width: min(400px, 100%);
  padding: 20px 12px 16px;
}

.skins-sheet .sheet-title {
  margin-bottom: 2px;
}

.skins-modes {
  width: 100%;
  padding: 2px;
  overflow: visible;
}

/* 抽屉比主界面窄，给排行按钮预留的那块也要跟着收——按主界面的 16vw 留，
   「矩形面积等于数字」正好差十来个像素放不下，末尾会掉一个孤字下来 */
.skins-modes .home-mode-card {
  padding-right: 52px;
}

.skins-modes .home-mode-rank {
  right: 5px;
}

.home-mode-card.locked {
  filter: saturate(0.28);
  opacity: 0.72;
}

/* 还差几关：压在预览图左下角。右边是排行入口，占不得 */
.home-mode-lock {
  position: absolute;
  bottom: 5px;
  left: clamp(8px, 2.4vw, 12px);
  z-index: 2;
  padding: 1px 7px;
  border-radius: 999px;
  color: var(--cream);
  background: var(--ink);
  font-size: 11px;
  font-weight: 900;
  line-height: 1.5;
  white-space: nowrap;
}

/* 解锁后 textContent 被清空，别留一个空胶囊 */
.home-mode-lock:empty {
  display: none;
}

/* 点锁着的卡：主界面的 #toast 在 gameShell 里、这儿看不见，靠抖一下给反馈。
   不写 fill-mode，动画结束后立刻交回常规样式，hover 的位移才不会被顶掉 */
.home-mode-card.shake {
  animation: card-shake 360ms ease;
}

@keyframes card-shake {
  0%, 100% { transform: translateX(0); }
  15% { transform: translateX(-6px); }
  32% { transform: translateX(5px); }
  50% { transform: translateX(-4px); }
  68% { transform: translateX(3px); }
  84% { transform: translateX(-2px); }
}

@media (prefers-reduced-motion: reduce) {
  .home-mode-card.shake {
    animation: none;
  }
}


/* ===== 清档（试玩用） ===== */

/* 叠在「跳关」上面一格，两颗共用 .level-jump-button 的样式 */
.level-reset-button {
  bottom: calc(var(--jump-bottom) + 26px);
}

.reset-body {
  margin: 0;
  color: var(--ink-soft);
  font-size: 14px;
  line-height: 1.65;
}

/* 不可撤销的那一颗染成警示色，和「取消」一眼分得开 */
.sheet-close.reset-danger {
  color: #FFF6E9;
  background: #C4584E;
}


/* ===== 麻将主题的顶栏与进度条 =====
   玩家原话："顶部的美西螈要去掉，改个表达形式，ui 框也不要跟美西螈相关了"。
   六角龙龙那套表达是"小美西螈沿着进度条跑向终点的食物，过关时吃掉它"；
   麻将换成最朴素的表达：一条干净的绿色进度条，关卡号居中压在条里，
   顶栏正中摆一枚牌标。其余四种玩法仍是六角龙龙主题，吉祥物照旧。 */

.topbar-mark {
  justify-self: center;
  align-self: center;
  width: auto;
  height: clamp(42px, 11.5vw, 54px);
  /* 这是麻将的标，别的玩法不挂 */
  display: none;
  animation: mark-float 3.6s ease-in-out infinite;
}

@keyframes mark-float {
  0%, 100% { transform: translateY(0) rotate(-1.6deg); }
  50% { transform: translateY(-2.5px) rotate(1.6deg); }
}

@media (prefers-reduced-motion: reduce) {
  .topbar-mark {
    animation: none;
  }
}

.game-shell[data-mode="mahjong"] .topbar-mark,
.game-shell[data-mode="word"] .topbar-mark {
  display: block;
}

.game-shell[data-mode="mahjong"] .progress-mascot,
.game-shell[data-mode="mahjong"] .progress-food,
.game-shell[data-mode="word"] .progress-mascot,
.game-shell[data-mode="word"] .progress-food {
  display: none;
}

/* 原来左 82 右 66 的内边距是给吉祥物和食物留的溢出空间。
   它们不在了，就把条放满，左右和顶栏对齐 */
.game-shell[data-mode="mahjong"] .progress-area,
.game-shell[data-mode="word"] .progress-area {
  min-height: 0;
  padding: 4px 0 14px;
}

.game-shell[data-mode="mahjong"] .progress-label,
.game-shell[data-mode="word"] .progress-label {
  left: 50%;
  transform: translate(-50%, -50%);
}

/* 填充沿用底座那条粉→紫→蓝→薄荷的糖果渐变（矩形底色同一套），
   这套颜色经玩家确认最贴用户画像；牌面留给墨绿，条上要甜 */

/* 条里上浮的气泡是水下主题，麻将不要，留白更清爽 */
.game-shell[data-mode="mahjong"] .progress-fill::after,
.game-shell[data-mode="word"] .progress-fill::after {
  content: none;
}

/* 食物没了，撞击特效要收回条内。
   光改 right 不够：基础规则上还挂着 transform: translate(50%, -50%)，
   那是为了让特效骑在"食物"那个条外锚点上的，锚点收回条内之后这个 +50%
   会把 54px 宽的方块推出去 27px，撑出整页的横向滚动
   （LTR 下 scrollWidth 比 clientWidth 多 14px，RTL 下放大到 70px，
   界面整体错位）。所以这两种模式下把水平位移一并去掉。 */
.game-shell[data-mode="mahjong"] .progress-impact,
.game-shell[data-mode="word"] .progress-impact {
  right: 6px;
  transform: translate(0, -50%);
}

/* 拖拽预览的底纹也是美西螈的水波贴图，麻将模式下只留纯色 */
.game-shell[data-mode="mahjong"] .selection-preview {
  background-image: none;
}


/* ===== 主界面右上角的设置入口 ===== */
.home-settings {
  position: absolute;
  top: max(12px, env(safe-area-inset-top));
  right: 14px;
  z-index: 5;
}

/* .home 得当它的定位父 */
.home {
  position: relative;
}


/* ===== 落子强反馈与撤销收缩 ===== */

/* 框对那一下：一圈光边从矩形边缘扩出去再散掉 */
.region-flash {
  position: absolute;
  inset: 0;
  z-index: 6;
  border: 3px solid rgba(255, 255, 255, 0.95);
  border-radius: inherit;
  pointer-events: none;
  animation: region-flash-out 440ms cubic-bezier(0.2, 0.7, 0.3, 1) both;
}

@keyframes region-flash-out {
  0% {
    opacity: 0.95;
    transform: scale(0.94);
  }

  60% {
    opacity: 0.5;
    transform: scale(1.04);
  }

  100% {
    opacity: 0;
    transform: scale(1.08);
  }
}

/* 撤销：朝线索牌收缩回去（origin 由 JS 按线索格位置写进 style） */
.region.region-undoing {
  animation: region-undo-shrink 200ms cubic-bezier(0.5, 0, 0.75, 0.4) both;
  pointer-events: none;
}

@keyframes region-undo-shrink {
  0% {
    opacity: 1;
    transform: scale(1);
  }

  100% {
    opacity: 0;
    transform: scale(0.12);
  }
}

@media (prefers-reduced-motion: reduce) {
  .region-flash,
  .region.region-undoing {
    animation: none;
  }
}


/* 「卡住了点灯泡」时提示按钮脉冲几下。不挡操作，只把视线引过去 */
.tool-button.coach-pulse {
  animation: coach-pulse 620ms ease-in-out 4;
}

@keyframes coach-pulse {
  0%, 100% { transform: scale(1); filter: brightness(1); }
  50% { transform: scale(1.12); filter: brightness(1.14); }
}

@media (prefers-reduced-motion: reduce) {
  .tool-button.coach-pulse {
    animation: none;
  }
}


/* ===== 阿拉伯语：整体 RTL =====
   由 src/ui/sheets.mjs 的 applySettings 在 <html> 上挂 dir="rtl"。

   物理的 left/right 不会因为 dir 自动翻转，所以绝大多数装饰性定位不用管；
   真正会跟着翻的是三类：文字对齐、flex/grid 的主轴方向、逻辑属性。
   下面只处理这三类里翻错了的，以及一处绝对不能翻的。 */

/* 【绝对不能翻】棋盘。
   格子层和线索层是 CSS Grid（grid-template-columns: repeat(--board-cols, 1fr)），
   RTL 下列序会整体镜像；而矩形层、预览层、提示高亮都是 JS 按物理 left/top 写
   inline 样式的，不受 dir 影响。两边一翻一不翻就会整盘错位——线索显示在
   第 0 列，玩家画的矩形却盖在第 11 列。
   所以从 .board-frame 往下强制 ltr，玩法几何完全不受语言影响。
   引导那套棋盘是平行实现，同样处理。 */
[dir="rtl"] .board-frame,
[dir="rtl"] .tutorial-board,
[dir="rtl"] .tutorial-stage {
  direction: ltr;
}

/* 但棋盘里的文字（线索上的单词、选区读数）还是要按语言的方向排 */
[dir="rtl"] .clue-word,
[dir="rtl"] .selection-info,
[dir="rtl"] .tutorial-selection-info {
  direction: rtl;
}

/* 排行入口压在玩法卡右侧，卡片本身的 grid 在 RTL 下已经翻到左边了，
   这颗是绝对定位的物理 right，得手动挪到左边 */
[dir="rtl"] .home-mode-rank {
  right: auto;
  left: 5px;
}

[dir="rtl"] .home-mode-row .home-mode-card {
  padding-right: clamp(13px, 3.6vw, 18px);
  padding-left: clamp(58px, 16vw, 72px);
}

[dir="rtl"] .skins-modes .home-mode-card {
  padding-right: clamp(13px, 3.6vw, 18px);
  padding-left: 52px;
}

[dir="rtl"] .skins-modes .home-mode-rank {
  right: auto;
  left: 5px;
}

/* 「差N关」角标同理 */
[dir="rtl"] .home-mode-lock {
  left: auto;
  right: clamp(8px, 2.4vw, 12px);
}

/* 主界面的设置入口 */
[dir="rtl"] .home-settings {
  right: auto;
  left: 14px;
}

/* 开发用的两颗按钮 */
[dir="rtl"] .level-jump-button {
  left: auto;
  right: 14px;
}

/* 挑战角标挂在进度条右上角，RTL 下挪到左上角 */
[dir="rtl"] .progress-track.is-challenge::after {
  right: auto;
  left: 6px;
}

/* 进度条本身不翻：填充永远从左往右长，这是"进度"的通用视觉约定，
   RTL 语言的进度条也普遍保持左起。关卡号本来就居中，不受影响 */
[dir="rtl"] .progress-track {
  direction: ltr;
}

[dir="rtl"] .progress-label {
  direction: rtl;
}
