:root {
  --bg: #faf8f4;
  --fg: #2a2622;
  --fg-muted: #6b6255;
  --accent: #8a2e2e;
  --border: #ddd5c7;
  --card-bg: #ffffff;
  --serif: "Noto Serif TC", "Songti TC", "PMingLiU", Georgia, serif;
  --sans: -apple-system, BlinkMacSystemFont, "Helvetica Neue", "PingFang TC", "Microsoft JhengHei", sans-serif;
  /* immersive ink — text sits directly on the (dimmed) scene, no boxes */
  --ink: rgba(255, 255, 255, 0.95);
  --ink-soft: rgba(255, 255, 255, 0.68);
  --ink-shadow: 0 1px 10px rgba(0, 0, 0, 0.75);
  --change: #ff9a7a;
  --focus-ring: #d7bd91;
}

/* --- ambient scene tokens (time-of-day, set by app.js body.tod-*) --- */
:root {
  --sky-top: #bfe3f7; --sky-mid: #dcf0e4; --sky-bot: #f3f0d8; --sun: #fff3c4;
  --mtn: #7f9a86; --wtr-top: #bfe0e6; --wtr-bot: #6fa6ad; --ledge: #5f7a5f;
  --tree: #3f5a41; --cloud-op: 0.55; --star-op: 0;
}
body.tod-dawn {
  --sky-top: #f2b6a0; --sky-mid: #f7d9b0; --sky-bot: #fbeccb; --sun: #ffd9a0;
  --mtn: #9c7f7a; --wtr-top: #f0cdb9; --wtr-bot: #c98f83; --ledge: #7a675a;
  --tree: #5c4b3e; --cloud-op: 0.6; --star-op: 0;
}
body.tod-day {
  --sky-top: #bfe3f7; --sky-mid: #dcf0e4; --sky-bot: #f3f0d8; --sun: #fff3c4;
  --mtn: #7f9a86; --wtr-top: #bfe0e6; --wtr-bot: #6fa6ad; --ledge: #5f7a5f;
  --tree: #3f5a41; --cloud-op: 0.55; --star-op: 0;
}
body.tod-dusk {
  --sky-top: #4a3a5e; --sky-mid: #a85a52; --sky-bot: #e8a35a; --sun: #ff9d5c;
  --mtn: #443247; --wtr-top: #7a5a63; --wtr-bot: #45324a; --ledge: #362a38;
  --tree: #2c2230; --cloud-op: 0.45; --star-op: 0;
}
body.tod-night {
  --sky-top: #0c1330; --sky-mid: #16224a; --sky-bot: #263a5e; --sun: #cfd8ff;
  --mtn: #131b30; --wtr-top: #1a2947; --wtr-bot: #0c1730; --ledge: #10182a;
  --tree: #0b1220; --cloud-op: 0.25; --star-op: 1;
}

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

html, body {
  margin: 0;
  padding: 0;
  background: #0d0a16;
  color: var(--fg);
  font-family: var(--sans);
  min-height: 100%;
}
body {
  min-height: 100vh;
  overflow: hidden;
  -webkit-tap-highlight-color: transparent;
}

/* ==========================================================================
   Ambient cliff-lake SVG scene (backdrop) — preserved verbatim
   ========================================================================== */

#scene {
  position: fixed;
  inset: 0;
  z-index: 0;
  pointer-events: none;
  overflow: hidden;
  animation: breathe 14s ease-in-out infinite alternate;
  transform-origin: 50% 50%;
  /* baseline transition so home→bowed always animates (idle animation
     overrides transform while running; when bowed sets animation:none this
     transition takes over the tilt). */
  transition: transform 1.3s cubic-bezier(0.4, 0, 0.2, 1), filter 1.3s ease;
}
#scene svg { width: 100%; height: 100%; display: block; }
.scene-layer { transition: transform 0.45s ease-out; }

#scene .sky { fill: url(#sky-grad); }
#scene .sky-0 { stop-color: var(--sky-top); }
#scene .sky-1 { stop-color: var(--sky-mid); }
#scene .sky-2 { stop-color: var(--sky-bot); }
#scene .stars circle { fill: #fdf6e3; }
#scene .stars { opacity: var(--star-op); transition: opacity 0.8s ease; }
#scene .sun { fill: url(#sun-grad); }
#scene .sun-0 { stop-color: var(--sun); }
#scene .sun-1 { stop-color: var(--sun); }
#scene .sun-glow { fill: var(--sun); opacity: 0.28; filter: blur(6px); }
#scene .mtn-far { fill: var(--mtn); opacity: 0.55; }
#scene .mtn-near { fill: var(--mtn); opacity: 1; }
#scene .cloud-drift ellipse { fill: #ffffff; opacity: var(--cloud-op); }
#scene .lake { fill: url(#water-grad); }
#scene .wtr-0 { stop-color: var(--wtr-top); }
#scene .wtr-1 { stop-color: var(--wtr-bot); }
#scene .shim {
  stroke: var(--sky-top); stroke-width: 4; stroke-linecap: round;
  opacity: 0.25; animation: shim 3.4s ease-in-out infinite;
}
#scene .shim-2 { animation-delay: 0.6s; }
#scene .shim-3 { animation-delay: 1.2s; }
#scene .ledge { fill: var(--ledge); }
#scene .tree-sway {
  fill: var(--tree); transform-box: fill-box; transform-origin: 50% 100%;
  animation: sway 7s ease-in-out infinite alternate;
}
#scene .tree-right-sway { animation-duration: 8s; animation-delay: -3s; }
#scene .cloud-drift { animation: drift 46s ease-in-out infinite alternate; }
#scene .cloud-2 { animation-duration: 60s; animation-delay: -8s; }

@keyframes breathe { from { transform: scale(1); } to { transform: scale(1.015); } }
@keyframes drift { from { transform: translateX(0); } to { transform: translateX(26px); } }
@keyframes shim { 0%, 100% { opacity: 0.25; } 50% { opacity: 0.7; } }
@keyframes sway { from { transform: rotate(-1.2deg); } to { transform: rotate(1.2deg); } }

/* 耷低頭：pick/read/cast/exhausted 期間鏡頭向下望 ~30° —— 近底部為原點大幅放大
   + rotateX 透視，令地平線明顯上升、湖面 + 地面湧入眼前。幅度做大先，過火你叫細。 */
body.bowed #scene {
  animation: none;
  transform-origin: 50% 90%;
  transform: perspective(1000px) rotateX(21deg) scale(1.5);
  filter: brightness(0.55) blur(1px);
}

/* ==========================================================================
   Raster 場景 + 程式打光層（?scene=raster）—— DESIGN_SPEC §8.3/§8.6 嘅 spike
   驗緊一件事：程式打光蓋喺真實相上,四個時段仲靚唔靚、字仲讀唔讀到。
   ========================================================================== */

/* 每個時段兩組數：--ras-* 調相本身嘅光度, --grade/--scrim/--body-* 調疊上去嗰層。
   刻意同上面 SVG 嗰組 token 分開 —— SVG 路徑一個字都唔郁。*/
/* ⚠️ --ras-contrast 係 spike 實測揾返嚟嘅關鍵 operator。素材刻意平光低對比(grade
   headroom 最大),但淨係 brightness 壓暗 → 低對比暗圖 = 一嚿糊,相直情消失(夜景
   實測 bright .32 同 .5 都係一片死藍, 連關晒 tint 都救唔返)。夜景真正需要嘅係
   高局部反差(天光/地黑剪影),所以壓暗嘅同時要拉對比。 */
:root {
  --ras-bright: 1; --ras-contrast: 1; --ras-sat: 1;
  --grade-op: 0.34; --scrim-op: 0.5;
  --body-x: 70%; --body-y: 24%; --body-r: 13vmin; --body-col: #fff3c4; --body-op: 0.85;
  --seam-a: 48%; --seam-b: 58%; /* 地貌層淡出區間, 夾住實測地平線 54.6% */
  --seam-mid: 54.6%;            /* 實測地平線本身 —— ?body=horizon 用嚟擺餘光帶 */
  --sun-arc: 0.5;               /* 天體高度 0..1(app.js 寫), 0=貼地平線 1=最高 */
}
/* scrim 強度同畫面光暗成反比 —— 越光嘅時段, 白字越需要壓底先讀到。 */
body.tod-dawn { --ras-bright: 0.92; --ras-contrast: 1.30; --ras-sat: 1.05;
                --grade-op: 0.68; --scrim-op: 0.72; --body-col: #ffd9a0; --body-op: 0.70; }
body.tod-day  { --ras-bright: 1.06; --ras-contrast: 1.18; --ras-sat: 1.00;
                --grade-op: 0.50; --scrim-op: 0.70; --body-col: #fff3c4; --body-op: 0.85; }
body.tod-dusk { --ras-bright: 0.78; --ras-contrast: 1.55; --ras-sat: 1.10;
                --grade-op: 0.72; --scrim-op: 0.50; --body-col: #ff9d5c; --body-op: 0.80; }
/* 夜景係四個時段入面最弱嗰個 —— 平光素材本身冇夜間需要嘅高局部反差(天光/地黑),
   靠 contrast 1.85 硬撐返啲雲層結構。要再靚啲要另外一件夜間版地貌素材。*/
body.tod-night{ --ras-bright: 0.70; --ras-contrast: 1.85; --ras-sat: 0.90;
                --grade-op: 0.50; --scrim-op: 0.30; --body-col: #cfd8ff; --body-op: 0.55;
                --body-r: 7vmin; }

/* 卦象只供應乘數（§8.2：卦象決定光嘅性格, 時辰決定光嘅位置）。時段 × 卦象兩層
   相乘 —— 加場景唔使改 filter 式,只加一行 body[data-upper=…]。*/
:root { --gua-bright: 1; --gua-contrast: 1; --gua-sat: 1; --scrim-boost: 1; }
/* ⚠️ 乘數只表達「素材本身冇帶到嘅性格」。sky_li 本身已經係高調亮雲, 再乘 1.18
   就會過曝, 所以離只加少少暖同對比, 唔再加亮。*/
body[data-upper="離"] { --gua-bright: 1.02; --gua-contrast: 1.06; --gua-sat: 1.10; } /* 霞光、極亮天幕 */
body[data-upper="坎"] { --gua-bright: 0.86; --gua-contrast: 1.05; --gua-sat: 0.90; } /* 雨、密雲 */
body[data-upper="乾"] { --gua-bright: 1.10; --gua-contrast: 1.12; --gua-sat: 0.95; } /* 高遠澄空 */
body[data-upper="坤"] { --gua-bright: 0.90; --gua-contrast: 0.94; --gua-sat: 0.88; } /* 厚雲低垂 */
body[data-upper="震"] { --gua-bright: 0.82; --gua-contrast: 1.25; --gua-sat: 0.92; } /* 雷雲 */
body[data-upper="艮"] { --gua-bright: 0.88; --gua-contrast: 1.15; --gua-sat: 0.94; } /* 山影壓天 */

#scene-raster {
  display: none;
  position: absolute;
  inset: 0;
  filter:
    brightness(calc(var(--ras-bright) * var(--gua-bright)))
    contrast(calc(var(--ras-contrast) * var(--gua-contrast)))
    saturate(calc(var(--ras-sat) * var(--gua-sat)));
  transition: filter 1.3s ease;
}
body.scene-raster #scene svg { display: none; }
body.scene-raster #scene-raster { display: block; }

.ras { position: absolute; inset: 0; width: 100%; height: 100%; object-fit: cover; display: block; }

/* Baked scene64 圖第一幀同 app.js onload 後要完全同態，否則會先閃時段 tint／接縫，
   再先變成正式乾為天。Scrim 保留俾白字 readability，其餘兩層合成效果全部關掉。 */
body.scene64 {
  --ras-bright: 1;
  --ras-contrast: 1;
  --ras-sat: 1;
  --grade-op: 0;
  --scrim-op: 0.5;
  --scrim-boost: 1;
  --seam-a: 0%;
  --seam-b: 0%;
}
body.scene64 #scene-raster .seam { display: none; }

/* 🔴 scene64 baked 圖闊 viewport 顯示。**16:9 wide master 用 cover 通殺**(基礎
   .ras 已經 object-fit:cover) —— 真 content 延伸填滿闊 mon, 手機睇中間 subject。
   只有**直圖(4:5 過渡圖, body.scene64-portrait)**先喺闊 mon 用 contain + 模糊邊
   兜底(直圖硬 cover 落橫 mon 會又糊又裁上下)。臨界點 viewport 闊過 4:5。 */
@media (min-aspect-ratio: 4 / 5) {
  body.scene64.scene64-portrait #scene-raster .ras.sky {
    object-fit: cover;
    filter: blur(34px) brightness(0.55) saturate(1.1);
    transform: scale(1.18);
  }
  body.scene64.scene64-portrait #scene-raster .ras.ground {
    object-fit: contain;           /* 完整 4:5 主圖, 清晰置中, 兩側露出模糊 sky */
    -webkit-mask-image: none; mask-image: none;
  }
}

