@font-face {
  font-family: 'ArmWrestler';
  src: url('../font/ArmWrestler-Bold.otf') format('opentype');
  font-weight: bold;
  font-style: normal;
}
@font-face {
  font-family: 'EncodeSans';
  src: url('../font/EncodeSans-Regular.otf') format('opentype');
  font-weight: normal;
  font-style: normal;
}
@font-face {
  font-family: 'Acme';
  src: url('../font/Acme-Regular.otf') format('opentype');
  font-weight: bold;
  font-style: normal;
}

/* ---------- 基础 ---------- */
@keyframes puff-out-center {
  0%   { transform: scale(1);   filter: blur(0px); opacity: 1; }
  50%  { transform: scale(1.2); filter: blur(4px); opacity: 0.3; }
  100% { transform: scale(1);   filter: blur(0px); opacity: 1; }
}

[x-cloak] { display: none !important; }

/* ---------- 首屏入场（配合 x-cloak）----------
   Alpine 在 start() 时移除 x-cloak ⇒ 元素从 display:none 转入显示，animation 会**从头重播**
   （transition 不会触发：隐藏期间元素没有渲染盒，不存在「变化前的计算值」）。
   board.html / controller.html 内联自足，同样一套规则各写一份。

   三条不能碰的约束（都是踩过的坑）：
   1) transform / filter / will-change 会让元素成为 fixed 后代的包含块 ⇒ 动画只加在 body 的
      **后代**上，且这些后代内部没有 fixed（页面里的 fixed 都收在 .desktopLayer 或组件内部）。
   2) `#app` 上不能做 transform 动画：desktop-frame.css 给它的 scale(--ui-scale) 会被整条覆盖
      （动画优先级高于普通声明，窄列会瞬间变回满宽）。
   3) 含 fixed 后代的容器**整个不做动画**（尤其是 `.desktopLayer` 的子容器：零高度、内部全是 fixed）——
      容器一旦有 opacity 动画就会**临时**成为层叠上下文、并在收尾那一帧被释放，底部栏要在这一瞬间
      重新合成 ⇒ 看起来就是「最后闪一下」。桌面端改在 `.desktopLayer` 上做延迟淡入：它自带 transform，
      本来就一直是层叠上下文 + 合成层，动画前后状态不变；手机端的 `.desktopLayer` 没有 transform，
      同样会有「临时层叠上下文」的问题 ⇒ 手机端不做，底部栏跟随 body 的整页淡入。

   缓动统一 cubic-bezier(.22, 1, .36, 1)（快起步、柔收尾）。fill-mode 用 `both`：动画结束后继续保留
   动画状态，元素不会在收尾那一帧被「交还」给普通绘制路径而重新合成（那正是闪的来源）。
   代价是这些元素上不能再靠 inline 改 opacity / transform —— 首页的两个全屏浮层
   （.seasonPicker / .dayCalendar）自带 Alpine x-transition（不是「不参与动画」，而是它们**不在 #app 里**
   ——那两个浮层在页面末尾的 .desktopLayer 内，本文件这几条规则够不着），正文元素没有这种用法。 */
@keyframes page-in {
  from { opacity: 0;}
  to   { opacity: 1;}
}
/* 起始方向由元素自己的 --from-x / --from-y 决定（缺省自下而上 10px） */
@keyframes rise-in {
  from { opacity: 0; transform: translate3d(var(--from-x, 0px), var(--from-y, 10px), 0); }
  to   { opacity: 1; transform: none; }
}

/* 整页淡入（body 无 inline 样式、无其它动画，用 both 保持终态不跳变） */
body.default { animation: page-in .34s cubic-bezier(.22, 1, .36, 1) both; }

