:root {
  --stage-max: 750px;
  --brand-deep: #061c6e;
  --brand-blue: #0a2fb8;
  --brand-bright: #1e5cff;
  --brand-cyan: #4fe3ff;
  --brand-red: #e60012;
  --safe-b: env(safe-area-inset-bottom, 0px);
}

*,
*::before,
*::after {
  box-sizing: border-box;
}

html {
  -webkit-text-size-adjust: 100%;
}

/* 整屏切换：页面本身不滚动，靠 .reel 位移换屏 */
html,
body {
  height: 100%;
  overflow: hidden;
}

/* .sheet 是 display:flex，播放时被藏起来的热区是 position:absolute，
   两者的 display 都不是 none，不加这条 hidden 属性就形同虚设 */
[hidden] {
  display: none !important;
}

body {
  margin: 0;
  background: #04123f;
  color: #fff;
  font: 400 14px/1.6 -apple-system, BlinkMacSystemFont, "PingFang SC",
    "Helvetica Neue", "Microsoft YaHei", sans-serif;
  overflow-x: hidden;
  -webkit-tap-highlight-color: transparent;
}

/* 桌面端两侧留白，模拟手机内查看 */
@media (min-width: 800px) {
  body {
    background:
      radial-gradient(120% 60% at 50% 0%, #123a9e 0%, #04123f 60%, #02081f 100%);
  }
}

/* ---------- 顶部滚动进度 ---------- */

.progress {
  position: fixed;
  inset: 0 0 auto;
  z-index: 60;
  height: 3px;
  background: rgba(255, 255, 255, .1);
}

.progress i {
  display: block;
  height: 100%;
  width: 0;
  background: linear-gradient(90deg, var(--brand-cyan), #fff 60%, var(--brand-red));
  box-shadow: 0 0 10px rgba(79, 227, 255, .8);
  transition: width .1s linear;
}

/* ---------- 长图主体 ---------- */

/* 只露一屏的窗口 */
.stage {
  position: relative;
  width: 100%;
  max-width: var(--stage-max);
  height: 100svh;
  margin: 0 auto;
  overflow: hidden;
  background: var(--brand-blue);
  box-shadow: 0 0 80px rgba(0, 0, 0, .6);
}

/* 九屏竖着排成一条，靠位移把第 --i 屏推到窗口里。
   触摸手势由 js/app.js 接管，所以这里要关掉浏览器自己的滚动和回弹 */
.reel {
  display: block;
  touch-action: none;
  will-change: transform;
  transform: translate3d(0, calc(var(--i, 0) * -100svh), 0);
  transition: transform .58s cubic-bezier(.33, .92, .26, 1);
}

/* 一屏一页，每屏占满视口高度 */
.screen {
  position: relative;
  display: flex;
  align-items: center;
  justify-content: center;
  height: 100svh;
  overflow: hidden;
  background-size: cover;
  background-position: center;
  background-repeat: no-repeat;
}

/* 图和热区共用的等比盒子。热区的百分比坐标是相对它算的，
   所以它必须严丝合缝等于图片实际占的区域，不能大一分也不能小一分 */
/* 图就是设计稿本身 3000x5801（1:1.9337），一个像素都不加，所以永远不会变形。
   这个比例是照微信内视口定的，微信里几乎正好铺满；浏览器全面屏比它更瘦长，
   差出来的高度落在上下两条留白里，露出的是 .screen 的模糊底图（css/lqip.css
   生成，24px 宽的整屏缩略图按 cover 铺开），颜色天然接着图的上下边缘，看起来
   像虚化背景而不是黑边。16:9 的老机型反过来比图矮，会变成两侧各留十几个像素。
   min() 的意思是「整张图完整可见」，只在稿子不比目标机型长的时候成立。
   哪天换成 3000x7000（1:2.333）那种比任何手机都长的稿子，这句必须改成
   width:100%，靠 .screen 的 overflow:hidden 把多出来的高度上下均分裁掉，
   照搬现在这句会缩成两侧大留白。 */
.shot {
  position: relative;
  width: min(100%, 100svh * 3000 / 5801);
  aspect-ratio: 3000 / 5801;
  transition: opacity .5s ease, transform .58s cubic-bezier(.33, .92, .26, 1);
}

/* contain 而不是默认的 fill：正常情况下图和盒子都是 3000:5801，两者等效；
   但只要图的比例和盒子对不上（最常见的是访客缓存里还留着上一版尺寸的图），
   fill 会闷头拉伸，contain 只是留黑边。宁可露一条底色也不要变形 */
.shot img {
  display: block;
  width: 100%;
  height: 100%;
  object-fit: contain;
  opacity: 0;
  transition: opacity .45s ease;
}

.shot img.is-ready {
  opacity: 1;
}

/* 非当前屏压暗并轻微缩小，切过去的过程中长大、提亮，给翻页一点纵深。
   和 .reel 的位移同一条时长，两个动作看起来是一体的 */
.screen:not(.is-now) .shot {
  opacity: .55;
  transform: scale(.96);
}

@media (prefers-reduced-motion: reduce) {

  .reel {
    transition: none;
  }

  .shot,
  .shot img {
    opacity: 1 !important;
    transform: none !important;
    transition: none !important;
  }
}

/* ---------- 首屏下滑提示 ---------- */

/* 用 fixed 而不是贴在图底部：390 宽的手机上首屏图高 754px，微信内几乎正好等于
   可视区高度，贴着图底放会被折叠线切掉，钉在视口上才稳 */
.scroll-hint {
  position: fixed;
  left: 50%;
  bottom: calc(20px + var(--safe-b));
  z-index: 30;
  transform: translateX(-50%);
  width: 26px;
  height: 42px;
  border: 2px solid rgba(255, 255, 255, .85);
  border-radius: 14px;
  background: rgba(2, 8, 31, .3);
  backdrop-filter: blur(2px);
  -webkit-backdrop-filter: blur(2px);
  pointer-events: none;
}

.scroll-hint span {
  position: absolute;
  left: 50%;
  top: 8px;
  width: 4px;
  height: 8px;
  margin-left: -2px;
  border-radius: 2px;
  background: #fff;
  animation: hint 1.6s ease-in-out infinite;
}

@keyframes hint {

  0%,
  100% {
    transform: translateY(0);
    opacity: 1;
  }

  70% {
    transform: translateY(16px);
    opacity: 0;
  }
}

.scroll-hint.is-gone {
  opacity: 0;
  transition: opacity .4s;
}

/* ---------- 图上热区 ---------- */

.hot {
  position: absolute;
  left: var(--l);
  top: var(--t);
  width: var(--w);
  height: var(--h);
  padding: 0;
  margin: 0;
  border: 0;
  border-radius: 10px;
  background: transparent;
  cursor: pointer;
  /* 文案保留给读屏软件，视觉上不显示 */
  font-size: 0;
  color: transparent;
  overflow: hidden;
  -webkit-appearance: none;
  appearance: none;
}

.hot:active {
  transform: scale(.97);
  background: rgba(255, 255, 255, .12);
}

/* The agreement link sits on one line of fine print, so the measured hotspot is
   only about 79x16 CSS px on a 390px-wide phone, under the 24x24 that WCAG
   2.5.8 asks for. The visual highlight stays on the text; a transparent
   pseudo-element widens the hit area to 32px instead. Pseudo-elements take part
   in hit testing and events still target the button. It overflows the box, so
   this modifier has to release the clip that .hot sets. */
.hot--thin {
  overflow: visible;
}

.hot--thin::before {
  content: "";
  position: absolute;
  left: 0;
  right: 0;
  top: 50%;
  height: 32px;
  transform: translateY(-50%);
}

/* 视频播放热区：悬浮时轻微提亮 */
.hot--play {
  border-radius: 14px;
}

.hot--play:active {
  background: rgba(0, 0, 0, .2);
}

/* 设计稿上画成胶囊的按钮，按下时的高亮块要跟着圆，否则会露出四个直角 */
.hot--pill {
  border-radius: 999px;
}

/* Tap feedback for the s9 call-to-action: the pill dips and springs back while a
   ring sweeps outward. White matches the button's own text and arrow.
   The ring overflows the button box, so this modifier has to release the clip
   that .hot sets. In WeChat the jump is deferred until the animation is done,
   see the `join` action in js/app.js. */
.hot--tap {
  overflow: visible;
  animation: tap-press .26s ease-out;
}

.hot--tap::after {
  content: "";
  position: absolute;
  inset: 0;
  border-radius: inherit;
  border: 2px solid rgba(255, 255, 255, .9);
  box-shadow: 0 0 12px rgba(120, 226, 255, .7);
  animation: tap-ring .5s ease-out forwards;
  pointer-events: none;
}

@keyframes tap-press {
  40% {
    transform: scale(.94);
  }

  100% {
    transform: scale(1);
  }
}

@keyframes tap-ring {
  0% {
    transform: scale(1);
    opacity: .95;
  }

  100% {
    transform: scale(1.35);
    opacity: 0;
  }
}

/* H5-6 的设计稿里没画播放标识，整屏截图看不出能点，这里补一个（s2 换成视频截图当封面后同样用它）。
   尺寸 39x44、描边 #578de2、圆角接头都是从 H5-2 图上那个三角量出来的，
   svg 源码就是：<path d="M5 5 L35 22 L5 39 Z" fill 半透明 stroke #578de2>
   垂直位置默认在热区正中央，某屏想让按钮偏上/偏下就在 button 的 style 里加
   --cue-y:xx%（xx 是相对热区高度的百分比，0=贴热区顶，100=贴热区底） */
.hot--cue::before {
  content: "";
  position: absolute;
  left: 50%;
  top: var(--cue-y, 50%);
  width: 40px;
  height: 44px;
  margin: -22px 0 0 -20px;
  background:
    url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 40 44'%3E%3Cpath d='M5 5 L35 22 L5 39 Z' fill='rgba(150,205,255,.28)' stroke='%23578de2' stroke-width='5' stroke-linejoin='round'/%3E%3C/svg%3E")
    no-repeat center / contain;
  filter: drop-shadow(0 2px 6px rgba(0, 0, 0, .35));
}

/* 调试模式：地址加 ?debug=1 可看见所有热区框，方便对位微调 */
.debug .hot {
  background: rgba(255, 0, 0, .22);
  outline: 2px dashed #ff3b3b;
  font-size: 11px;
  color: #fff;
  text-shadow: 0 1px 2px #000;
  display: flex;
  align-items: center;
  justify-content: center;
  text-align: center;
  line-height: 1.2;
  padding: 2px;
}

/* ---------- 右侧圆点导航 ---------- */

.dots {
  position: fixed;
  right: 10px;
  top: 50%;
  transform: translateY(-50%);
  z-index: 40;
  display: flex;
  flex-direction: column;
  gap: 9px;
  opacity: 0;
  pointer-events: none;
  transition: opacity .3s;
}

.dots.is-on {
  opacity: 1;
  pointer-events: auto;
}

.dots button {
  width: 8px;
  height: 8px;
  padding: 0;
  border: 0;
  border-radius: 50%;
  background: rgba(255, 255, 255, .32);
  cursor: pointer;
  transition: all .25s;
}

.dots button.is-now {
  height: 20px;
  border-radius: 4px;
  background: var(--brand-cyan);
  box-shadow: 0 0 8px rgba(79, 227, 255, .9);
}

@media (max-width: 380px) {
  .dots {
    display: none;
  }
}

@media (prefers-reduced-motion: reduce) {

  .hot--ring::after,
  .scroll-hint span {
    animation: none;
  }
}

/* ---------- 弹层 ---------- */

.sheet {
  position: fixed;
  inset: 0;
  z-index: 80;
  display: flex;
  align-items: flex-end;
  justify-content: center;
}

.sheet__mask {
  position: absolute;
  inset: 0;
  background: rgba(2, 8, 31, .72);
  backdrop-filter: blur(3px);
  -webkit-backdrop-filter: blur(3px);
  animation: fade .25s ease;
}

.sheet__body {
  position: relative;
  width: 100%;
  max-width: var(--stage-max);
  padding: 22px 20px calc(22px + var(--safe-b));
  border-radius: 20px 20px 0 0;
  background: linear-gradient(180deg, #0d3aa8, #061c6e);
  border-top: 1px solid rgba(120, 226, 255, .4);
  box-shadow: 0 -10px 40px rgba(0, 0, 0, .5);
  animation: rise .3s cubic-bezier(.22, .9, .3, 1);
}

@keyframes fade {
  from {
    opacity: 0;
  }
}

@keyframes rise {
  from {
    transform: translateY(100%);
  }
}

.sheet__x {
  position: absolute;
  right: 12px;
  top: 8px;
  width: 32px;
  height: 32px;
  border: 0;
  background: transparent;
  color: rgba(255, 255, 255, .65);
  font-size: 26px;
  line-height: 1;
  cursor: pointer;
}

.sheet__title {
  margin: 0 0 10px;
  font-size: 18px;
  font-weight: 700;
}

.sheet__text {
  font-size: 13px;
  line-height: 1.75;
  color: rgba(255, 255, 255, .82);
  word-break: break-all;
}

/* 句子里穿插的短代码，保持行内不打断阅读 */
.sheet__text code {
  padding: 1px 5px;
  border-radius: 5px;
  background: rgba(0, 0, 0, .3);
  font-size: 12px;
  color: var(--brand-cyan);
}

.sheet__actions {
  display: flex;
  gap: 10px;
  margin-top: 18px;
}

.sheet__actions button {
  flex: 1;
  min-height: 44px;
  border: 1px solid rgba(120, 226, 255, .45);
  border-radius: 999px;
  background: rgba(255, 255, 255, .08);
  color: #fff;
  font-size: 15px;
  cursor: pointer;
}

.sheet__actions button.is-main {
  border-color: transparent;
  background: linear-gradient(100deg, #ff4d5f, var(--brand-red));
  font-weight: 700;
}

/* ---------- 框内播放 ---------- */

/* 视频就地铺在设计稿画的那个框里，不弹层、不放大。四个位置变量由 js/app.js 从
   同屏热区上原样抄过来，所以视频边界和图上的框天然重合，改热区不用改这里。 */
/* --controls-h is how much the native control bar eats at the bottom; the
   fullscreen button dodges it, see .film__full */
.film {
  --controls-h: 40px;
  position: absolute;
  left: var(--l);
  top: var(--t);
  width: var(--w);
  height: var(--h);
  z-index: 20;
  background: #000;
  overflow: hidden;
}

/* cover rather than contain: the video and the frame differ by a fraction of a
   percent, and contain would leave two black bands that make the frame drawn in
   the design look misaligned. cover trims a few pixels off top and bottom at
   most. That holds only while the source matches the frame -- a few percent off
   and the picture visibly jumps the moment playback starts, so there is an
   assertion in tools/verify/regress.mjs watching that gap. */
.film video {
  display: block;
  width: 100%;
  height: 100%;
  object-fit: cover;
  background: #000;
}

/* The native control bar owns the strip along the bottom of the frame, so the
   fullscreen button rides above it; in a small frame it would otherwise land on
   the scrubber. Bar height differs per engine (about 44px on iOS Safari, 36px
   on Android Chrome); --controls-h is a workable middle ground -- if some engine
   still covers the button, that is the one value to change. */
.film__full {
  position: absolute;
  right: 6px;
  bottom: var(--controls-h);
  width: 30px;
  height: 30px;
  padding: 0;
  border: 0;
  border-radius: 8px;
  background: rgba(2, 8, 31, .55) center / 17px 17px no-repeat
    url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%23fff' stroke-width='2.4' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M9 3H3v6M21 9V3h-6M15 21h6v-6M3 15v6h6'/%3E%3C/svg%3E");
  font-size: 0;
  color: transparent;
  cursor: pointer;
  -webkit-appearance: none;
  appearance: none;
}

.film__full:active {
  background-color: rgba(2, 8, 31, .8);
}

/* ---------- 轻提示 ---------- */

.toast {
  position: fixed;
  left: 50%;
  bottom: 96px;
  z-index: 100;
  max-width: 78%;
  padding: 11px 18px;
  border-radius: 999px;
  background: rgba(2, 8, 31, .92);
  border: 1px solid rgba(120, 226, 255, .35);
  color: #fff;
  font-size: 13px;
  text-align: center;
  transform: translate(-50%, 12px);
  opacity: 0;
  pointer-events: none;
  transition: opacity .25s, transform .25s;
}

.toast.is-on {
  opacity: 1;
  transform: translate(-50%, 0);
}