/* ?img= 單張全屏預覽: 9:16 素材喺闊 viewport(desktop) center-crop 會淨返一嚿暗山,
   揀住頂部嗰段(天 + 受光脊) —— SCENE_64_PLAN 實測嘅補救。手機(窄)唔受影響。*/
@media (min-aspect-ratio: 1 / 1) {
  body.img-preview .ras { object-position: 50% 18%; }
}

/* ?motion= 動勢對照 —— 單張平面 JPEG 都做到, 唔使 depth map、唔使影片。
   sky 同 ground 係同一張圖, 所以郁成一嚿, 唔做分層差速(我哋一張圖冇得分層)。
   郁幅刻意細 + 極慢 + ease-in-out alternate → 感覺係「呼吸」唔係「識飛」。
   #scene 喺 raster 已有 scale(1.16) headroom, 加呢個 1.05→1.11 唔會露邊。*/
@keyframes ras-kenburns {
  from { transform: scale(1.05) translate(-1%, -0.6%); }
  to   { transform: scale(1.12) translate(1.2%, 1%); }
}
@keyframes ras-drift {
  from { transform: scale(1.07) translate(-1.4%, 0.2%); }
  to   { transform: scale(1.07) translate(1.4%, -0.4%); }
}
body.motion-kenburns .ras { animation: ras-kenburns 42s ease-in-out infinite alternate; }
body.motion-drift .ras    { animation: ras-drift 28s ease-in-out infinite alternate; }
@media (prefers-reduced-motion: reduce) {
  body.motion-kenburns .ras, body.motion-drift .ras { animation: none; }
}
/* 地貌層上半淡走 → 露出後面天空層。呢個 mask 就係「任何天配任何地」嘅機制。*/
.ras.ground {
  -webkit-mask-image: linear-gradient(to bottom, transparent var(--seam-a), #000 var(--seam-b));
  mask-image: linear-gradient(to bottom, transparent var(--seam-a), #000 var(--seam-b));
}
/* ?body=occluded —— 唔刪圓盤, 而係修好佢。三樣嘢一次過解決, 全部因為佢由
   screen-space 嘅 #grade 搬咗入場景層:
   1. **真遮擋** —— 擺喺 .sky 同 .ground 之間, 地平線以下嗰截會俾地貌相蓋住,
      太陽落山真係會冇咗(現行版本浮喺暗山之上, 呢個係最大穿崩)。
   2. **同一 grade** —— 食 #scene-raster 個 filter(bright/contrast/sat), 同張相
      經同一條管, 唔會再係「後期疊上去」嗰種質感。
   3. **同一運鏡** —— 跟 #scene 嘅 bowed transform 一齊郁, 變成畫面入面嘅嘢。*/
.ras-body {
  display: none;
  position: absolute; inset: 0;
  /* y 錨返該張地貌相嘅真地平線(--seam-mid), 唔用 screen-space 嗰個寫死 64%。*/
  /* ⚠️ 搬入場景層之後個光**一定要重新調亮**。原本 screen-space 版睇落咁明顯,
     係因為佢畫喺 grade 之上、bypass 晒 tint 同 scrim; 一旦擺返落場景入面食同
     一條管, 一嚿平淡黃就會即刻俾中灰素材食走(day 實測完全睇唔到)。所以改成
     真光源嘅樣: 白熱核心 → 有色暈 → 長尾散射, 而唔係一嚿均勻黃。*/
  background: radial-gradient(circle calc(var(--body-r) * 2.4) at var(--body-x)
              calc(var(--seam-mid) - var(--sun-arc) * 15%),
              #ffffff 0%, var(--body-col) 16%,
              color-mix(in srgb, var(--body-col) 55%, transparent) 34%,
              rgba(255, 255, 255, 0) 100%);
  opacity: 1;
  mix-blend-mode: screen;
}
body.body-occluded .ras-body { display: block; }
/* 接縫霧帶 —— 大氣透視,真實風景相本來就有。同時係兩層嘅遮羞布。*/
.seam {
  position: absolute; left: 0; right: 0;
  top: calc(var(--seam-a) - 6%); height: 20%;
  background: linear-gradient(to bottom, transparent, var(--sky-mid) 48%, transparent);
  opacity: 0.5;
}

#grade { position: fixed; inset: 0; z-index: 0; pointer-events: none; display: none; }
body.scene-raster #grade { display: block; }

/* Landing hero 影片：只喺 home phase(唔 bowed)且有片載到先顯示。z-index 0 = 背景層,
   蓋住 #scene / photoreal 靜底,但 UI(#imm z1+) 仍喺上面。冇 has-landing-video =
   完全唔顯示 → 自動 fall back 落底層 g01 photoreal 靜圖。 */
#landing-hero {
  position: fixed; inset: 0; z-index: 0;
  width: 100%; height: 100%; object-fit: cover;
  display: none; pointer-events: none;
}
body.has-landing-video:not(.bowed) #landing-hero { display: block; }
#grade > * { position: absolute; inset: 0; }

/* 色溫層 —— 重用現有 --sky-* token, 唔另外定義顏色。
   ⚠️ blend mode 一定要 `color`, 唔可以用 `overlay`。overlay 喺 backdrop 接近中灰
   時結果 ≈ blend 色本身; 而我哋張素材成張都係中灰(正正因為要 grade headroom 先
   咁 gen), 所以 overlay 會直接抹走張相, 剩返 tint 自己條漸變(dusk/night 實測係
   一片平滑紫粉, 湖同蘆葦全部消失)。`color` 只取 blend 嘅色相+飽和, 完整保留底圖
   亮度結構 → 相仲喺度, 色跟時辰行。呢個係 spike 第二個關鍵發現。*/
.g-tint {
  background: linear-gradient(to bottom, var(--sky-top), var(--sky-mid) 55%, var(--sky-bot));
  mix-blend-mode: color;
  opacity: var(--grade-op);
  transition: opacity 1.3s ease;
}
/* 星野 —— raster 模式下 SVG 收埋咗, 用 gradient 砌返 */
.g-stars {
  opacity: var(--star-op);
  transition: opacity 0.8s ease;
  background-image:
    radial-gradient(1.5px 1.5px at 12% 14%, #fdf6e3, transparent),
    radial-gradient(1.2px 1.2px at 23% 26%, #fdf6e3, transparent),
    radial-gradient(1.4px 1.4px at 31% 9%,  #fdf6e3, transparent),
    radial-gradient(1.1px 1.1px at 44% 21%, #fdf6e3, transparent),
    radial-gradient(1.6px 1.6px at 52% 7%,  #fdf6e3, transparent),
    radial-gradient(1.2px 1.2px at 61% 17%, #fdf6e3, transparent),
    radial-gradient(1.3px 1.3px at 68% 29%, #fdf6e3, transparent),
    radial-gradient(1.5px 1.5px at 77% 11%, #fdf6e3, transparent),
    radial-gradient(1.1px 1.1px at 84% 23%, #fdf6e3, transparent),
    radial-gradient(1.4px 1.4px at 92% 15%, #fdf6e3, transparent),
    radial-gradient(1.2px 1.2px at 6%  32%, #fdf6e3, transparent),
    radial-gradient(1.0px 1.0px at 37% 35%, #fdf6e3, transparent),
    radial-gradient(1.3px 1.3px at 71% 38%, #fdf6e3, transparent);
}
/* 日 或 月 —— 永遠得一個, 位置由時間算(app.js 寫 --body-x/y)。§8.2：卦象決定
   光嘅性格,時辰決定光嘅位置。所以素材入面唔准有日月。*/
.g-body {
  background: radial-gradient(circle var(--body-r) at var(--body-x) var(--body-y),
              var(--body-col), rgba(255, 255, 255, 0) 68%);
  opacity: var(--body-op);
  mix-blend-mode: screen;
  transition: opacity 1.3s ease;
}
/* ?body=off|wash —— 三態對照, 答「個圓盤到底要唔要」。圓盤永遠 merge 唔入相:
   佢冇投影、冇遮擋(screen blend 必然穿過厚雲)、冇大氣散射, 而且位置由時鐘算,
   同素材本身烘死咗嘅光向冇 coupling → 有一半機會同張相嘅太陽反方向。
   wash = 刪走圓盤本體, 淨低「光從邊個方向嚟」: 橢圓大到出晒界, 畫面上見唔到
   圓心亦見唔到邊, 只係一側偏暖偏光。§8.2「時辰決定光嘅位置」依然成立。*/
body.body-off .g-body { display: none; }
body.body-wash .g-body {
  background: radial-gradient(ellipse 110vmax 80vmax at var(--body-x) var(--body-y),
              var(--body-col) 0%, rgba(255, 255, 255, 0) 100%);
  opacity: calc(var(--body-op) * 0.5);
}
/* horizon —— 唔畫光源, 畫「光留低嘅痕」: 一條坐喺實測地平線上、跟住 --body-x
   偏向一邊嘅餘光帶。§8.2 原文「離卦凌晨 → 冇太陽, 而係地平線一抹火色餘光」
   講嘅正正係呢種。同 wash 嘅分別: wash 係一大片方向性 wash, 呢個係一條帶,
   而且錨喺場景嘅地平線(每張地貌相唔同高度)而唔係錨喺天體位置。*/
body.body-horizon .g-body {
  background: radial-gradient(ellipse 70vmax 14vmin at var(--body-x) var(--seam-mid),
              var(--body-col) 0%, rgba(255, 255, 255, 0) 100%);
  opacity: calc(var(--body-op) * 0.8);
}
/* vignette —— 反方向做: 唔加光, 加暗。四周 multiply 壓落去, 光源方向嗰邊留返
   最光。減法打光結構上唔可能「浮」—— 佢冇疊任何新像素落張相上面, 只係將底圖
   本身某啲部位壓暗, 所以永遠同素材同一個 material。*/
body.body-vignette .g-body {
  background: radial-gradient(ellipse 95vmax 70vmax at var(--body-x) var(--body-y),
              rgba(0, 0, 0, 0) 25%, rgba(0, 0, 0, 0.60) 100%);
  mix-blend-mode: multiply;
  opacity: 1;
}
/* occluded —— 唔刪圓盤, 而係修好佢: 見 .ras-body(擺喺 sky 同 ground 之間)。*/
body.body-occluded .g-body { display: none; }
/* Scrim —— raster 專用。SVG 路徑靠 bowed 嘅 brightness(.55) 全屏調暗,但真實相
   咁樣做會變死灰,所以改成只喺上下文字帶壓暗,中間唔郁。柔和漸變,唔可以有硬邊
   (見上面「no boxes」原則)。*/
/* ⚠️ 底部斜坡一定要去到 ~62%: 文字帶實測由 52vh 一路去到 91vh(卦象+卦名+卦辭+
   象+爻辭), 舊嘅 44% 斜坡喺經典文字真正坐嗰度只剩 ~14% 有效黑度 → 晨景實測
   底部經典文字幾乎讀唔到、紅字隱形。*/
.g-scrim {
  background:
    linear-gradient(to bottom, rgba(0, 0, 0, 0.92), rgba(0, 0, 0, 0) 40%),
    linear-gradient(to top,    rgba(0, 0, 0, 0.92), rgba(0, 0, 0, 0) 62%);
  /* --scrim-boost 由地貌素材嘅實測底部高頻度算返（app.js applyScene）—— 岩石
     紋理要壓多啲, 玻璃水面唔使。
     ⚠️ 個乘積一定要封頂。boost 上限係封咗 1.35, 但兩個數一乘就爆:
     day 0.70 × 1.311 = 0.918, 配底部漸變 rgba(0,0,0,.92) → 實際黑度 0.845,
     直情食晒地貌層(實測地天泰塊雲海高原完全睇唔到, 得返一片平滑深灰)。
     0.78 係實測甜蜜點 —— 字仍然全部讀到, 岩石紋理同雲海層次保得住。
     只有 dawn/day 配碎地貌先會撞到呢個頂; dusk 0.655 / night 0.393 唔受影響。*/
  opacity: min(0.78, calc(var(--scrim-op) * var(--scrim-boost)));
  transition: opacity 1.3s ease;
}
/* raster 唔要 rotateX(相片會似歪咗嘅海報,唔似運鏡)亦唔要 brightness(.55)(真相
   會變死灰)。改成單純推近 + 下移。§8 B5 觀察點。*/
body.scene-raster.bowed #scene {
  transform: scale(1.16) translateY(2.5%);
  filter: none;
}

/* ==========================================================================
   眼皮 —— 合眼問卜
   一個手勢解四件事：(1) 儀式(誠心問卜合埋眼) (2) 生成等待變成儀式本身,唔使
   對住空白畫面 (3) 場景切換嘅掩護 —— 閂眼喺舊場景、開眼喺新場景, 唔使夾兩張
   實景相 crossfade (4) 幾乎零成本, 唔使跑 WebGL/影片。
   ========================================================================== */
#lids { position: fixed; inset: 0; z-index: 4; pointer-events: none; }
.lid {
  position: absolute; left: 0; right: 0;
  height: 51%;
  background: #0a0705;
  /* 開眼慢過閂眼 —— 閂係決定, 開係甦醒 */
  transition: transform 1.9s cubic-bezier(0.33, 0, 0.2, 1);
  will-change: transform;
}
.lid.t { top: 0; transform: translateY(-100%);
  box-shadow: 0 14px 28px -6px rgba(10, 7, 5, 0.9); }
.lid.b { bottom: 0; transform: translateY(100%);
  box-shadow: 0 -14px 28px -6px rgba(10, 7, 5, 0.9); }
body.eyes-shut .lid { transform: translateY(0); transition-duration: 1.25s; }
.lids-copy {
  position: absolute;
  top: 50%;
  left: 50%;
  z-index: 1;
  width: min(28rem, calc(100vw - 48px));
  color: rgba(255, 250, 241, 0.88);
  font-family: var(--serif);
  font-size: clamp(14px, 3.8vw, 17px);
  line-height: 1.75;
  letter-spacing: 0.12em;
  text-align: center;
  opacity: 0;
  transform: translate(-50%, -42%);
  transition: opacity 0.45s ease, transform 0.7s ease;
}
body.eyes-shut .lids-copy {
  opacity: 1;
  transform: translate(-50%, -50%);
  transition-delay: 0.85s;
}

@media (prefers-reduced-motion: reduce) {
  .lid { transition-duration: 0.3s !important; }
  .lids-copy { transition-duration: 0.2s !important; }
  body.eyes-shut .lids-copy { transition-delay: 0.25s; }
}

/* ==========================================================================
   Immersive stage — everything in-scene, no boxes
   ========================================================================== */

#imm {
  position: fixed;
  inset: 0;
  z-index: 1;
  font-family: var(--sans);
  user-select: none;
  -webkit-user-select: none;
  touch-action: manipulation;
}

/* Gate 2 legibility scrim：Pick／Cast／Exhausted 嘅短文字用 70% 全畫面底色守住
   worst-case contrast。Read 係另一種體驗：scene 本身亦係解讀嘅 inspiration，唔可以
   由頭到尾壓成暗底；Read 另喺下面降到 28%，再由緊貼字形嘅 --read-ink-shadow
   局部托字。Home 保持原景亮度。 */
#imm::before {
  content: "";
  position: absolute;
  inset: 0;
  z-index: 0;
  pointer-events: none;
  background: rgba(0, 0, 0, 0.70);
  opacity: 0;
}
#imm[data-phase="pick"]::before,
#imm[data-phase="cast"]::before,
#imm[data-phase="read"]::before,
#imm[data-phase="exhausted"]::before { opacity: 1; }
#imm[data-phase="read"]::before { background: rgba(0, 0, 0, 0.28); }

/* the "眼前中央嗰嚿" — interactive content stays a centred column, never
   edge-to-edge (董事長 原則：字/元素一攤到成個畫面就抽離) */
.stage {
  position: absolute;
  left: 50%;
  top: 50%;
  transform: translate(-50%, -50%);
  width: min(92vw, 540px);
  height: min(86vh, 620px);
}

/* --- entry dot (home) --- */
.dot {
  position: absolute;
  left: 50%;
  top: 54%;
  width: 44px;
  height: 44px;
  margin: -22px 0 0 -22px;
  border-radius: 50%;
  cursor: pointer;
}
.dot::before {
  content: "";
  position: absolute;
  left: 50%;
  top: 50%;
  width: 16px;
  height: 16px;
  margin: -8px 0 0 -8px;
  border-radius: 50%;
  background: #fff;
  box-shadow: 0 0 0 7px rgba(255, 255, 255, 0.14), 0 0 26px 8px rgba(255, 210, 160, 0.5);
  animation: pulse 2.6s ease-in-out infinite;
}
@keyframes pulse {
  0%, 100% { transform: scale(1); opacity: 0.9; }
  50% { transform: scale(1.28); opacity: 1; }
}
#imm:not([data-phase="home"]) .dot { display: none; }

/* Landing 係一次安靜入口：畫面只留場景同圓心。資訊、操作同法律短句唔刪除，
   入 Pick 即由 setPhase() 移除 .home-phase 全部回復；初始 HTML 亦帶 class，
   所以 app.js 未行或失敗時都唔會先閃一堆字。 */
body.home-phase .quota-chip,
body.home-phase .positioning,
body.home-phase .hint,
body.home-phase .topctl,
body.home-phase .disclaimer {
  display: none;
}

/* --- dev 控制台（?dev=1 先出）--- */
/* 上線唔使拆：冇 ?dev=1 就永遠 hidden。擺右下角，避開左上 quota chip、
   右上 卦誌/再問、底部 langbar 同 hint。 */
.devpanel {
  position: fixed;
  right: 12px;
  bottom: 12px;
  z-index: 40;
  display: flex;
  flex-direction: column;
  gap: 6px;
  padding: 10px 12px;
  background: rgba(0, 0, 0, 0.62);
  border: 1px solid rgba(255, 255, 255, 0.18);
  border-radius: 10px;
  backdrop-filter: blur(6px);
  font-family: var(--sans);
}
.devpanel .devtitle {
  font-size: 9px;
  letter-spacing: 0.14em;
  color: rgba(255, 255, 255, 0.42);
  text-transform: uppercase;
}
.devpanel button {
  font-family: var(--sans);
  font-size: 11px;
  letter-spacing: 0.04em;
  padding: 5px 10px;
  min-width: 168px;
  text-align: left;
  color: rgba(255, 255, 255, 0.62);
  background: rgba(255, 255, 255, 0.06);
  border: 1px solid rgba(255, 255, 255, 0.14);
  border-radius: 6px;
  cursor: pointer;
  transition: color 0.15s ease, background 0.15s ease, border-color 0.15s ease;
}
.devpanel button:hover { color: var(--ink); background: rgba(255, 255, 255, 0.12); }
.devpanel button[aria-pressed="true"] {
  color: #9ff5c8;
  border-color: rgba(120, 245, 190, 0.45);
  background: rgba(120, 245, 190, 0.12);
}

/* --- rings (pick) --- */
.rings {
  position: absolute;
  inset: 0;
  opacity: 0;
  pointer-events: none;
  transition: opacity 0.7s 0.25s ease;
}
#imm[data-phase="pick"] .rings { opacity: 1; pointer-events: auto; }

.basic-intention {
  position: absolute;
  left: 50%;
  top: 51%;
  width: min(78vw, 430px);
  transform: translate(-50%, -50%);
  text-align: center;
  color: var(--ink);
  text-shadow: var(--ink-shadow);
}
.basic-intention p {
  margin: 0;
  font-family: var(--serif);
  font-size: clamp(17px, 2.5vw, 22px);
  line-height: 1.75;
  letter-spacing: 0.06em;
}
.plus-rings { display: none; }
body.plus-mode .basic-intention { display: none; }
body.plus-mode .plus-rings { display: contents; }

.ring {
  position: absolute;
  top: 54%;
  width: clamp(150px, 30vw, 220px);
  aspect-ratio: 1;
}
.ring.l { left: 50%; transform: translate(calc(-100% - 14px), -50%); }
.ring.r { left: 50%; transform: translate(14px, -50%); }
.ring .slots { position: absolute; inset: 0; }
.ring .slots svg { position: absolute; inset: 0; width: 100%; height: 100%; overflow: visible; }
.ring .ctr {
  position: absolute;
  left: 50%;
  top: 50%;
  transform: translate(-50%, -50%);
  font-family: var(--serif);
  font-size: 16px;
  color: var(--ink);
  text-shadow: var(--ink-shadow);
  text-align: center;
  pointer-events: none;
  white-space: nowrap;
}
.ring .ctr.empty { font-family: var(--sans); font-size: 12px; letter-spacing: 0.16em; color: var(--ink-soft); }

/* 八卦盤 wedges */
.wring { fill: none; stroke: rgba(255, 255, 255, 0.2); stroke-width: 0.6; }
.wedge {
  fill: rgba(255, 255, 255, 0.05);
  stroke: rgba(255, 255, 255, 0.16);
  stroke-width: 0.7;
  outline: none; /* 清走 browser 預設藍色方框；keyboard focus 由下面真 path stroke 負責 */
  cursor: pointer;
  transition: fill 0.15s, stroke 0.15s;
}
.wedge:hover { fill: rgba(255, 255, 255, 0.15); }
/* Basic 入口只用沿真實扇形邊界嘅暖沙色提示，唔預先填色；撳中後仍由 `.on` 反白。 */
.wedge.basic-open {
  fill: rgba(255, 255, 255, 0.045);
  stroke: rgba(215, 189, 145, 0.88);
  stroke-width: 1.25;
  filter: drop-shadow(0 0 2px rgba(215, 189, 145, 0.28));
}
.wtext.basic-open { fill: #ead9bd; font-weight: 700; opacity: 1; }
.wedge.on { fill: rgba(255, 255, 255, 0.92); stroke: rgba(255, 255, 255, 0.95); }
.wedge.locked { fill: rgba(255, 255, 255, 0.02); stroke: rgba(255, 255, 255, 0.07); cursor: pointer; }
.wedge.locked:hover { fill: rgba(255, 255, 255, 0.04); }
.wtext.locked { fill: var(--ink); opacity: 0.32; }
.wedge:focus-visible {
  stroke: var(--focus-ring);
  stroke-width: 2;
  filter: drop-shadow(0 0 3px rgba(215, 189, 145, 0.62));
}
.wtext {
  fill: var(--ink);
  font-family: var(--serif);
  letter-spacing: 1px;
  pointer-events: none;
}
.wtext.on { fill: #6f341f; }

/* --- 開始 --- */
.startb {
  position: absolute;
  left: 50%;
  top: 82%;
  transform: translateX(-50%);
  font-family: var(--sans);
  font-size: 13px;
  letter-spacing: 0.32em;
  color: var(--ink);
  background: rgba(255, 255, 255, 0.12);
  border: 0.5px solid rgba(255, 255, 255, 0.55);
  padding: 9px 28px;
  border-radius: 99px;
  cursor: pointer;
  opacity: 0;
  pointer-events: none;
  transition: opacity 0.5s ease, background 0.15s;
}
.startb.on { opacity: 1; pointer-events: auto; }
.startb:hover { background: rgba(255, 255, 255, 0.22); }

/* 解讀語言選擇（pick 版，Start 上面）：求籤前揀定 LLM 出文語言。英文行先。 */
.langpick { position: absolute; left: 50%; top: 25%; transform: translateX(-50%); display: flex; gap: 5px; z-index: 2; white-space: nowrap; }
.langpick button {
  font-family: var(--sans); font-size: 13px; letter-spacing: 0.03em;
  padding: 5px 12px; color: var(--ink-soft); background: transparent;
  border: 1px solid transparent; border-radius: 999px; cursor: pointer;
  transition: color 0.2s ease, background 0.2s ease, border-color 0.2s ease;
}
.langpick button:hover { color: var(--ink); }
.langpick button.on { color: var(--ink); background: rgba(255, 255, 255, 0.14); border-color: rgba(255, 255, 255, 0.18); }

/* --- read (story top + hexagram/classical bottom) --- */
/* 字喺上（由畫面上半向下讀）, 卦象 + 卦辭喺下。上下兩帶用 flex 排 —— 舊版兩者
   各自用 vh 硬算絕對位（story 底 13+44=57vh vs hexwrap 兩欄滿文頂到 52vh）,
   任何視窗高度都會疊（實測疊 28px）。flex 之下 story 食剩餘空間, 卦象要幾多得
   幾多, 結構上唔可能再撞。底 padding 讓開 .hint（bottom:52px）。 */
.read {
  --read-ink-shadow:
    0 0 1px rgba(0, 0, 0, 1),
    0 1px 3px rgba(0, 0, 0, 1),
    0 0 9px rgba(0, 0, 0, 0.94),
    0 0 20px rgba(0, 0, 0, 0.72);
  position: absolute;
  inset: 0;
  opacity: 0;
  pointer-events: none;
  transition: opacity 0.8s ease;
  display: flex;
  flex-direction: column;
  align-items: center;
  /* 頂 padding 舊版係 story 嘅絕對起始位(13vh); flex 之下佢變咗純死位, 矮螢幕
     會迫到 story 縮水(實測 265→200px, 切走 157px 字)。改成隨高度縮: 603px 高
     得 48px, 1200px 高先去到 96px。底 padding 讓開 .hint(bottom:52px)。*/
  /* ⚠️ 底 padding = **langbar 嘅實際 footprint**，唔係 .disclaimer。
     舊值 clamp(34px,5vh,46px) 個註解寫住「只需讓開 .disclaimer(~28px)」—— 但
     .langbar 係後來先加,`position:absolute; bottom:34px` + 28px 高 = 佔 62px,
     從來冇補返。後果:成個 flow 底部落低 21px,於是
       · 內容中心比可視空檔(pill 底 → langbar 頂)中心低 10–16px（實測三個機高）
       · 最長嗰版嘅字直接鑽入 langbar 底下 16–27px,睇唔到
     34(bottom) + 28(高) + 8(呼吸位) = 70px。**一定要定值,唔好用 vh** —— langbar
     係固定 px 定位,footprint 唔隨機高變,用 vh 高機就過度補償(900px 機實測由
     低 10px 反過頭變高 7px)。定值 70px 三個機高(667/812/900)偏移一律 −2px。
     改 .langbar 個 bottom 或者字級,呢度要一齊改。
     story 保持 flex:1 —— 解讀必須由頂開始向下流,縮嘅係佢。*/
  padding: clamp(40px, 8vh, 110px) 0 70px;
  gap: clamp(10px, 2.4vh, 26px);
  /* Browser／OS 200% zoom 會放大 flex children，但 media query viewport 仍報原尺寸。
     卦象區因此可以食晒剩餘高度，將 story 壓成 0px。容許成個 reading 垂直 scroll，
     再由 story 最小高度保住第一段同「顯示下一段」操作；正常桌面／手機有足夠空間
     時唔會產生額外 scroll。 */
  overflow-x: hidden;
  overflow-y: auto;
  scrollbar-width: none;
}
.read::-webkit-scrollbar { display: none; }
/* read phase：只收起 .hint（提示字）。「再問」係功能掣,唔可以收埋。 */
/* 卦誌開住嗰陣 .hint 亦要收 —— 但唔再喺呢度做（卦誌已經唔係 phase），見 body.jopen。 */
#imm[data-phase="read"] .hint { display: none; }
#imm[data-phase="read"] .read { opacity: 1; pointer-events: auto; }

.story {
  flex: 1 1 auto;
  min-height: clamp(96px, 20vh, 160px);
  width: min(88%, 480px);
  overflow-y: auto;
  scrollbar-width: none; /* 冚 scrollbar — 唔好彈 tool bar 破沉浸 */
  display: flex;
  flex-direction: column;
  gap: 15px;
  text-align: center;
}
.story::-webkit-scrollbar { display: none; }
.story[role="button"]:focus-visible {
  outline: 2px solid var(--focus-ring);
  outline-offset: 4px;
  border-radius: 8px;
}
/* 捲出可視區嗰邊淡出 —— scrollbar 冚咗, 冇呢個提示用戶唔知仲有段未讀（實測
   scrollHeight 356 > clientHeight 265, 91px 靜靜哋消失咗）。兩端獨立: 上面仲有
   字就淡上邊, 下面仲有就淡下邊。*/
.story.fade-top {
  -webkit-mask-image: linear-gradient(to bottom, transparent 0, #000 34px);
  mask-image: linear-gradient(to bottom, transparent 0, #000 34px);
}
.story.fade-bot {
  -webkit-mask-image: linear-gradient(to bottom, #000 calc(100% - 34px), transparent 100%);
  mask-image: linear-gradient(to bottom, #000 calc(100% - 34px), transparent 100%);
}
.story.fade-top.fade-bot {
  -webkit-mask-image: linear-gradient(to bottom, transparent 0, #000 34px, #000 calc(100% - 34px), transparent 100%);
  mask-image: linear-gradient(to bottom, transparent 0, #000 34px, #000 calc(100% - 34px), transparent 100%);
}
.para {
  color: var(--ink);
  font-size: 15px;
  line-height: 1.82;
  text-shadow: var(--read-ink-shadow);
  opacity: 0;
  transform: translateY(8px);
  transition: opacity 0.7s ease, transform 0.7s ease;
}
.para.show { opacity: 1; transform: none; }
.para-label {
  display: block;
  margin-bottom: 3px;
  font-family: var(--sans);
  font-size: 11px;
  line-height: 1.3;
  letter-spacing: 0.16em;
  color: rgba(255, 255, 255, 0.72);
  text-shadow: var(--read-ink-shadow);
}
.para-text { display: block; }

.hexwrap {
  flex: 0 0 auto;
  width: min(94%, 600px);
  display: flex;
  justify-content: center;
}
/* 每卦成欄：本卦圖+本卦文 ｜「之」｜ 之卦圖+之卦文（n≥3）；靜卦單欄照樣居中 */
.hexcols { display: flex; flex-direction: row; align-items: flex-start; justify-content: center; gap: clamp(12px, 3vw, 32px); }
/* .guacol / .zhi-link 已隨舊底部文字區移除（carousel 取代）；.hex 系保留俾 carousel reuse */
.hex { display: flex; flex-direction: column-reverse; gap: 5px; width: clamp(120px, 15vh, 152px); }
/* 陽爻 = 一條實線滿寬；陰爻 = 兩截但總寬一樣、缺口喺正中（唔好令九爻「长咗」）*/
.hx { position: relative; display: flex; gap: 13px; height: clamp(9px, 1.3vh, 13px); }
.hx .b { flex: 1; background: rgba(255, 255, 255, 0.94); border-radius: 3px; }
.hx .b.h { flex: 1; }
.hx.chg .b { background: var(--change); }
/* 變爻爻位紅字：absolute → 唔佔 flex，唔影響陰陽爻等寬 */
/* 爻位標籤坐喺 scrim 透明嘅中段, 背景乜色都有可能(實測橙字壓喺光雲海上幾乎
   隱形)。10px 細字用軟陰影托唔起, 要收緊做描邊。*/
.ylab { position: absolute; right: calc(100% + 6px); top: 50%; transform: translateY(-50%);
  font-family: var(--sans); font-size: 10px; letter-spacing: 0.04em; color: var(--change);
  white-space: nowrap;
  text-shadow: 0 0 3px rgba(0, 0, 0, 0.95), 0 1px 2px rgba(0, 0, 0, 0.9); }

/* ===== 轉輪 carousel（取代舊底部靜態文字區）—— 全部 .gw scope 免撞名 ===== */
.gw { display: flex; flex-direction: column; align-items: center; }
.gw-ident { display: flex; flex-direction: column; align-items: center; }
/* identity 卦象保持原本大細（reuse 預設 .hex），唔縮 —— 卦象係主角 */
.gw-name { text-align: center; margin-top: 7px; }
/* ⚠️ line-height 一定要寫實 px，唔可以留 `normal`：`normal` 由字體 metrics 推導，中文
   fallback（Noto Serif TC）行高 22px、拉丁字 18px —— 切語言個名嘅高度變 4px，而成個
   .gw 係 bottom-anchored，於是**上面嘅卦象成個跌 4px**，睇落好似斷裂。
   最長英文名 Attached Radiance 153px < stage 293px，永遠一行，鎖 22px 唔會切。 */
/* 「本卦 → 之卦」一行擺得落：主 16px、次 11px、箭嘴 11px。實測最壞英文組合
   `Attached Radiance → Before Completion` = 278px < stage 293px（次用 12.5px 只剩
   4px，太貼）。nowrap 係硬要求 —— 一 wrap 個名行就高咗，成個 .gw bottom-anchored，
   上面卦象就會跳。 */
.gw-name { display: flex; align-items: baseline; justify-content: center; gap: 6px; white-space: nowrap; }
.gw-name .zh { color: var(--ink); font-size: 16px; line-height: 22px; letter-spacing: 0.06em; text-shadow: var(--ink-shadow); }
.gw-name .zh.sub { font-size: 11px; color: rgba(255, 255, 255, 0.45); }
.gw-name .nmarrow { font-family: var(--sans); font-size: 11px; line-height: 22px; color: rgba(255, 255, 255, 0.32); }

.gw .gw-wheel { display: flex; flex-direction: column; align-items: center; }
/* Pager：兩粒點 + 一粒固定闊嘅 pill（序號 · title）。擺喺卦名同面板之間。
   固定闊 = 成條 indicator 幾何靜止，切版只有入面啲字換 —— 唔好改成 auto，
   title 由 2 個中文字去到 25 個英文字元，auto 會令佢每 swipe 一次就縮放。
   66vw 上限係度返 320px 機：pill 211px − padding 20px = 191px，最長 title 169px。*/
.gw .gw-pager { display: flex; align-items: center; justify-content: center; gap: 6px; margin-top: 8px; }
/* number 用 absolute 釘死喺左緣 —— 唔可以同 title 同一個 inline flow，否則 title 長短
   一變，成串置中就會拉住個數字左右飄（實測 12px）。左右 padding 對稱 22px 令 title
   喺整條 pill 度真置中，數字就坐喺左邊嗰 22px 入面。 */
.gw .ppill {
  position: relative;
  width: min(213px, 66vw); box-sizing: border-box; padding: 3px 22px;
  border-radius: 999px; background: rgba(0, 0, 0, 0.3); border: 1px solid rgba(255, 255, 255, 0.16);
  font-family: var(--serif); font-size: 11px; letter-spacing: 0.1em; color: var(--ink);
}
.gw .ppill .pnum { position: absolute; left: 9px; top: 50%; transform: translateY(-50%); color: rgba(255, 255, 255, 0.45); }
.gw .ppill .ptxt { text-align: center; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.gw .source-slot { width: 26px; flex: 0 0 26px; display: flex; justify-content: center; }
.gw .source-spacer { visibility: hidden; }
.gw .source-toggle {
  width: 24px;
  height: 24px;
  padding: 0;
  border-radius: 50%;
  border: 1px solid rgba(255, 255, 255, 0.22);
  background: rgba(0, 0, 0, 0.3);
  color: rgba(255, 255, 255, 0.66);
  font-family: var(--serif);
  font-size: 11px;
  line-height: 1;
  cursor: pointer;
  text-shadow: var(--read-ink-shadow);
  transition: color 0.2s ease, background 0.2s ease, border-color 0.2s ease;
}
.gw .source-toggle:hover { color: var(--ink); border-color: rgba(255, 255, 255, 0.4); }
.gw .source-toggle[aria-pressed="true"] {
  color: #24180f;
  background: rgba(255, 217, 160, 0.9);
  border-color: rgba(255, 230, 190, 0.96);
  text-shadow: none;
}
.gw .source-toggle:focus-visible { outline: 2px solid var(--focus-ring); outline-offset: 2px; }
.gw .carousel { display: flex; align-items: center; gap: 6px; margin-top: 3px; }
.gw .arrow { min-width: 24px; background: none; border: 0; cursor: pointer; padding: 10px 8px; font-family: var(--serif); font-size: 24px; line-height: 1; color: rgba(255, 255, 255, 0.32); transition: color 0.2s ease; flex: none; }
.gw .arrow:hover { color: rgba(255, 255, 255, 0.72); }
/* ⚠️ 高度係度返嚟嘅，唔好隨手加大：64 卦 × panel × 中／英／經文內容高度
   中位 45px / p90 62px / 最高 124px。stage 90px → 3/640 要 scroll、典型內容上下
   各留 23px；再縮到 80px scroll 數就由 3 跳去 23。130px 就變返「內容浮喺正中、
   上面吉一大截」。 */
.gw .gw-stage {
  position: relative;
  width: min(340px, 78vw);
  height: clamp(84px, 11.5vh, 96px);
  touch-action: pan-y;
  user-select: none;
  text-shadow: var(--read-ink-shadow);
}
/* 上下置中 —— 但個 stage 已經縮到 84–96px，所以留白由舊時 40px 減到 ~23px，
   既貼實 title 又有對稱呼吸位。（純頂對齊試過：上 9px 下 72px，重心明顯偏上。） */
/* ⚠️ `safe center` 唔係 `center` —— 呢個係 scroll 容器嘅硬性要求，唔好簡化返。
   純 `center` 喺內容爆位時會將內容頂推到 **scroll 原點之上**，而 scrollTop 最細
   得 0，即係開頭嗰截**永遠碌唔到**（實測 #2 坤英文卦辭：文字係
   "Supreme success…" 開頭，但碌到最頂見到嘅第一行係中間嗰句 "mare does."，
   前 11px 冚咗，冇任何 error）。`safe` = 一 overflow 就自動退返頂對齊，
   內容短嘅時候照樣置中。 */
.gw .face { position: absolute; inset: 0; display: flex; flex-direction: column; align-items: center; justify-content: safe center; text-align: center; padding: 6px 12px; opacity: 1; transition: opacity 0.4s ease; overflow-y: auto; scrollbar-width: none; }
.gw .face.hidden { opacity: 0; pointer-events: none; }
.gw .face::-webkit-scrollbar { display: none; }
/* 「下面仲有嘢」嘅唯一訊號 —— 成個 app 嘅 scrollbar 都冚起(唔好彈 tool bar 破沉浸),
   唔加提示嘅話碌得嘅版同碌唔到嘅版睇落一模一樣。用 mask 唔用畫個漸變 overlay:
   overlay 要一隻同背景一樣嘅色,但背景係場景圖(64 張都唔同色),食硬穿崩。
   class 由 JS bindScrollFade() 開關 —— 碌到底就除返,唔好遮住最後一行。 */
.gw .fade-more {
  -webkit-mask-image: linear-gradient(to bottom, #000 calc(100% - 20px), transparent);
  mask-image: linear-gradient(to bottom, #000 calc(100% - 20px), transparent);
}

/* face 卦辭 + 象曰（無 label、無英文，字級還原舊 .ctext）*/
.gw .dual { display: flex; flex-direction: column; gap: 9px; }
.gw .cls2 { font-family: var(--serif); font-size: 12.5px; line-height: 1.65; color: var(--ink-soft); text-shadow: var(--ink-shadow); }

/* face 變爻（左卦象 highlight 該爻 / 右爻辭）*/
.gw .face.yaoface { flex-direction: row; align-items: center; justify-content: center; gap: 12px; text-align: left; }
.gw .face .hex { width: 36px; gap: 3px; }
.gw .face .hx { height: 4px; gap: 5px; }
.gw .hx.hl .b { background: #ffd9a0; box-shadow: 0 0 8px rgba(255, 217, 160, 0.6); }
.gw .yaotext { flex: 1; max-width: 252px; }
.gw .yao { font-family: var(--serif); font-size: 12.5px; line-height: 1.65; color: var(--ink-soft); text-shadow: var(--ink-shadow); }

/* face 卦象結構（上卦左 / 下卦右：象 / 德 / 物象 分行；卦符擺內側頂角、兩符中線相鄰示意上下卦組成）*/
.gw .trigrams { display: flex; align-items: stretch; width: 100%; }
/* padding-top 要避開頂上個 pin（卦符+象，face 內 top:3px、高 ~23px）—— 加咗象之後
   個 pin 橫向覆蓋咗大半個內半欄，唔留位就會壓住個德。 */
.gw .col { position: relative; flex: 1; display: flex; align-items: flex-start; justify-content: flex-start; padding: 28px 8px 8px; overflow: hidden; }
.gw .col + .col { border-left: 1px solid rgba(255, 255, 255, 0.14); }
/* struct 面頂對齊（唔置中）→ 內容高度變（切中英）頂唔郁；內容過長就內部 scroll，卦符唔跟住 scroll。 */
.gw .face.struct { justify-content: flex-start; overflow: hidden; }
.gw .face.struct .trigrams { max-height: 100%; overflow-y: auto; scrollbar-width: none; }
.gw .face.struct .trigrams::-webkit-scrollbar { display: none; }
/* 卦符+象 孖住 pin 喺 face 頂、貼實中線 divider 鏡像向外（`水 ☵ | ☲ 火`），唔入 scroll
   內容 → scroll / 切語言都 hold 實。呢對嘢就係新嘅 top-lock anchor：中文永遠 1 字、
   英文最長 Mountain，所以**永遠一行、兩邊永遠同一水平**（舊 anchor 係「象+德」，德會
   wrap 兩行，混合卦兩欄就錯開一行）。 */
.gw .gsym-pin { position: absolute; top: 3px; z-index: 2; display: flex; align-items: center; gap: 5px; line-height: 1; color: var(--ink-soft); text-shadow: var(--ink-shadow); pointer-events: none; }
.gw .gsym-pin.up { right: calc(50% + 5px); }
.gw .gsym-pin.lo { left: calc(50% + 5px); }
.gw .gsym-pin .psym { font-size: 23px; line-height: 1; }
.gw .gsym-pin .pim { font-family: var(--serif); font-size: 14px; letter-spacing: 0.1em; color: var(--ink); white-space: nowrap; }
.gw .col .colbody { position: relative; z-index: 1; display: flex; flex-direction: column; align-items: center; gap: 7px; width: 100%; text-shadow: var(--ink-shadow); }
/* 德固定兩行高 + 垂直置中：英文德最長 21 字元（Intentional Stillness 137px）超過欄闊
   118px 會 wrap，短嗰啲唔會 —— 唔鎖高度，兩欄下面嘅流區就會差一行。 */
.gw .col .t-at { font-family: var(--serif); font-size: 14px; letter-spacing: 0.08em; color: var(--ink); min-height: 2.7em; line-height: 1.35; display: flex; align-items: center; text-align: center; }
.gw .col .flowrow { font-family: var(--serif); font-size: 11px; line-height: 1.55; letter-spacing: 0.04em; color: var(--ink-soft); text-align: center; }

/* 序卦 · 前/後 故事走向：此卦置頂、前後兩欄（沿用 struct 兩欄 feel） */
/* 序卦面：鄰卦卦象 + 文字並排（跟變爻面同一個 row 語法）。上一卦卦象喺左、下一卦喺右
   —— 由 JS append 次序決定，唔用 row-reverse，免得 DOM 同視覺次序唔一致。 */
/* 鄰卦卦名 2026-07-27 搬咗上 pill title，panel 入面只剩序卦傳句 → 返返上下置中
   （原本要 flex-start 係為咗釘住嗰個 sub-title，而家冇咗嗰個 special-case）。
   卦象（.seqside）照舊 absolute 釘喺垂直正中。 */
/* ⚠️ seqface 自己**唔准** scroll（覆蓋 .face 個 overflow-y:auto）—— .seqside 個鄰卦
   卦象係 absolute 釘喺 face 正中,face 一 scroll 佢就跟住碌走。同 struct 面一樣,
   scroll 交俾入面嗰個文字容器(.seqtext),卦象就永遠 hold 實喺兩側正中。 */
.gw .face.seqface { flex-direction: row; align-items: center; justify-content: center; gap: 0; overflow: hidden; }
/* 卦象用 absolute 釘死：垂直永遠喺 panel 正中、水平永遠貼外緣。
   用 flex 槽唔夠 —— 槽會跟 align-items 同兄弟高度郁，文字長短就拉到個符上上落落。
   .seqtext 留返 44px padding 唔好行入去符下面。 */
.gw .seqside { position: absolute; top: 50%; transform: translateY(-50%); width: 36px; display: flex; justify-content: center; }
.gw .seqside.l { left: 0; }
.gw .seqside.r { right: 0; }
.gw .seqtext { flex: 1; min-width: 0; max-height: 100%; overflow-y: auto; scrollbar-width: none; padding: 0 44px; display: flex; flex-direction: column; align-items: center; gap: 5px; }
.gw .seqtext::-webkit-scrollbar { display: none; }
/* 序卦傳過渡句：解釋呢條箭嘴點解成立。比卦名細同淡 —— 卦名先係 panel 主角。 */
/* ⚠️ 11px 係成個 carousel 嘅**字級下限**（＝ pill title 嗰個 size）。實測再細喺手機
   同 desktop 都望唔到 —— 唔准為咗塞多啲字而寫 10px / 9.5px，寧願收短內容。
   （呢行本來係 10.5px，2026-07-26 拉返上 11px。） */
/* ⚠️ 同 .cls2 / .yao **完全一樣**（12.5px / 1.65 / --ink-soft / text-shadow），唔准
   再降級做副標題。舊值係 11px + 白 0.5 + 冇 text-shadow，因為佢原本係鄰卦卦名
   嘅旁白（註解寫住「比卦名細同淡 —— 卦名先係 panel 主角」）。但卦名 f7f021d
   已經搬咗上 pill title，佢而家係序卦版**唯一嘅內容**，個理由冇咗。
   冇 text-shadow 嗰樣最傷：背景係場景相，冇暗邊啲字會散、偏灰，實測會被讀成
   「另一隻字體」（其實由頭到尾都係 Noto Serif TC）。 */
.gw .seqwhy { font-family: var(--serif); font-size: 12.5px; line-height: 1.65; color: var(--ink-soft); text-shadow: var(--ink-shadow); text-align: center; margin-top: 2px; }
/* `.clip` / `.open` 兩個 state 2026-07-27 拆走 —— 序卦傳而家全文出、長就碌。 */

/* --- exhausted --- */
.exhausted {
  position: absolute;
  left: 50%;
  top: 50%;
  transform: translate(-50%, -50%);
  width: min(84%, 380px);
  text-align: center;
  opacity: 0;
  pointer-events: none;
  transition: opacity 0.6s ease;
}
#imm[data-phase="exhausted"] .exhausted { opacity: 1; pointer-events: auto; }
.exhausted-msg { color: var(--ink); text-shadow: var(--ink-shadow); line-height: 1.8; margin: 0; }

/* --- hint (bottom) + reask (top-right) --- */
.hint {
  position: absolute;
  left: 0;
  right: 0;
  bottom: 52px;
  text-align: center;
  color: var(--ink-soft);
  font-size: 11.5px;
  letter-spacing: 0.2em;
  text-shadow: var(--ink-shadow);
  pointer-events: none;
}
.positioning {
  position: absolute;
  left: 14px;
  right: 14px;
  bottom: 78px;
  text-align: center;
  color: rgba(255, 255, 255, 0.58);
  font-size: 11px;
  line-height: 1.3;
  letter-spacing: 0.04em;
  text-shadow: var(--ink-shadow);
  white-space: nowrap;
  pointer-events: none;
}
.positioning[hidden] { display: none; }
/* 右上控件槽。`.reask` 本來自己 absolute top/right:14px；加第二粒掣（卦誌）就要
   算住第一粒幾闊，而佢闊度隨字數／語言變。改成一個 flex 槽，掣本身唔再定位 ——
   之後加 membership / 設定掣照 append 入去就得。 */
.topctl {
  position: absolute;
  top: 14px;
  right: 14px;
  z-index: 3;
  display: flex;
  gap: 7px;
}
.reask, .topctl .tbtn {
  min-height: 24px;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  font-family: var(--sans);
  font-size: 11.5px;
  color: var(--ink);
  background: rgba(0, 0, 0, 0.28);
  border: 0.5px solid rgba(255, 255, 255, 0.32);
  padding: 4px 12px;
  border-radius: 99px;
  cursor: pointer;
}
.topctl a.tbtn { text-decoration: none; }
.reask:hover, .topctl .tbtn:hover { background: rgba(0, 0, 0, 0.42); }
/* hidden 屬性喺 flex 容器入面會被 display:flex 蓋過 —— 要顯式關返。 */
.topctl [hidden] { display: none; }
/* 320px 英文 Read：Hexagram + Journal + Again 同左邊 tier/quota chip 並排。
   Desktop padding 12px 會重疊；只收橫向空氣，字級守住 11px 下限。 */
@media (max-width: 360px) {
  .topctl { gap: 5px; }
  .reask, .topctl .tbtn { padding: 4px 8px; font-size: 11px; }
}
/* ── 卦誌：右滑 drawer overlay ──────────────────────────────────────────
   形態＝場景卡（direction B）：每條記錄用返嗰卦嘅 scene64 圖。點解揀佢：定位係
   「唔賣籤，賣記憶」，而記憶係畫面唔係文字；而且 81 張圖已經生咗，零新素材。

   ⚠️ **同 data-phase 正交**（同 .sheet-wrap 同一個理由，睇嗰段註解）。舊版係
   `#imm[data-phase="journal"] .journal` —— 一個**取代式 phase**，一開就冚走用戶
   啱啱抽到嗰支卦同成個場景。而家由右滑入疊上去，底層 phase 由頭到尾唔郁。
   ⚠️ 唔可以複用 `.sheet` 個 class —— 佢係 bottom-sheet（bottom:0 + translateY），
   方向唔同。複用嘅係「頂層 sibling + [hidden]/.open 兩段式 + 專屬 z-index」呢個架構。
   ⚠️ 底 padding 唔再有 44px / 70px 雙軌問題：drawer 自己一層，langbar 開住都唔關
   事（body.jopen 直接收起佢），所以只需要自己嗰份呼吸位 + safe-area。 */
.jwrap { position: fixed; inset: 0; z-index: 6; }   /* 5 = .sheet-wrap，6 以上全空 */
.jwrap[hidden] { display: none; }
/* 自己帶 scrim —— 唔可以靠當時個場景啱啱好夠暗。實測：由首頁入卦誌，背景係
   landing hero（光亮天空），底部嗰段白 40% 說明字直情睇唔到。 */
.jscrim {
  position: absolute; inset: 0;
  background: rgba(6, 5, 4, 0.62);
  opacity: 0; transition: opacity 0.28s ease;
}
.journal {
  position: absolute; top: 0; right: 0; bottom: 0;
  /* 384 = .jcards 340px + 2×22 padding（＝場景卡幅圖闊度，用戶：「應該唔會霸好多位」）。
     手機 88vw = 左邊留 ~12% 見到底下場景 —— 已定：唔准霸晒成個畫面。 */
  width: min(384px, 88vw);
  transform: translateX(100%);
  transition: transform 0.34s cubic-bezier(0.22, 0.9, 0.3, 1);   /* 同 .sheet 同一條曲線 */
  background: rgba(18, 16, 14, 0.94);
  -webkit-backdrop-filter: blur(18px); backdrop-filter: blur(18px);
  border-left: 0.5px solid rgba(255, 255, 255, 0.18);
  display: flex;
  flex-direction: column;
  align-items: center;
  padding: clamp(28px, 5vh, 52px) clamp(14px, 4vw, 22px) calc(22px + env(safe-area-inset-bottom, 0px));
}
/* .open 由 JS 落，同 [hidden] 分開兩幀（同 .sheet 一樣，見 app.js openJournal）。 */
.jwrap.open .jscrim { opacity: 1; }
.jwrap.open .journal { transform: none; }
/* drawer 開住：收起 #imm 內嘅控件。.topctl 係 top-right，會被 drawer 整個蓋住（撳唔
   到，等於死掣）；.langbar / .hint 會由 scrim 後面透出嚟嘈。四條退路（drawer 自帶
   ×、scrim、Escape、右掃）都唔靠 .topctl，收起後「再問」即刻返嚟。
   .quota-chip 係 fixed top-left ~70px 闊，手機 88vw drawer 只剩 45px 位 —— 會被 drawer
   左邊切一半（實測截圖見到）。純資訊唔係功能掣，收起後即刻返嚟。
   ⚠️ **`.disclaimer` 唔准落呢個清單** —— 法律聲明，唔可以因為開個 panel 就消失。佢
   居中兼 pointer-events:none，喺 scrim 後面照出唔會擋路。 */
body.jopen .topctl,
body.jopen .langbar,
body.jopen .hint,
body.jopen .positioning,
body.jopen .quota-chip { display: none; }
.jttl {
  font-family: var(--serif); font-size: 15px; letter-spacing: 0.14em;
  color: var(--ink); text-shadow: var(--ink-shadow); flex: none;
}
/* ⚠️ 呢句係**誠實聲明**唔係裝飾：卦誌只係 browser localStorage，唔會自動同步；
   browser／私隱模式／用戶清除資料都可能令記錄消失。唔准改成「永久保存」之類。 */
.jsub {
  font-size: 11px; letter-spacing: 0.04em; color: rgba(255, 255, 255, 0.45);
  text-shadow: var(--ink-shadow); margin-top: 4px; flex: none;
}
/* 闊度由 drawer 定（384 − 2×22 = 340px 桌面），唔再自己 min(340px,88%)。 */
.jcards {
  width: 100%; margin-top: 16px;
  flex: 1 1 auto; min-height: 0;          /* 冇 min-height:0 個 flex child 唔會 scroll */
  overflow-y: auto; scrollbar-width: none;
  display: flex; flex-direction: column; gap: 11px;
}
.jcards::-webkit-scrollbar { display: none; }
/* ⚠️ 呢條規則之前**唔存在** —— app.js syncFade(jcardsEl) 一直 toggle 個 `fade-more`
   class，但 CSS 只有 `.gw .fade-more`（要 .gw 祖先），.jcards 唔喺 .gw 底下，即係成個
   底部漸層由頭到尾零效果（死 code）。用同一個 mask 手法，唔用漸變 overlay：卡本身
   係場景圖，overlay 冇一隻色遮得住。 */
.jcards.fade-more {
  -webkit-mask-image: linear-gradient(to bottom, #000 calc(100% - 20px), transparent);
  mask-image: linear-gradient(to bottom, #000 calc(100% - 20px), transparent);
}
/* 100px 唔係執靚數：5 張 × 100 + 4 隙 × 11 = 544，啱啱好坐得落 812px 機嘅 552px
   可用高（clamp 頂 padding 40 + 底 44 + 標題區）。106px 就差 22px 要碌 —— 免費層
   5 條**應該一屏睇晒**，碌係 667px 矮機先出現嘅退化，唔係常態。 */
.jcard-wrap {
  position: relative;
  flex: none;
}
.jcard {
  position: relative; height: 100px; flex: none;
  width: 100%; display: block;
  border-radius: 9px; overflow: hidden; cursor: pointer;
  box-shadow: 0 0 0 1px rgba(255, 255, 255, 0.08);
  border: 0; padding: 0; background: rgba(255, 255, 255, 0.04);
  text-align: left; font: inherit; color: inherit;
}
.jcard .ji {
  position: absolute; inset: 0; background-size: cover; background-position: center 42%;
  filter: brightness(0.62); transition: filter 0.25s ease;
}
.jcard:hover .ji { filter: brightness(0.78); }
/* 底部漸變：卦名同日期坐喺相上面，冇呢層就會撞落淺色天空度睇唔到。 */
.jcard .jg { position: absolute; inset: 0; background: linear-gradient(180deg, transparent 34%, rgba(0, 0, 0, 0.72)); }
.jcard .jt {
  position: absolute; left: 13px; right: 13px; bottom: 10px;
  display: flex; align-items: baseline; justify-content: space-between; gap: 8px;
}
.jcard .jn { font-family: var(--serif); font-size: 15px; letter-spacing: 0.05em; color: #fff; text-shadow: var(--ink-shadow); }
.jcard .jd { font-size: 10.5px; color: rgba(255, 255, 255, 0.72); text-shadow: var(--ink-shadow); flex: none; }
/* ⚠️ max-width + ellipsis 係硬要求：自定記錄嘅 chip 出嘅係用戶自己打嗰條問題
   （最長 30 中／60 英），冇上限就會撐爆張卡或者換行 —— 而張卡高度係鎖死 100px
   （5 張啱啱一屏）。截字好過令個版面散。 */
.jcard .jc {
  position: absolute; top: 9px; left: 13px; max-width: 43%;
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
  font-size: 10px; letter-spacing: 0.08em; padding: 2px 8px; border-radius: 999px;
  background: rgba(0, 0, 0, 0.42); border: 1px solid rgba(255, 255, 255, 0.2);
  color: rgba(255, 255, 255, 0.86);
}
.jpulse {
  position: absolute; z-index: 3; top: 9px; right: 13px;
  max-width: 46%; min-height: 24px; padding: 2px 8px;
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
  border: 1px solid rgba(215, 189, 145, 0.5); border-radius: 999px;
  color: #ead9bd; background: rgba(20, 15, 10, 0.58);
  font-family: var(--sans); font-size: 10px; letter-spacing: 0.06em;
  cursor: pointer;
}
.jpulse:hover,
.jpulse:focus-visible,
.jpulse[aria-expanded="true"] {
  color: #fff; border-color: var(--focus-ring); background: rgba(48, 35, 22, 0.78);
}
.jpulse-details {
  margin-top: 7px; padding: 11px 12px 12px;
  border: 1px solid rgba(255, 255, 255, 0.11); border-radius: 8px;
  background: rgba(0, 0, 0, 0.24);
}
.jpulse-details[hidden] { display: none; }
.jpulse-row + .jpulse-row { margin-top: 9px; }
.jpulse-term {
  color: #d7bd91; font-size: 9.5px; letter-spacing: 0.1em;
}
.jpulse-text {
  margin-top: 2px; color: rgba(255, 255, 255, 0.82);
  font-family: var(--serif); font-size: 11.5px; line-height: 1.55;
}
.jempty {
  font-family: var(--serif); font-size: 12.5px; line-height: 1.7;
  color: var(--ink-soft); text-shadow: var(--ink-shadow); text-align: center;
  margin: auto 0; padding: 0 20px;
}
.jfoot { flex: none; margin-top: 13px; display: flex; flex-direction: column; align-items: center; gap: 7px; }
/* ⚠️ 匯出係**功能掣**唔係裝飾 —— local-only 產品最必要嘅唔係 sync，係一個顯眼嘅
   出口（Daylio 用「entries missing」開咗成篇 FAQ 就係反面教材）。唔准收埋。 */
.jact {
  font-family: var(--sans); font-size: 11.5px; color: var(--ink);
  background: rgba(0, 0, 0, 0.28); border: 0.5px solid rgba(255, 255, 255, 0.32);
  padding: 5px 16px; border-radius: 99px; cursor: pointer;
}
.jact:hover { background: rgba(0, 0, 0, 0.42); }
.jnote {
  font-size: 11px; line-height: 1.55; color: rgba(255, 255, 255, 0.44);
  text-shadow: var(--ink-shadow); text-align: center; max-width: 300px; padding: 0 16px;
}

/* ── Sheet：通用 overlay primitive ──────────────────────────────────────
   ⚠️ **同 data-phase 正交**。呢個係佢存在嘅唯一理由：`.exhausted` 嗰種做法係
   一個 phase view，會**取代**而唔係疊喺 read 上面 —— membership panel 一開就
   冚走用戶啱啱抽到嗰支卦，唔可以咁。所以 sheet 用自己一層，唔碰 data-phase。
   z-index 5：0–4 已經俾 #scene/#grade/#landing-hero(0)、#imm(1)、.disclaimer(2)、
   .quota-chip/.preview-nav(3)、#lids(4) 佔晒，5 以上全空。
   ⚠️ 呢個 z-index **只有喺 .sheet-wrap 留喺 #imm 外面做頂層兄弟先生效**。#imm 有
   z-index:1 = 開咗 stacking context，擺入面嘅話 5 只係喺佢內部排，實際仍然壓喺
   .disclaimer(2) 之下 —— 實測就係兩段字疊埋。改 DOM 位置前睇清楚呢點。*/
.sheet-wrap { position: fixed; inset: 0; z-index: 5; }
.sheet-wrap[hidden] { display: none; }
.sheet-scrim {
  position: absolute; inset: 0;
  background: rgba(6, 5, 4, 0.62);
  opacity: 0; transition: opacity 0.28s ease;
}
.sheet {
  position: absolute; left: 50%; bottom: 0;
  width: min(420px, 100%);
  transform: translate(-50%, 100%);
  transition: transform 0.34s cubic-bezier(0.22, 0.9, 0.3, 1);
  background: rgba(18, 16, 14, 0.94);
  -webkit-backdrop-filter: blur(18px); backdrop-filter: blur(18px);
  border-top: 0.5px solid rgba(255, 255, 255, 0.18);
  border-radius: 18px 18px 0 0;
  padding: 10px 22px calc(22px + env(safe-area-inset-bottom, 0px));
  max-height: 86vh; overflow-y: auto; scrollbar-width: none;
}
.sheet::-webkit-scrollbar { display: none; }
/* .open 由 JS 落，同 [hidden] 分開兩幀 —— 一齊落嘅話 display:none → block 同
   transform 喺同一幀，browser 唔會 transition，個 sheet 會硬彈出嚟。 */
.sheet-wrap.open .sheet-scrim { opacity: 1; }
.sheet-wrap.open .sheet { transform: translate(-50%, 0); }
.sheet-grab {
  width: 34px; height: 3.5px; border-radius: 99px;
  background: rgba(255, 255, 255, 0.26); margin: 0 auto 14px;
}
.sheet-x {
  position: absolute; top: 8px; right: 12px;
  background: none; border: 0; cursor: pointer; line-height: 1;
  font-family: var(--sans); font-size: 22px; color: rgba(255, 255, 255, 0.4);
  padding: 4px 8px;
}
.sheet-x:hover { color: var(--ink); }
.sheet h3 {
  font-family: var(--serif); font-weight: 400; font-size: 16px; letter-spacing: 0.08em;
  color: var(--ink); text-align: center; margin-bottom: 5px;
}
.sheet .sh-sub {
  font-size: 11.5px; line-height: 1.6; color: rgba(255, 255, 255, 0.5);
  text-align: center; margin-bottom: 16px;
}
/* 對照表：免費 vs 付費。兩欄，唔用 table —— 窄機 table 好難控。 */
.sh-rows { display: flex; flex-direction: column; gap: 1px; background: rgba(255, 255, 255, 0.08);
  border: 0.5px solid rgba(255, 255, 255, 0.12); border-radius: 10px; overflow: hidden; }
.sh-row { display: grid; grid-template-columns: 1fr auto auto; align-items: center;
  gap: 10px; padding: 9px 13px; background: rgba(10, 9, 8, 0.86); }
.sh-row.head { background: rgba(255, 255, 255, 0.05); }
.sh-k { font-size: 11.5px; color: var(--ink-soft); letter-spacing: 0.03em; }
.sh-free, .sh-paid { font-size: 11.5px; text-align: right; min-width: 58px; }
.sh-free { color: rgba(255, 255, 255, 0.38); }
.sh-paid { color: var(--ink); }
.sh-row.head .sh-k, .sh-row.head .sh-free, .sh-row.head .sh-paid {
  font-size: 10px; letter-spacing: 0.12em; color: rgba(255, 255, 255, 0.45);
}
.sh-cta {
  display: block; width: 100%; margin-top: 16px;
  font-family: var(--sans); font-size: 12.5px; letter-spacing: 0.06em;
  padding: 11px; border-radius: 99px; cursor: pointer;
  color: #1a1613; background: rgba(255, 255, 255, 0.92); border: 0;
}
.sh-cta[disabled] {
  cursor: default; color: rgba(255, 255, 255, 0.55);
  background: rgba(255, 255, 255, 0.08); border: 0.5px solid rgba(255, 255, 255, 0.18);
}
.sh-fine { font-size: 10.5px; line-height: 1.55; color: rgba(255, 255, 255, 0.36);
  text-align: center; margin-top: 10px; }

/* ── Read contextual guide ── */
.rg-intro { max-width: 350px; margin-left: auto; margin-right: auto; }
.rg-card {
  border: 0.5px solid rgba(255, 255, 255, 0.14);
  border-radius: 11px;
  background: rgba(255, 255, 255, 0.035);
  overflow: hidden;
}
.rg-identity {
  padding: 10px 13px;
  border-bottom: 0.5px solid rgba(255, 255, 255, 0.1);
  font-family: var(--serif);
  font-size: 13px;
  letter-spacing: 0.08em;
  color: var(--ink);
  text-align: center;
}
.rg-trigrams { display: grid; grid-template-columns: 1fr 1fr; }
.rg-trigram { padding: 10px 12px 11px; }
.rg-trigram + .rg-trigram { border-left: 0.5px solid rgba(255, 255, 255, 0.1); }
.rg-trigram-head {
  display: flex;
  align-items: center;
  justify-content: space-between;
  margin-bottom: 8px;
}
.rg-position {
  font-size: 11px;
  letter-spacing: 0.08em;
  color: rgba(255, 255, 255, 0.48);
}
.rg-symbol { font-size: 20px; line-height: 1; color: var(--ink); }
.rg-trigram-details { display: flex; flex-direction: column; gap: 6px; }
.rg-detail {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: 8px;
  font-size: 11px;
}
.rg-detail span { color: rgba(255, 255, 255, 0.4); }
.rg-detail b {
  min-width: 0;
  font-family: var(--serif);
  font-weight: 400;
  color: var(--ink-soft);
  text-align: right;
}
.rg-sections-title {
  margin: 17px 0 8px;
  font-family: var(--serif);
  font-size: 13px;
  font-weight: 400;
  letter-spacing: 0.08em;
  color: var(--ink);
}
.rg-sections {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: 7px;
}
.rg-section {
  min-width: 0;
  padding: 9px 10px;
  border-radius: 8px;
  background: rgba(255, 255, 255, 0.045);
}
.rg-section b {
  display: block;
  margin-bottom: 3px;
  font-family: var(--serif);
  font-size: 11.5px;
  font-weight: 400;
  color: var(--ink);
}
.rg-section span {
  display: block;
  font-size: 11.5px;
  line-height: 1.5;
  color: rgba(255, 255, 255, 0.48);
}
.rg-full-link {
  display: block;
  margin-top: 14px;
  padding: 9px;
  border: 0.5px solid rgba(255, 255, 255, 0.14);
  border-radius: 999px;
  font-size: 11.5px;
  color: var(--ink-soft);
  text-align: center;
  text-decoration: none;
}
.rg-full-link:hover { background: rgba(255, 255, 255, 0.06); color: var(--ink); }

/* ── 自定問題輸入 ── */
.sh-q {
  width: 100%; min-height: 84px; resize: none;
  font-family: var(--serif); font-size: 14px; line-height: 1.7; color: var(--ink);
  background: rgba(255, 255, 255, 0.05);
  border: 0.5px solid rgba(255, 255, 255, 0.18); border-radius: 10px;
  padding: 12px 14px; letter-spacing: 0.03em;
}
.sh-q::placeholder { color: rgba(255, 255, 255, 0.26); }
.sh-q:focus { outline: none; border-color: rgba(255, 255, 255, 0.4); }
.sh-q-error {
  margin: -5px 0 10px; padding: 8px 11px; border-radius: 8px;
  font-size: 11.5px; line-height: 1.55; color: rgba(255, 224, 190, 0.9);
  background: rgba(178, 112, 61, 0.12); border: 0.5px solid rgba(224, 166, 111, 0.3);
}
/* 倒數：一路打一路跳。⚠️ 顯示嘅係「你而家打緊嗰種字仲可以打幾多個」，唔係
   raw units —— 背後 CJK 一個字食 2 units，直接顯示 units 中文用戶會見到每打
   一個字跳 2，好突兀。中文由 30 數落 0、英文由 60 數落 0，兩邊都係一格一跳。 */
.sh-count {
  display: flex; justify-content: flex-end; align-items: baseline; gap: 4px;
  margin-top: 7px; font-family: var(--sans); font-size: 11px;
  color: rgba(255, 255, 255, 0.32); transition: color 0.25s ease;
}
.sh-count b { font-weight: 400; font-size: 12.5px; }
/* 剩 20% 先變色 —— 一開始就跳暖色會有壓迫感，唔啱呢個 app 嘅氣質。 */
.sh-count.low { color: #e8a87c; }
.sh-count.zero { color: var(--change); }

/* ── 危機支援：冷靜、有人接住嘅調子；刻意同 membership 嘅黑金對照表分開 ── */
.sheet.crisis {
  background: rgba(12, 27, 27, 0.97);
  border-top-color: rgba(151, 211, 198, 0.42);
  box-shadow: 0 -18px 55px rgba(2, 12, 12, 0.32);
}
.sheet.crisis .sheet-grab { background: rgba(177, 224, 213, 0.38); }
.sheet.crisis h3 {
  color: rgba(224, 244, 238, 0.98);
  font-size: 18px; line-height: 1.45; letter-spacing: 0.06em;
  margin: 2px 24px 12px;
}
.crisis-body {
  max-width: 350px; margin: 0 auto 15px;
  font-family: var(--serif); font-size: 13px; line-height: 1.8;
  color: rgba(225, 239, 235, 0.76); white-space: pre-line;
}
.crisis-hotlines { display: flex; flex-direction: column; gap: 8px; margin-bottom: 12px; }
.crisis-hotline {
  display: grid; grid-template-columns: minmax(0, 1fr) auto; gap: 4px 12px;
  align-items: center; padding: 11px 13px; border-radius: 11px;
  background: rgba(143, 199, 188, 0.09);
  border: 0.5px solid rgba(168, 218, 207, 0.2);
}
.crisis-region {
  font-size: 11.5px; line-height: 1.5; color: rgba(225, 239, 235, 0.72);
}
.crisis-phone {
  grid-row: span 2; color: rgba(218, 246, 238, 0.98);
  font-family: var(--sans); font-size: 15px; letter-spacing: 0.04em;
  text-decoration: none; padding: 5px 0 5px 8px;
}
.crisis-phone:hover, .crisis-phone:focus { text-decoration: underline; }
.crisis-hours { font-size: 10.5px; color: rgba(211, 230, 225, 0.48); }
.crisis-links { display: flex; flex-wrap: wrap; gap: 8px; margin-bottom: 14px; }
.crisis-links a {
  flex: 1 1 150px; padding: 9px 12px; border-radius: 999px;
  color: rgba(219, 244, 237, 0.9); text-align: center; text-decoration: none;
  font-size: 11.5px; line-height: 1.4;
  background: rgba(143, 199, 188, 0.1);
  border: 0.5px solid rgba(168, 218, 207, 0.24);
}
.crisis-links a:hover, .crisis-links a:focus { background: rgba(143, 199, 188, 0.18); }
.crisis-reframe {
  padding-top: 12px; border-top: 0.5px solid rgba(168, 218, 207, 0.18);
  font-size: 11.5px; line-height: 1.65; text-align: center;
  color: rgba(211, 230, 225, 0.55);
}

@media (prefers-reduced-motion: reduce) {
  .sheet, .sheet-scrim, .journal, .jscrim { transition: none !important; }
}

/* 主語言掣：中文 / EN；古典經文由 carousel 內獨立圓形掣開關。 */
.langbar {
  position: absolute;
  bottom: 34px;
  left: 50%;
  transform: translateX(-50%);
  z-index: 3;
  display: flex;
  padding: 2px;
  background: rgba(0, 0, 0, 0.32);
  border: 1px solid rgba(255, 255, 255, 0.16);
  border-radius: 999px;
}
.langbar button {
  min-height: 24px;
  font-family: var(--sans);
  font-size: 11px;          /* ＝ carousel 字級下限，唔好再細 */
  letter-spacing: 0.08em;
  padding: 3px 10px;
  color: rgba(255, 255, 255, 0.5);
  background: none;
  border: 0;
  border-radius: 999px;
  cursor: pointer;
  transition: color 0.2s ease, background 0.2s ease;
}
.langbar button:hover { color: var(--ink); }
.langbar button[aria-pressed="true"] { color: var(--ink); background: rgba(255, 255, 255, 0.16); }

/* --- quota chip --- */
.quota-chip {
  position: fixed;
  top: 14px;
  left: 14px;
  z-index: 3;
  font-size: 11.5px;
  letter-spacing: 0.04em;
  padding: 4px 12px;
  border-radius: 999px;
  border: 0.5px solid rgba(255, 255, 255, 0.28);
  background: rgba(0, 0, 0, 0.24);
  color: var(--ink-soft);
}

/* --- preview nav (?preview=<N> dev-only, 窮舉 64 卦用) --- */
.preview-nav {
  position: fixed;
  top: 14px;
  left: 50%;
  transform: translateX(-50%);
  z-index: 3;
  display: flex;
  align-items: center;
  gap: 8px;
  font-size: 11.5px;
  letter-spacing: 0.04em;
  padding: 4px 6px;
  border-radius: 999px;
  border: 0.5px solid rgba(255, 255, 255, 0.28);
  background: rgba(0, 0, 0, 0.24);
  color: var(--ink-soft);
}
.preview-nav button {
  font-family: var(--sans);
  font-size: 13px;
  line-height: 1;
  color: var(--ink);
  background: rgba(255, 255, 255, 0.08);
  border: 0.5px solid rgba(255, 255, 255, 0.32);
  width: 22px;
  height: 22px;
  border-radius: 999px;
  cursor: pointer;
}
.preview-nav button:hover { background: rgba(255, 255, 255, 0.18); }
.preview-nav span { min-width: 6.5em; text-align: center; }

/* 窄機嘅 preview QA 模式同時有 quota、Read top controls 同 64 卦 nav。
   三組硬塞同一行會互相遮住；nav 落第二行，Read flow 預留返同等空間。正式 cast
   冇 preview-mode，所以產品版面完全不受影響。 */
@media (max-width: 480px) {
  body.preview-mode .preview-nav { top: 44px; }
  body.preview-mode #imm[data-phase="read"] .read { padding-top: 82px; }
}

/* --- footer disclaimer (bottom, in-scene) --- */
.disclaimer {
  position: fixed;
  left: 0;
  right: 0;
  bottom: 0;
  z-index: 2;
  text-align: center;
  font-size: 10.5px;
  letter-spacing: 0.03em;
  color: var(--ink-soft);
  text-shadow: 0 1px 6px rgba(0, 0, 0, 0.6);
  padding: 0 1rem 14px;
  pointer-events: none;
}

/* --- reduced motion: idle anim + parallax + tilt transition off, dim stays --- */
@media (prefers-reduced-motion: reduce) {
  #scene, #scene *, .dot { animation: none !important; }
  #scene, .scene-layer { transition: none !important; }
  .rings, .read, .exhausted, .para, .startb { transition: none !important; }
  .gw .face { transition: none !important; }
}