/* 分层错峰：页头两侧向外「对开」→ 主体自下浮起（<style> 不渲染 ⇒ nth-of-type 计数不用管它） */
#app > *:not(style) { animation: rise-in .52s cubic-bezier(.22, 1, .36, 1) both; }
#app > .logo,
#app > .back      { --from-x: -16px; --from-y: 0px; }
#app > .title,
#app > .pageTitle { --from-x: 16px;  --from-y: 0px; }
#app > *:nth-of-type(2) { animation-delay: .05s; }
#app > *:nth-of-type(3) { animation-delay: .11s; }
#app > *:nth-of-type(4) { animation-delay: .17s; }
#app > *:nth-of-type(5) { animation-delay: .23s; }
#app > *:nth-of-type(n+6) { animation-delay: .29s; }

/* 列表卡片逐条浮现（x-for 新插入的项也会各自入场）；只让前 6 条错峰，长列表不至于越等越久 */
#app > .main > *:not(template) { --from-y: 8px; animation: rise-in .46s cubic-bezier(.22, 1, .36, 1) .26s both; }
#app > .main > *:not(template):nth-of-type(2) { animation-delay: .31s; }
#app > .main > *:not(template):nth-of-type(3) { animation-delay: .36s; }
#app > .main > *:not(template):nth-of-type(4) { animation-delay: .41s; }
#app > .main > *:not(template):nth-of-type(5) { animation-delay: .46s; }
#app > .main > *:not(template):nth-of-type(6) { animation-delay: .51s; }

/* 吸附窗口的底部操作栏（桌面端）：在「层」上延迟淡入，让它最后落定（见文件顶部第 3 条） */
html.is-desktop-frame .desktopLayer {
  animation: page-in .44s cubic-bezier(.22, 1, .36, 1) .2s both;
}

@media (prefers-reduced-motion: reduce) {
  body.default,
  #app > *:not(style),
  #app > .main > *:not(template),
  html.is-desktop-frame .desktopLayer { animation: none; }
}

/* ---------- 按下反馈：给需要反馈的元素加 `.tap` 类（加在哪由页面决定）----------
   **触发在 js/tap-feedback.js，这里只出参数**（@keyframes + 老内核兜底）。为什么不在 CSS 里触发：
   · `:active` 只表示「此刻正按住」；手指一抬就失配 ⇒ 元素上不再有 animation 声明 ⇒ 浏览器
     **立即取消**动画、元素瞬间回到声明值（keyframes 不填 fill）⇒ 快速点一下只播了个头，
     看着就是「动画被瞬间结束」。触发源必须活过抬手。
   · 触摸端的 `:hover` 不是悬停而是**粘滞**的（Chromium bug 370155）：点完粘在原处直到点别处；
     而滚动结束时浏览器会按手指位置**重算 hover**，那往往已经落到**另一张卡片**上 ⇒ 抬手就
     给它播一遍（「滚动后弹一下」）；同一元素连点两次，第二次因 hover 已粘住而不重播。
   ⇒ 脚本在 `click` 上用 WAAPI 播放**这份 keyframes**（只认真点击：滚动 / 拖拽不会产生
      click ⇒ 不必自己判位移；连点按动画 id 收掉上一条重播）。所以 —— **参数只在这里改**，
      board / controller / review 的内联副本同理，四处关键帧保持一致。
   ⇒ 老内核没有 WAAPI：脚本给 <html> 挂 `.no-waapi`，下面那条按老办法退回 `:active`。

   三条不能碰的约束：
   1) 用 `scale` 这个**独立变换属性**而不是 transform —— 它与元素自己的 transform 叠加，
      不会把 .eye 的 translateY(-50%)、牌上的位移按错位；
   2) 用 animation 且**不填 fill** —— 不覆盖元素自己的过渡（发牌动画就是 transform/opacity 过渡），
      播完自动回到声明状态（这就是「恢复状态」）；
   3) 缩放**只能往小里缩**：transformed box 参与滚动溢出计算、也会被祖先 overflow 裁 ⇒ 放大
      （旧值 1.1 / 1.2）会把文档滚动区顶出去、视觉盒盖住邻居，就是「点一下布局闪一下」。
      实测 17 个 `.tap` 元素：放大版 8 个横向溢出 +12px，内缩 0/17。也去掉 `filter: blur()`：
      逐帧重新栅格化本身就是「闪」，还会把墨迹顶出盒子一圈。
      （`.button` 那类自带 puff 的元素别加 `.tap`。） */
@keyframes tap-pop {
  0%   { scale: 1;   opacity: 1; }
  35%  { scale: .9; opacity: .9; filter: blur(2px); }
  60%  { scale: 1;   opacity: 1; }
  100% { scale: 1;   opacity: 1; }
}
/* 老内核兜底（脚本给 <html> 加 `.no-waapi` 才会命中）：没有 WAAPI 就只能退回 `:active`，
   会被抬手截断 —— 老引擎下的已知局限。
   · **`!important` 不能省**：像赛局卡片这种 `.main > *` 身上挂着入场动画（`#app > .main > *`
     的特异性 (1,1,1) 比 `.tap:active` 的 (0,2,0) 高），不加就被压住、点了没反应。
   · 命中区不一定落在 `.tap` 元素**自己**身上：底栏那几张牌为了让点击形状贴合斜放的牌面，
     把命中区放在内部的 <rect> 上，父 <svg> 反而是 pointer-events:none ⇒ 还得认一种
     「元素内部的可点图形被按下」。下一条必须**单独成条**：老引擎不认识 `:has()` 会把
     **整条选择器列表**一起丢掉，若与上一条合并写，连「自己按下」的兜底都会没。 */
html.no-waapi .tap:active { animation: tap-pop .36s cubic-bezier(.22, 1, .36, 1) !important; }
html.no-waapi .tap:has(:active) { animation: tap-pop .36s cubic-bezier(.22, 1, .36, 1) !important; }
@media (prefers-reduced-motion: reduce) {
  html.no-waapi .tap:active,
  html.no-waapi .tap:has(:active) { animation: none !important; }
}

/* .button {
  user-select: none;
  cursor: pointer;
}
.button:active, .button:after, .button:before {
  animation: puff-out-center 0.5s both;
} */

/* ---------- 响应式 ----------
   桌面端（宽窗口 + 鼠标）适配已抽到 css/desktop-frame.css（配合 js/desktop-frame.js）：
   手机版整页缩成一条居中的窄列，底部操作栏 / 全屏浮层仍吸附窗口。
   这里不再对 body 做 transform —— 那会让 body 变成 shrink-to-fit，整页被压成一条，
   且 body 上的 transform 会让所有 position:fixed 元素改成相对 body 定位（底部栏落到文档末尾）。
   需要桌面适配的页面在 <head> 里额外引入这两个文件。 */
/* 画布（html）底色：**不是装饰色** —— body 上有整页淡入（`page-in` 从 opacity:0 开始），
   淡入的那几帧看到的就是这一层；滚到尽头（overscroll / 回弹）露出来的也是它。
   所以它必须等于**页面底色**，否则这两个瞬间会从顶部/底部透出一道异色边（原来是写死的 gray）。
   页面要别的画布色时用 --canvas 覆盖（如 login.html 的 #eaeaea）。 */
body, html {
  background: var(--canvas, #fff);
  margin: 0;
  padding: 0;
  font-family: 'EncodeSans', '微软雅黑', sans-serif;
  min-height: 100vh;
}
body { background: var(--canvas, #fff); }

/* ---------- 出血（edge-to-edge）：画布铺满整屏，内容用这四个变量让开系统栏 ----------
   值有两个来源，谁在场算谁的（行内样式优先级最高）：
     ① App（kdaladder-app）：DefaultActivity.pushPageInsets() 把真实系统栏尺寸写成
        documentElement 的行内 `--sat/--sab/--sal/--sar`；
     ② 浏览器 / iOS：走下面的 env() 默认值（需 `viewport-fit=cover`）。
   ⚠️ 为什么不只用 env()：Android WebView 要把 systemBars 当 safe-area 下发给网页得 M136+，
      旧版恒为 0（Chromium issue 40699457）⇒ App 里必须由原生提供。
   自包含页（board / controller / review 不引本文件）各自在 <style> 里声明一份同样的变量。

   让位只有两种写法，别再造第三种：
     · **整个盒子**要避开系统栏（整页 / 整屏浮层）→ 用 margin（或 inset:0 + margin）；
     · 只要把**流内内容**推下去（吸顶条之类）→ 用 padding。
   ⚠️ 为什么盒子不能用 padding：绝对定位的包含块是祖先 padding box 的**外沿**，
      padding 只推得动流内内容，推不动 position:absolute 的子元素
      （在 .endPage / .personPage / .logo 这类元素上写 padding 是**没任何效果**的）。 */
:root {
  --sat: env(safe-area-inset-top, 0px);
  --sab: env(safe-area-inset-bottom, 0px);
  --sal: env(safe-area-inset-left, 0px);
  --sar: env(safe-area-inset-right, 0px);
}
/* 整页内容整体下移一个安全区（底部不用管：固定条自己按 --sab 上移，见 components/page-chrome.html）。
   用 margin 而不是 padding：margin 把整个盒一起挪，页内绝对定位的 .logo / .back / .pageTitle
   自动跟着走，不用逐个点名；#app 必须 position: relative，它们才会以它为基准。 */
#app {
  position: relative;
  margin-top: var(--sat);
}

/* ---------- 子标题栏 ---------- */
.sub-top {
  display: flex;
  flex-direction: row;
  align-items: flex-end;
  justify-content: center;
  position: relative;
}
.sub-top .iconBox { display: contents; }
.sub-top .text {
  font-size: 4vw;
  color: #000;
  border-bottom: 1px solid gray;
  padding: 0 2vw;
}
.sub-top .sub-text {
  font-size: 3vw;
  color: gray;
  position: absolute;
  top: 6vw;
}

/* ---------- Banner ---------- */
.banner {
  position: absolute;
  width: 100%;
  overflow-x: hidden;
  margin-top: -12vw;
  height: 6vw;
}
.banner .banner-btn {
  background: #ffdb0f;
  border-radius: 5vw;
  position: absolute;
  top: 0;
  padding: 0.5vw 8vw;
  font-size: 3.5vw;
  color: rgb(74, 74, 74);
  display: flex;
  align-items: center;
  justify-content: center;
}
.banner .banner-btn img {
  width: 6vw;
  height: 6vw;
  position: absolute;
  opacity: 0.7;
}
.banner .left-btn  { background: #fefefe; left: -5vw; }
.banner .left-btn img  { right: 0; }
.banner .right-btn { background: #fefefe; right: -5vw; }
.banner .right-btn img { left: 0; }

/* ---------- 主内容区 ---------- */
.main { position: relative; }

/* 顶部占位 */
.top-holder {
  height: 40vw;
  background-image: linear-gradient(0deg, #ffffff 30%, #8e8e8e 200%);
  margin-bottom: -22vw;
}

/* ---------- 未登录提示 ---------- */
.need-login {
  position: absolute;
  width: 100%;
  height: 100%;
  display: flex;
  align-items: center;
  justify-content: center;
  background: rgba(0,0,0,0.1);
  z-index: 10;
  color: #ffd600;
  font-weight: bold;
  text-decoration: underline double;
  border-radius: 2vw;
}
.need-login span {
  padding: 3vw 5vw;
  border-radius: 1.5vw;
  background: #808080;
  cursor: pointer;
}

/* ---------- 弹窗 ---------- */
@keyframes dialog-ani {
  0%   { transform: scale(1.2); filter: blur(2px); opacity: 0; }
  100% { transform: scale(1);   filter: blur(0px); opacity: 1; }
}
.prompt {
  z-index: 3002;
  position: fixed;
  width: 100%;
  height: 100%;
  background: rgba(0,0,0,0.8);
  display: flex;
  justify-content: center;
  align-items: center;
  top: 0;
  left: 0;
  animation: dialog-ani 0.5s both;
}
.prompt .dialog {
  width: 50vw;
  background: #cacaca;
  border-radius: 2vw;
  padding: 4vw;
  display: flex;
  flex-direction: row;
  flex-wrap: wrap;
  justify-content: space-between;
}
.prompt .dialog .title {
  font-size: 5vw;
  margin: 3vw 0;
  width: 100%;
  color: #343434;
}
.prompt .dialog input {
  font-size: 5vw;
  color: black;
  width: 100%;
  border-radius: 1vw;
  border: 0;
  padding: 2vw 5vw;
  box-sizing: border-box;
}
.prompt .dialog button {
  width: 100%;
  margin-top: 3vw;
  font-size: 3.5vw;
  padding: 2vw 0;
  color: black;
  border: 0;
  border-radius: 0.2rem;
  animation: none;
}
.prompt .dialog .okBtn     { background: black; color: white; }
.prompt .dialog .cancelBtn { background: gray;  color: black; }
.prompt.alert .dialog      { justify-content: center; }

/* ---------- 状态遮罩（.pageNotice）：未登录 / 加载失败 / 暂无数据 ----------
   已抽成共享组件 **components/page-notice.html**（标记 + 样式都在那份文件里，
   index / history / match 三页各用一行 <div x-include="components/page-notice.html"></div> 引入）；
   宿主侧配套的动作在 **js/page-notice.js**（retryNotice：整屏点击 / 回车都调它）。 */

/* ---------- 示例数据水印（.demoWatermark）----------
   只有「一场比赛都没有」的账号才会看到（数据见 js/demo-data.js）：三个内容页（index / history / match）
   各在 #app 末尾放一个空的 div.demoWatermark，斜向循环的半透明贴层就铺满可视区，
   一眼就能看出「这些成绩是编的」，又不用在每张卡片上写一遍。三处讲究：
     · `pointer-events:none` —— 水印绝不能吃掉点击（日期牌 / 记录卡片 / 按钮都要照常能点）；
     · 用 `position:fixed` 而不是 absolute —— 手机端贴着窗口（滚动时一直在），
       桌面端 #app 有 transform（缩放）⇒ fixed 会改成相对 #app 定位，正好铺满整条窄列，两种形态不用各写一套；
     · `#app > .demoWatermark{animation:none}` —— 别让它跟着 #app 的入场动画淡入（常驻层动起来反而像内容在闪）。
   水印文字与副标题都在 data URI 里（SVG 百分比编码），改文案时注意同步编码。 */
.demoWatermark {
  position: fixed;
  inset: 0;
  z-index: 11;            /* 高于页面内容（日期块的 9/10），低于吸顶说明条（12）与底部操作栏（20） */
  pointer-events: none;
  background-repeat: repeat;
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='260' height='160'%3E%3Cg transform='rotate(-24 130 80)' fill='%23000' fill-opacity='0.075' font-family='sans-serif' text-anchor='middle'%3E%3Ctext x='130' y='72' font-size='27' font-weight='bold'%3E%E7%A4%BA%E4%BE%8B%E6%95%B0%E6%8D%AE%3C/text%3E%3Ctext x='130' y='100' font-size='15'%3E%E8%99%9A%E6%9E%84%20%C2%B7%20%E4%BB%85%E4%BE%9B%E9%A2%84%E8%A7%88%3C/text%3E%3C/g%3E%3C/svg%3E");
}
#app > .demoWatermark { animation: none; }

/* ---------- 倒计时动画 ---------- */
@keyframes cooldown {
  0%   { background-position: 100% 50%; }
  100% { background-position: 0% 50%; }
}
button.cooldown {
  position: relative;
  overflow: hidden;
}
button.cooldown:before {
  content: ' ';
  width: 100%;
  height: 100%;
  display: block;
  position: absolute;
  left: 0;
  top: 0;
  background-size: 200% 200% !important;
  background-image: linear-gradient(90deg, white 0%, white 50%, black 50%, black 100%) !important;
  opacity: 0.3;
}
button.cooldown5000:before { animation: cooldown 5s linear !important; }
button.cooldown2000:before { animation: cooldown 2s linear !important; }
