/* ==========================================================================
   方案B —— 完全对照 botcgrimoire.top（= limpy01/townsquare，bra1n/townsquare 的 fork）
   的源码做外观：token.png 令牌 + Papyrus 弧形角色名 + life/death/shroud + reminder 圆片
   + demon-head 中央剧本盘 + 夜色幕布 + background.jpg。
   数值全部按 townsquare 的 SCSS 原样搬；这里只把 SCSS 变量/循环换成普通 CSS（循环改由 JS 下发）。
   ========================================================================== */

@font-face { font-family: "Papyrus"; src: url("/ts/fonts/papyrus.woff2") format("woff2"); font-display: swap; }
@font-face { font-family: "PiratesBay"; src: url("/ts/fonts/piratesbay.ttf"); font-display: swap; }

:root {
  --ts-townsfolk: #1f65ff;
  --ts-outsider: #46d5ff;
  --ts-minion: #ff6900;
  --ts-demon: #ce0100;
  --ts-traveler: #cc04ff;
  --ts-fabled: #ffe91f;
  --ts-panel: rgba(0, 0, 0, .5);
  --ts-font: "Roboto Condensed", "PingFang SC", "Microsoft YaHei", "Hiragino Sans GB", system-ui, sans-serif;
}

/* ---------------- 底色 / 字体（App.vue 的 html,body + #app 背景） ---------------- */
html { background: #080d14; }
body {
  background: url("/ts/background.jpg?v=3") center center;
  background-size: cover;
  background-attachment: fixed;
  /* ★ 兜底底色：图片被拦 / 加载失败时也不让整页塌成纯黑（排查过：某些手机浏览器
     会屏蔽全部图片请求，那时整页只剩文字，看起来就是「全黑，啥也看不见」） */
  background-color: #0d1622;
  font-family: var(--ts-font);
  font-size: 16px;
  color: #fff;
}
/* 去掉旧版的暗色渐变底层，换成 townsquare 的夜色幕布 */
body::before, body::after { display: none !important; }

#tsBackdrop {
  position: fixed; inset: 0; z-index: -1; pointer-events: none;
  opacity: 0; transition: opacity 1s ease-in-out;
  background: linear-gradient(180deg, rgba(0, 0, 0, 1) 0%, rgba(1, 22, 46, 1) 50%, rgba(0, 39, 70, 1) 100%);
}
#tsBackdrop::after {
  content: " "; display: block; width: 100%; height: 100%;
  /* ★ 2026-09-22：这里**只留 webp 一层**。双层不是「兜底」而是「两张都下载」——
     实测 clouds.png 140KB + clouds.webp 84KB = 224KB，占了手机首屏图片流量的一半。
     云是纯装饰（透明度 .3、在令牌后面），老浏览器不认 webp 时没有它也不影响任何可读性，
     所以这里能放心只留一层；令牌底图那种「没了就看不见」的才继续留着 png 兜底。 */
  background: url("/ts/clouds.webp?v=3") repeat; background-size: 2000px auto;
  animation: ts-clouds 120s linear infinite; opacity: .3;
}
@keyframes ts-clouds { from { transform: translate3d(-2000px, 0, 0); } to { transform: translate3d(0, 0, 0); } }
body.night #tsBackdrop { opacity: .5; }

#app { max-width: 900px; padding: 10px 12px 40px; }

/* ---------------- 面板 / 按钮：townsquare 的黑底圆角 + 硬黑描边 ---------------- */
.card, .topbar, .info-box, .ts-top {
  background: var(--ts-panel);
  border: 3px solid #000;
  border-radius: 10px;
  box-shadow: none;
  filter: drop-shadow(0 4px 6px rgba(0, 0, 0, .5));
  backdrop-filter: none; -webkit-backdrop-filter: none;
}
.card { padding: 12px; }
.topbar { font-size: 13px; }
h1, h2, .ts-title { font-family: PiratesBay, sans-serif; font-weight: normal; letter-spacing: 1px; }
h2 { color: #fff; }
.sub, .tiny { color: #d6dbe4; }
button, .btn {
  background: linear-gradient(#4e4e4e, #040404) content-box, linear-gradient(#292929, #010101) border-box;
  border: solid 3px transparent;
  border-radius: 15px;
  color: #fff; font-weight: bold; text-shadow: 1px 1px rgba(0, 0, 0, .5);
  box-shadow: inset 0 1px 1px #9c9c9c, 0 0 10px #000;
  padding: 9px 12px;
}
button:hover { color: red; }
button.primary {
  background: radial-gradient(at 0 -15%, rgba(255, 255, 255, .07) 70%, rgba(255, 255, 255, 0) 71%) 0 0/80% 90% no-repeat content-box,
    linear-gradient(#0031ad, rgba(5, 0, 0, .22)) content-box, linear-gradient(#292929, #001142) border-box;
  border-color: transparent; color: #fff; box-shadow: inset 0 1px 1px #002c9c, 0 0 10px #000;
}
button.danger { border-color: transparent; background: radial-gradient(at 0 -15%, rgba(255, 255, 255, .07) 70%, rgba(255, 255, 255, 0) 71%) 0 0/80% 90% no-repeat content-box, linear-gradient(#ad0000, rgba(5, 0, 0, .22)) content-box, linear-gradient(#292929, #420000) border-box; color: #fff; }
button.sm { padding: 6px 10px; }
.pick { background: rgba(0, 0, 0, .45); border: 3px solid #000; border-radius: 10px; }
.pick.sel { background: #123a6b; border-color: #4e8cf0; color: #fff; }
.seat { background: rgba(0, 0, 0, .45); border: 3px solid #000; border-radius: 10px; }
.night-veil { background: #000; }

/* ==================== 城镇广场（#townsquare / .circle / Player / Token） ==================== */
.fl-square {
  position: relative; width: 100%;
  height: var(--fl-h, min(calc(100vh - 128px), 860px)); min-height: 300px;
  display: flex; align-items: center; align-content: center; justify-content: center;
  /* 房主端留 18px；玩家端/大屏（data-round）只留 8px —— 少爷嫌玩家端圆环太小，
     手机上每一像素宽度都直接换成圆环的半径。 */
  /* 房主端留 12px（原来是 18 —— 少爷 2026-09-22：「圆再往外扩扩」）；
     玩家端/大屏（data-round）只留 8px —— 手机上每一像素宽度都直接换成圆环的半径。 */
  padding: 12px;
  /* townsquare 的 html/body 是 1.2em（桌面 23px）；这里按令牌大小等比推，手机上下限兜住。
     放在 .fl-square 上，圆环和中央剧本盘就都跟着缩放。 */
  font-size: clamp(9px, calc(var(--fl-token, 60px) * .19), 24px);
}
.fl-square[data-round] { padding: 8px; }
/**
 * ★ 2026-09-23：房主端顶部压着两条**浮动**工具条（邀请/流程/魔典/复盘/设置 + 计时），
 *   它们是 fixed 定位、不占文档流 —— 环一缩小，最上面那个令牌就钻进计时条底下
 *   （实测令牌顶边 158px、计时条底边 163px，压住 5px）。
 *   给方块补 40px 顶部内边距：环心跟着 padding box 一起下移 20px，令牌顶边回到 ~178px。
 *   layout() 算 --fl-ry 时会读到这个 padding，所以环的**大小**不会因此变小（它受 OVAL_MAX 管）。
 */
.fl-square[data-fit] { padding-top: 40px; }
.fl-circle { position: relative; padding: 0; margin: 0; list-style: none; width: 100%; height: 100%; }
/* 座位定位：椭圆坐标。
   --fl-rx / --fl-ry 是椭圆半径（由 layout() 下发，竖屏 rx≠ry），
   --fl-ux / --fl-uy 是这个座位在单位椭圆上的坐标（-1..1，按弧长等分）。
   全部走 CSS 变量，重渲染时不会触发任何 transform 过渡 —— 之前「⊕ 都在转」的教训。 */
.fl-circle > li {
  position: absolute; pointer-events: none;
  left: calc(50% + var(--fl-rx, 0px) * var(--fl-ux, 0));
  top: calc(50% + var(--fl-ry, 0px) * var(--fl-uy, 0));
  width: var(--fl-token, 60px); height: var(--fl-token, 60px);
  margin-left: calc(var(--fl-token, 60px) * -.5);
  margin-top: calc(var(--fl-token, 60px) * -.5);
}
.fl-circle > li:hover { z-index: 60 !important; }
.fl-circle > li > .fl-player { position: relative; width: 100%; pointer-events: all; }
/* 圆片自己绝对定位（挂在环心→座位的连线上），但鼠标事件照旧要能点 */
.fl-circle > li > .fl-reminder { position: absolute; pointer-events: all; }

/* -------- 玩家（Player.vue .circle .player） -------- */
.fl-square .fl-player { margin-bottom: 10px; }
.fl-square .fl-player::before { content: " "; display: block; padding-top: 100%; }

/* 帷幕（Player.vue .shroud）—— 死亡的唯一标志。
   少爷两条要求：① 不要那块黑色背景，只留帷幕本身；② 帷幕要大一点，一眼能看出几号死了。
   所以：宽度撑到令牌的 1.4 倍、高度 0.72 倍，图按高度铺满（auto 100%）并居中。 */
.fl-square .fl-player .fl-shroud {
  top: -6%; left: -20%; position: absolute; width: 140%; height: 72%; cursor: pointer;
  transform: rotateX(0deg); transform-origin: top center; transition: transform 200ms ease-in-out;
  z-index: 2; filter: drop-shadow(0 0 5px rgba(0, 0, 0, .8));
}
.fl-square .fl-player .fl-shroud::before {
  content: " ";
  /* ★ 千万别写回 background 简写：`background: url(a), url(b) center -10px no-repeat` 里
     的 no-repeat / 位置只作用于**最后一层**，第一层(webp)保留默认的 repeat。
     shroud 图是 166×300，auto 110% 下渲染宽只有盒子的 ~27% ——
     于是同一个帷幕被横向平铺了 3.65 次，少爷手机上数出来「4 个」。
     必须分层把 position / repeat / size 都显式写全。 */
  background-image: url("/ts/shroud.webp?v=3");
  background-position: center top, center top;
  background-repeat: no-repeat, no-repeat;
  background-size: auto 100%, auto 100%;
  /* 兜底底色去掉（少爷：黑色背景不好看）——图挂掉时靠 drop-shadow 里那圈黑边示意「已盖」 */
  position: absolute; margin-left: -50%; width: 100%; height: 100%; left: 50%; top: 0;
  opacity: 0; transform: perspective(400px) scale(1.5); transform-origin: top center;
  transition: all 200ms; pointer-events: none;
}
.fl-square .fl-player.dead .fl-shroud::before { opacity: 1; top: 0; transform: perspective(400px) scale(1); }
.fl-square .fl-player.dead > .fl-playername { opacity: .5; }

/* 令牌（Token.vue .token） */
.fl-square .fl-token {
  position: absolute; left: 0; top: 0; width: 100%; z-index: 1;
  border-radius: 50%; background: url("/ts/token.webp?v=3") center center; background-size: 100%;
  /* ★ 兜底底色（放在 background 之后才生效）：令牌图挂掉时依然是个米白圆盘，
     而不是「黑底上一个看不见的黑圈」——手机上被拦图片时就靠它撑住可读性 */
  background-color: #e3d0ad;
  text-align: center; border: 3px solid #000; box-shadow: 0 0 10px rgba(0, 0, 0, .5);
  cursor: pointer; display: flex; align-items: center; justify-content: center;
  transition: border-color 250ms; padding: 0; overflow: visible;
}
/* 公开视角的令牌背面（TownSquare.vue 里 .life 翻到正面） */
.fl-square .fl-token.fl-life { background-image: url("/ts/life.webp?v=3"); background-color: #e8d6b6; }
.fl-square .fl-token.fl-death { background-image: url("/ts/death.webp?v=3"); background-color: #6f7783; }
/* 「我还没标记过这个人 / 这个位子还空着」—— 就用官方那张令牌底图（token.png），
   跟已标记的令牌同一个底，只是上面没有角色立绘和名字。
   （少爷 2026-09-21：别自己画灰底问号了，改成 botcgrimoire 上那张官方 token 图。）
   生死仍然靠上面那唯一一层黑帷幕区分，不靠底色。 */
.fl-square .fl-token.fl-unknown {
  background-image: url("/ts/token.webp?v=3");
  background-color: #e3d0ad;
}
.fl-square .fl-token > span {
  position: absolute; width: 100%; height: 100%; background-size: 100%;
  pointer-events: none; border-radius: 50%;
}
.fl-square .fl-token > span.fl-icon { margin-top: 3%; background-repeat: no-repeat; background-position: center 30%; }
.fl-square .fl-token > span.fl-leaf-left { background-image: url("/ts/leaf-left.webp?v=3"); }
.fl-square .fl-token > span.fl-leaf-right { background-image: url("/ts/leaf-right.webp?v=3"); }
.fl-square .fl-token > span.fl-leaf-orange { background-image: url("/ts/leaf-orange.webp?v=3"); }
.fl-square .fl-token > span.fl-leaf-top1 { background-image: url("/ts/leaf-top1.webp?v=3"); }
.fl-square .fl-token > span.fl-leaf-top2 { background-image: url("/ts/leaf-top2.webp?v=3"); }
.fl-square .fl-token > span.fl-leaf-top3 { background-image: url("/ts/leaf-top3.webp?v=3"); }
.fl-square .fl-token > span.fl-leaf-top4 { background-image: url("/ts/leaf-top4.webp?v=3"); }
.fl-square .fl-token > span.fl-leaf-top5 { background-image: url("/ts/leaf-top5.webp?v=3"); }
.fl-square .fl-token .fl-fb {
  position: absolute; font-size: 150%; font-weight: bold; color: #333;
  font-family: Papyrus, serif; pointer-events: none;
}

/* 弧形角色名（Token.vue svg.name + textPath）
   注意：这个 svg 必须留在文档流里 —— 它靠 viewBox 的 1:1 比例把 .token 撑成正方形
   （townsquare 也是这样：.name 宽 100%、高 auto → 高=宽）。设成 absolute 会让令牌塌成一条线。 */
.fl-square .fl-token .fl-namesvg { width: 100%; height: auto; display: block; font-size: 24px; pointer-events: none; }
.fl-square .fl-token .fl-namesvg text {
  fill: #000; stroke: #fff; stroke-width: 2px; paint-order: stroke;
  font-family: Papyrus, "STKaiti", "KaiTi", "楷体", serif;
  font-weight: bold; letter-spacing: 1px; text-shadow: 0 2px 2px rgba(0, 0, 0, .2);
}
.fl-square .fl-token:hover .fl-namesvg text { stroke: #000; fill: #fff; }

/* 座位号（Player.vue .seatNum） */
.fl-square .fl-seatNum {
  position: absolute; top: -5%; left: 8%; font-size: 120%; z-index: 3; font-weight: bold;
  text-shadow: -1px 1px #000, 1px 1px #000, -1px -1px #000, 1px -1px #000;
}
.fl-square .fl-player.me .fl-seatNum { color: #ffd24a; }
.fl-square .fl-player.talking .fl-seatNum { color: var(--ts-townsfolk); }

/* 被提名 / 待处决：townsquare 的 marked（骷髅） */
.fl-square .fl-marked {
  position: absolute; left: 0; top: 0; width: 100%; height: 100%; z-index: 4;
  display: flex; align-items: center; justify-content: center; pointer-events: none;
}
.fl-square .fl-marked i { font-style: normal; font-size: 260%; color: #fff; opacity: .55;
  filter: drop-shadow(0 0 6px #000); text-shadow: 0 0 6px #000; }

/* 玩家名（Player.vue .player > .name）—— **挂在「环心 → 座位圆心」的连线上**。
   旧版是「令牌正下方固定一块」：那样和同样要往圆心摆的提醒圆片必然打架
   —— 环上半圈的座位，圆片往圆心走就是往下走，正好压住名字（少爷截图里那个问题）。
   现在名字框和圆片走同一条**直线**（--fl-rdx/--fl-rdy：环心 → 座位圆心的单位向量），
   **名字朝环外、圆片朝环内**，中间隔着一整个令牌，谁也压不到谁。
   ★ 方向必须用径向向量，不能用椭圆外法线 --fl-fx/--fl-fy：椭圆上两者差 10° 以上，
     房主端竖屏椭圆时圆片就歪出那条连线（实测 ⊕ 偏离 sin=0.18）。
   --fl-dn = 名字框中心离座位圆心多远（由 layout() 按令牌大小和环半径算好下发）。 */
.fl-square .fl-player > .fl-playername {
  position: absolute; left: 50%; top: 50%; display: flex; justify-content: center;
  /**
   * ★ 少爷 2026-09-22：「座位把角色名字那一栏挡住了」。
   *   .fl-token 有 z-index:1，而名字框以前**没写 z-index** —— 于是名字永远被令牌盖在下面，
   *   名字框再怎么往外推，只要和圆的边角有一点几何重叠，那一块就看不见。
   *   名字本来就该压在令牌之上（线下也是名字牌摆在令牌前面）。
   */
  z-index: 5;
  /**
   * ★ 2026-09-23：尺寸和方向**照参考站 botcgrimoire.top 实测的比例**重做。
   *
   *   那边（393 视口 / 12 人 / 令牌 55px）量出来：名字框 66×26、字号 12px，
   *   也就是 宽 1.20×令牌、高 0.47×令牌、字号 0.20×令牌。
   *   我们旧值（宽 104%、字号 80%→实测只有 8.4px、行高 170%）在手机上小到看不清，
   *   少爷说的「名字的比例太怪」有一半是这个。
   *
   *   方向也一样：参考站的名字**一律挂在令牌正下方**（实测中心偏移恒为 (0, +40.7)），
   *   而旧版是沿「环心 → 座位」的径向往外推 —— 1 号飘在上方、4 号在右边、10 号在左边，
   *   整圈名字像花瓣一样散开，这是「位置太怪」的另一半。
   *   --fl-dn 由 layout() 下发（= 0.74×令牌），这里只管往下走。
   */
  width: 120%; height: calc(var(--fl-token, 60px) * .47);
  align-items: center;
  font-size: calc(var(--fl-token, 60px) * .2); cursor: pointer; white-space: nowrap;
  margin: 0; box-sizing: border-box;
  background: rgba(0, 0, 0, .5); border: 2px solid #000; border-radius: 8px;
  box-shadow: 0 0 5px #000; padding: 0 3px;
  transform: translate(-50%, -50%) translateY(var(--fl-dn, 40px));
}
.fl-square .fl-player > .fl-playername > span {
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap; text-align: center; flex-grow: 1;
}
.fl-square .fl-player.empty > .fl-playername > span { color: #cfd6e2; opacity: .8; }
.fl-square .fl-player.nominated > .fl-playername { background: rgba(160, 20, 20, .7); }
.fl-square .fl-token { position: absolute; }
.fl-square .fl-player.nominated .fl-token { border-color: #ff2a1e; box-shadow: 0 0 14px rgba(255, 42, 30, .8); }
.fl-square .fl-player.me .fl-token { border-color: #1f65ff; }

/* 能力提示（Token.vue .ability）
   ★ 默认必须是 display:none，不能用 opacity:0 ——
   opacity:0 的元素照样占布局：它 left:120% + width:250px 会把文档宽度撑到 492px
   （手机视口才 393），安卓 Chrome 一看内容比视口宽，就允许用户「缩小」，
   右侧于是露出一条黑边（少爷手机上那个「莫名其妙能缩小」就是这么来的）。 */
.fl-square .fl-token .fl-ability {
  display: none; position: absolute; padding: 5px 10px; left: 120%; width: 250px; z-index: 25;
  font-size: 80%; background: rgba(0, 0, 0, .5); border-radius: 10px; border: 3px solid #000;
  filter: drop-shadow(0 4px 6px rgba(0, 0, 0, .5)); text-align: left; align-items: center;
  pointer-events: none; opacity: 0; transition: opacity 200ms ease-in-out; white-space: normal;
  line-height: 1.5; color: #fff;
}
.fl-square .fl-ability.left { right: 120%; left: auto; }
.fl-square .fl-token:hover .fl-ability { display: flex; opacity: 1; }

/* 提醒标记圆片（Player.vue .circle .reminder）
   ★ 圆心严格落在「环心 → 座位圆心」的连线上：先把圆心对齐座位圆心（left/top 50% +
     translate(-50%,-50%)），再沿径向向量的**反方向**（朝圆心）推出去 --fl-d。
     --fl-d = --fl-d0（第一个圆片的距离）+ --fl-ri（第几个）× --fl-ds，
     全部由 layout() / seatHtml 算好下发 —— 也就不会再出现「环上半圈的加号压住名字」。
     --fl-d0 是**每个座位单独下发**的：挂的圆片越多整串越往外挪，
     免得最后一个糊到中央剧本盘上。
     旧写法是「流内排在令牌正下方再用 transform 掰」：流内位置固定朝下，
     于是上半圈的圆片原地不动（压名字）、下半圈的被掰飞出去老远，两头都不对。 */
.fl-square .fl-reminder {
  position: absolute; left: 50%; top: 50%;
  background: url("/ts/reminder.webp?v=3") center center; background-size: 100%;
  background-color: #f6dfbd;   /* 兜底：圆片图挂掉也别变成黑洞 */
  /* ★ 2026-09-23 少爷：「提醒标记的圈可以再小点」—— 50% → 40%（直径从 0.61×令牌降到 0.51×）。
     边框仍是 3px，缩小后描边反而更显眼，不会糊在令牌上。 */
  width: 40%; height: 0; padding-bottom: 40%; box-sizing: content-box;
  display: flex; align-items: center; justify-content: center;
  margin: 0; border-radius: 50%; border: 3px solid #000;
  --fl-d: calc(var(--fl-d0, 46px) + var(--fl-ri, 0) * var(--fl-ds, 30px));
  /* ★ 2026-09-23（第二十四轮）：圆片**沿径向朝环心**摆 —— 实测参考站 botcgrimoire.top 的
     `<div class="reminder add">`：10 号（环左侧）偏移 (+32, −2.5) 是纯朝右（朝环心）；
     1 号（环顶）偏移 (−24, +57) 与「令牌 → 环心」单位向量点积 0.99。
     --fl-rdx/--fl-rdy 是「环心 → 座位」的单位向量，乘 −1 即朝环心方向。
     ⚠ 第二十三轮我改成「与名字同一条竖线朝下」是错的，这里已回滚。 */
  transform: translate(-50%, -50%) translate(calc(var(--fl-rdx, 0) * var(--fl-d) * -1), calc(var(--fl-rdy, 0) * var(--fl-d) * -1));
  /* 只过渡透明度：带 transform 的话，重渲染时圆片会「转」一圈 */
  /* z-index 必须高于名字（5）：环上半圈圆片与名字条必然重叠（参考站同样如此），
     而且圆片是点击入口，被名字盖住就点不到了。 */
  box-shadow: 0 0 10px rgba(0, 0, 0, .5); transition: opacity 200ms; cursor: pointer; z-index: 6;
}
/* 圆片上半部那个「角色专属小图」—— 中毒画瓶子、占卜师的干扰项画水晶球…
   （townsquare 里是 assets/icons/<roleId>.png，我们直接用角色自己的立绘，同一个意思） */
.fl-square .fl-reminder .fl-ricon {
  position: absolute; left: 8%; top: 5%; width: 84%; height: 56%;
  background-repeat: no-repeat; background-position: center center; background-size: contain;
  pointer-events: none;
}
.fl-square .fl-reminder .fl-rt {
  position: absolute; bottom: 6%; width: 100%; text-align: center;
  font-size: 52%; font-weight: bold; color: #000; line-height: 1.05;
  text-shadow: 0 1px 1px #f6dfbd, 0 -1px 1px #f6dfbd, 1px 0 1px #f6dfbd, -1px 0 1px #f6dfbd;
  word-break: break-word;
}
/* ★ 2026-09-25 少爷：「自定义的提醒标记 文字居中 怎么有点靠上」。
   带角色小图的圆片文字仍贴下半部（上面让给图）；**只有文字没有图的**（= 自己写的那句）
   改成真正垂直居中：top:50% + 往上挪自身一半。别用 flex 居中——圆片是 absolute + 伪等高的。 */
.fl-square .fl-reminder .fl-rt.center {
  top: 50%; bottom: auto; transform: translateY(-50%);
}
.fl-square .fl-reminder.add { opacity: .3; }
.fl-square .fl-reminder.add .fl-pi {
  position: absolute; top: 4%; left: 4%; width: 92%; height: 92%;
  background: url("/ts/plus.webp?v=3") center 0 no-repeat; background-size: 100%;
  background-color: #f6dfbd;   /* 兜底：加号图挂掉时圆片还在（不至于连「加提醒」都点不到） */
  border-radius: 50%;
}
.fl-circle > li:hover .fl-reminder.add { opacity: 1; }
@media (hover: none) { .fl-square .fl-reminder.add { opacity: 1; } }

/* -------- 中央剧本盘（TownInfo.vue .info） -------- */
.fl-info {
  position: absolute; display: flex; align-items: center; align-content: center;
  justify-content: center; flex-wrap: wrap; text-align: center;
  /* TownInfo.vue 是 padding: 50px 0 0；令牌 126px 时 50px ≈ .4 token */
  padding: calc(var(--fl-token, 60px) * .4) 0 0;
  /* 真站 TownInfo.vue 只有这张恶魔头底图、**没有**底色。
     我们自己加过一层 rgba(0,0,0,.45) 当「兜底底板」，结果环中央糊出一个方方正正的黑框
     （少爷 2026-09-21：这个黑框不好看，删掉）。
     字的清晰度靠 li 上的黑色描边（text-shadow），不需要底板。 */
  background: url("/ts/demon-head.webp?v=3") center center no-repeat; background-size: auto 100%;
  pointer-events: none;
}
.fl-info > ul { margin: 0; padding: 0; list-style: none; width: 100%; }
.fl-info li {
  font-weight: bold; width: 100%; display: flex; flex-wrap: wrap; justify-content: center;
  filter: drop-shadow(0 0 2px rgba(0, 0, 0, .7));
  text-shadow: 0 2px 1px #000, 0 -2px 1px #000, 2px 0 1px #000, -2px 0 1px #000;
  font-size: 90%; line-height: 1.3;
}
.fl-info li > span { white-space: nowrap; margin: 0 4px; }
.fl-info .fl-meta { flex-basis: 100%; font-family: PiratesBay, sans-serif; font-weight: normal;
  font-size: 110%; overflow: hidden; text-overflow: ellipsis; }
/* 中央剧本图（TownInfo.vue li.edition：220×200、背景 100% auto、top:-25%）
   —— 真站就是这样：json 里有 _meta.logo 就显示它，没有就显示 editions/custom.png */
.fl-info .fl-edition {
  position: absolute; top: -25%; left: 50%; transform: translateX(-50%);
  width: 69.6%; height: 100%;
  background-position: 0 center; background-repeat: no-repeat; background-size: 100% auto;
}
.fl-info .t-townsfolk { color: var(--ts-townsfolk); }
.fl-info .t-outsider { color: var(--ts-outsider); }
.fl-info .t-minion { color: var(--ts-minion); }
.fl-info .t-demon { color: var(--ts-demon); }
.fl-info .t-traveler { color: var(--ts-traveler); }
.fl-info .t-alive { color: #ff4a50; }
.fl-info .t-player { color: #00f700; }
.fl-info .t-vote { color: #ffd24a; }

/* -------- 恶魔的伪装身份 / 传奇角色（TownSquare .bluffs / .fabled） -------- */
.fl-bluffs, .fl-fabled {
  position: absolute; left: 8px; background: rgba(0, 0, 0, .5); border-radius: 10px;
  border: 3px solid #000; filter: drop-shadow(0 4px 6px rgba(0, 0, 0, .5));
  z-index: 50; padding: 2px 6px 6px;
}
.fl-bluffs { bottom: 8px; }
.fl-fabled { top: 8px; }
.fl-bluffs h3, .fl-fabled h3 {
  margin: 3px 0 2px; font-size: 90%; display: flex; align-items: center; justify-content: center;
  font-family: PiratesBay, sans-serif; font-weight: normal;
}
.fl-bluffs ul, .fl-fabled ul { display: flex; align-items: center; justify-content: center; gap: 4px; margin: 0; padding: 0; list-style: none; }
.fl-mini { width: min(58px, calc(var(--fl-token, 56px) * .62)); height: min(58px, calc(var(--fl-token, 56px) * .62));
  border-radius: 50%; background: url("/ts/token.webp?v=3") center center; background-size: 100%;
  background-color: #e3d0ad;   /* 兜底 */
  border: 3px solid #000; box-shadow: 0 0 8px rgba(0, 0, 0, .5); position: relative; cursor: pointer; }
.fl-mini > i {
  position: absolute; inset: 0; margin-top: 3%; background-repeat: no-repeat; background-position: center 30%;
  background-size: 100%; border-radius: 50%;
}
.fl-mini > b {
  position: absolute; left: 50%; bottom: -2px; transform: translateX(-50%);
  font-size: 62%; color: #000; white-space: nowrap; text-shadow: 0 1px 1px #fff, 0 -1px 1px #fff, 1px 0 1px #fff, -1px 0 1px #fff;
  font-family: Papyrus, "STKaiti", serif;
}
/* 小屏：左下角的「恶魔的伪装身份」缩小一点，别压住座位 */
@media (max-width: 600px) {
  .fl-bluffs, .fl-fabled { transform: scale(.82); transform-origin: bottom left; left: 2px; bottom: 2px; }
  .fl-fabled { transform-origin: top left; bottom: auto; top: 2px; }
  .fl-bluffs h3, .fl-fabled h3 { font-size: 86%; }
}

/* 房主端圆环的浮动控制条（对照 townsquare 的浮动工具条） */
/* 图片自检失败时的提示条（见 app.js 里的图片自检） */
.img-warn {
  position: fixed; left: 8px; right: 8px; bottom: 8px; z-index: 999;
  background: rgba(130, 22, 22, .94); border: 3px solid #000; border-radius: 10px;
  padding: 10px 12px; font-size: 13px; line-height: 1.5; color: #fff;
  box-shadow: 0 0 12px rgba(0, 0, 0, .7);
}

.grim-wrap { position: relative; }

/* 夜间行动：在魔典上点人 —— 选中的座位套一圈红光（少爷要求「红色高亮」，原来金光太不明显） */
.night-ring { min-height: 44vh; }
/* 选号时只关心「点谁」——⊕ 加提醒小圆片全是干扰（还都挤在圆心那一圈），藏掉 */
.night-ring .fl-reminder.add { display: none; }
/* 选择器挂在 li 上（见 app.js markPicked 的注释），比 .fl-player.me .fl-token 更具体，
   免得「自己」那个座位的蓝框把红光盖掉 */
.fl-square .fl-circle > li.fl-picked .fl-token {
  border-color: #ff2a1e;
  animation: flPickPulse 1.3s ease-in-out infinite;
}
@keyframes flPickPulse {
  0%, 100% { box-shadow: 0 0 0 4px rgba(255, 42, 30, .55), 0 0 18px rgba(255, 42, 30, .95); }
  50% { box-shadow: 0 0 0 8px rgba(255, 42, 30, .18), 0 0 26px rgba(255, 42, 30, 1); }
}
.fl-square .fl-circle > li.fl-picked .fl-playername { background: rgba(190, 24, 18, .92); color: #fff; border-color: #000; }
/* 被选中座位的座位号也点个红，视口里一眼能扫到 */
.fl-square .fl-circle > li.fl-picked .fl-seatNum { color: #ff6a5e; }

/* ---- 夜间「轮到你」的三段式界面：提示 → 选号 → 二次确认 ---- */
.night-step-tip {
  display: flex; align-items: center; justify-content: center; gap: 8px;
  font-size: 13px; color: #ffe6a8; margin: 10px 0 4px;
}
.night-step-tip i { font-style: normal; font-size: 15px; }
.night-step-tip b { color: #ffd24a; }
.night-bigbtn { font-size: 17px !important; padding: 15px 18px !important; letter-spacing: 2px; }
/* 「看魔典选人」开关 */
.grim-toggle {
  display: block; width: 100%; text-align: center; box-sizing: border-box;
  padding: 12px 14px; margin: 10px 0 2px; border-radius: 12px; font-size: 15px;
  background: linear-gradient(#3a3f4d, #14161c) content-box, #000 border-box;
  border: solid 3px transparent; color: #fff; cursor: pointer;
}
.grim-toggle.on { background: radial-gradient(at 0 -15%, rgba(255,255,255,.07) 70%, rgba(255,255,255,0) 71%) 0 0/80% 90% no-repeat content-box, linear-gradient(#8d2a20, rgba(5,0,0,.22)) content-box, linear-gradient(#292929, #4a1208) border-box; }
/* 已选目标的小标签 */
.pick-chips { display: flex; flex-wrap: wrap; gap: 8px; justify-content: center; margin: 10px 0 4px; }
.pick-chip {
  background: rgba(190, 24, 18, .9); border: 2px solid #000; border-radius: 10px;
  padding: 7px 12px; font-size: 14px; color: #fff; cursor: pointer;
}
.pick-chip i { font-style: normal; opacity: .75; margin-left: 6px; }
.confirm-box {
  background: rgba(14, 18, 26, .82); border: 3px solid #a8100a; border-radius: 12px;
  padding: 14px 14px 12px; margin: 12px 0 6px;
}
.confirm-box h4 { margin: 0 0 8px; font-size: 15px; color: #ffb3ac; letter-spacing: 1px; }
.confirm-box .line { font-size: 17px; line-height: 1.9; color: #fff; }
.confirm-box .line b { color: #ffd24a; }
/* 全屏魔典（⛶）：铺满视口，圆环按整屏高度算 —— 窗口矮的时候能大一圈 */
.grim-wrap.fl-full {
  position: fixed; inset: 0; z-index: 300; margin: 0; padding: 0; overflow: hidden;
  background: #07080c url("/ts/background.jpg?v=3") center center / cover no-repeat;
}
.grim-wrap.fl-full .fl-square { height: 100vh; min-height: 0; }
.grim-wrap.fl-full .ts-float { top: 8px; right: 10px; }
.ts-float { position: absolute; top: 6px; right: 8px; z-index: 80; display: flex; gap: 6px; align-items: center; }
.ts-float button { padding: 7px 11px; border-radius: 12px; font-size: 13px; }
.ts-float button.on { background: radial-gradient(at 0 -15%, rgba(255,255,255,.07) 70%, rgba(255,255,255,0) 71%) 0 0/80% 90% no-repeat content-box, linear-gradient(#ad5c00, rgba(5,0,0,.22)) content-box, linear-gradient(#292929, #421d00) border-box; }

/* 底部说明条（顶栏按钮用，保留我们自己的工具栏样式） */
.ts-top { position: relative; z-index: 70; }
.ts-top .ts-tab { background: linear-gradient(#4e4e4e, #040404) content-box, linear-gradient(#292929, #010101) border-box; border: solid 3px transparent; color: #fff; }
.ts-top .ts-tab.on { background: radial-gradient(at 0 -15%, rgba(255,255,255,.07) 70%, rgba(255,255,255,0) 71%) 0 0/80% 90% no-repeat content-box, linear-gradient(#ad5c00, rgba(5,0,0,.22)) content-box, linear-gradient(#292929, #421d00) border-box; }

/* 大屏 */
.screen-wrap { max-width: 1200px; }
.ring { display: block; }
/**
 * ★ 少爷 2026-09-22：「环形魔典组成的圆可以再大一点，再往外扩扩」。
 *   手机上实测：#app 有 14px padding，中间还夹着一层卡片的 padding —— 两层一叠，
 *   393 宽的视口里圆环只剩 339，**宽度每少 1px，环就小 1px**。
 *
 *   第一版写死 `margin-left:-30px` 是猜的数：中间那层有多少 padding 全靠猜，
 *   猜少了不够、猜多了溢出。现在改成**直接吃满视口宽度**，跟祖先有几层 padding 无关：
 *     width:100vw + margin-left:calc(50% - 50vw) 是「逃出父容器」的标准写法 ——
 *     把自己左移到视口左边缘，宽度正好一屏。
 *   横溢由 html/body 的 overflow-x:hidden 兜住（本来就开着），不会出现横向滚动条。
 *   只在窄屏生效：宽屏（大屏投屏）本来就不缺这几十像素，没必要让圆环比其它元素宽出去。
 */
@media (max-width: 560px) {
  .ring {
    width: 100vw; max-width: 100vw;
    margin-left: calc(50% - 50vw); margin-right: calc(50% - 50vw);
  }
}

/* ==========================================================================
   以下是「点击令牌 / 点击名字」的弹层 —— 逐条对照
   modals/Modal.vue + modals/RoleModal.vue + modals/ReminderModal.vue
   + Player.vue 的 .player > .menu
   ========================================================================== */

/* -------- 名字右键菜单（Player.vue .player > .menu） -------- */
.fl-square .fl-player > .fl-menu {
  position: absolute; left: 110%; bottom: -5px; text-align: left; white-space: nowrap;
  background: rgba(0, 0, 0, .5); padding: 2px 5px; border-radius: 10px; border: 3px solid #000;
  margin: 0 0 0 15px; cursor: pointer; box-shadow: 0 0 5px rgba(0, 0, 0, .5);
  list-style: none; z-index: 90; font-size: 90%; line-height: 1.6;
}
.fl-square .fl-player > .fl-menu::before {
  content: " "; width: 0; height: 0; position: absolute; border: 10px solid transparent;
  border-right-color: #000; right: 100%; bottom: 5px; margin-right: 2px;
}
.fl-square .fl-player > .fl-menu li:hover { color: red; }
.fl-square .fl-player > .fl-menu li.disabled { cursor: not-allowed; opacity: .5; }
.fl-square .fl-player > .fl-menu li.disabled:hover { color: #fff; }
/* 靠近屏幕边缘时翻到另一侧 / 翻到上方 */
.fl-square .fl-player > .fl-menu.flip { left: auto; right: 110%; margin: 0 15px 0 0; }
.fl-square .fl-player > .fl-menu.flip::before {
  right: auto; left: 100%; border-right-color: transparent; border-left-color: #000;
  margin: 0 0 0 2px;
}
.fl-square .fl-player > .fl-menu.up { top: -5px; bottom: auto; }
.fl-square .fl-player > .fl-menu.up::before { bottom: auto; top: 5px; }

/* -------- 弹窗外壳（Modal.vue） -------- */
.modal-backdrop {
  position: fixed; top: 0; bottom: 0; left: 0; right: 0; background-color: rgba(0, 0, 0, .3);
  display: flex; justify-content: center; align-items: center; z-index: 200;
}
.modal {
  position: relative; background: rgba(0, 0, 0, .8); padding: 10px 20px; border-radius: 10px;
  box-shadow: 2px 2px 20px 1px #000; display: flex; flex-direction: column;
  max-height: 95%; max-width: 95%; overflow-y: auto; overflow-x: hidden; color: #fff;
}
.modal > .top-right-buttons { position: absolute; z-index: 100; top: 15px; right: 20px; }
.modal > .top-right-buttons .top-right-button {
  cursor: pointer; width: 28px; background: none; border: 0; box-shadow: none;
  color: #fff; font-size: 22px; line-height: 1; padding: 0;
}
.modal > .top-right-buttons .top-right-button:hover { color: red; }
.modal h3 {
  font-family: PiratesBay, sans-serif; font-weight: normal; letter-spacing: 1px;
  margin: 4px 40px 12px; text-align: center; font-size: 130%;
}
.modal ul { list-style: none; margin: 0; padding: 0; display: flex; flex-wrap: wrap;
  align-content: center; align-items: center; justify-content: center; line-height: 100%; max-width: 95%; }
.modal .tips { text-align: center; font-size: 88%; color: #d6dbe4; margin: 0 0 10px; }

/* 弹窗里的按钮（App.vue .button / .button-group） */
.modal .button, .fl-menu-button {
  padding: 0 10px; border: solid 3px transparent; border-radius: 15px;
  box-shadow: inset 0 1px 1px #9c9c9c, 0 0 10px #000;
  background: radial-gradient(at 0 -15%, rgba(255, 255, 255, .07) 70%, rgba(255, 255, 255, 0) 71%) 0 0/80% 90% no-repeat content-box,
    linear-gradient(#4e4e4e, #040404) content-box, linear-gradient(#292929, #010101) border-box;
  color: #fff; font-weight: bold; text-shadow: 1px 1px rgba(0, 0, 0, .5); line-height: 170%;
  margin: 5px auto; cursor: pointer; transition: all 200ms; white-space: nowrap; font-size: 100%;
}
.modal .button:hover { color: red; }
.modal .button-group { display: flex; align-items: center; justify-content: center; align-content: center; margin-top: 8px; }
.modal .button-group .button { margin: 5px 0; border-radius: 0; }
.modal .button-group .button:first-child { border-top-left-radius: 15px; border-bottom-left-radius: 15px; }
.modal .button-group .button:last-child { border-top-right-radius: 15px; border-bottom-right-radius: 15px; }
.modal .button.on { background: radial-gradient(at 0 -15%, rgba(255, 255, 255, .07) 70%, rgba(255, 255, 255, 0) 71%) 0 0/80% 90% no-repeat content-box, linear-gradient(#0031ad, rgba(5, 0, 0, .22)) content-box, linear-gradient(#292929, #001142) border-box; }
.modal .button.danger { background: radial-gradient(at 0 -15%, rgba(255, 255, 255, .07) 70%, rgba(255, 255, 255, 0) 71%) 0 0/80% 90% no-repeat content-box, linear-gradient(#ad0000, rgba(5, 0, 0, .22)) content-box, linear-gradient(#292929, #420000) border-box; }

/* 「自己写一个」那一行：输入框 + 「放上这枚」并排。
   ⚠ input 在 style.css 里是 width:100%，不压回去会把按钮挤到下一行（手机上更明显）。 */
.modal .row input.input { flex: 1 1 auto; width: auto; min-width: 0; }
.modal .row .button { flex: 0 0 auto; margin: 0; line-height: 150%; padding: 10px 14px; }

/* 「我的历史信息」弹层：一条一行（⚠ 不能用 <ul>，.modal ul 是横向 wrap 的布局）。 */
.modal .ihist { display: block; width: 100%; max-height: 46vh; overflow-y: auto; text-align: left; }
.modal .ihist .ih { display: block; padding: 7px 4px; border-bottom: 1px solid rgba(255, 255, 255, .10); line-height: 150%; }
.modal .ihist .ih .ih-when { display: inline-block; min-width: 58px; color: #f0c96b; font-weight: bold; font-size: 88%; }
.modal .ihist .ih .ih-txt { color: #eaeeF6; }
.modal .ihist .ih .ih-lbl { display: block; padding-left: 58px; color: #9aa4b2; font-size: 82%; }

/* -------- 「速查」弹层（第三十三轮 2026-09-23）--------
   玩家端顶栏「速查」→ 技能表 / 夜间顺序 / 官方速查图 三栏。
   ⚠ 全部挂在 `.modal` 下：一是不出 .modal 就没有弹窗基础样式，
   二是不会误伤环上的同名元素（`.qtag` 之类在外面没有用到，但保持同样的纪律）。 */
.modal.qref-modal { width: min(94vw, 720px); }
.modal .qref-tabs { flex-wrap: wrap; margin-bottom: 10px; }
.modal .qref-tabs .button { margin: 3px 2px; }
.modal .qref-zoom { flex-wrap: wrap; margin: 0 0 8px; }
.modal .qref-zoom .button { margin: 3px 2px; }

/* 技能表：按阵营分组，一条一块，自己的那条高亮 */
.modal .qref { display: block; width: 100%; max-height: 56vh; overflow-y: auto; text-align: left; }
.modal .qref .qteam {
  display: block; margin: 14px 0 4px; padding-bottom: 3px;
  border-bottom: 1px solid rgba(255, 255, 255, .22);
  color: #f0c96b; font-weight: bold; letter-spacing: 1px;
}
.modal .qref .qteam:first-child { margin-top: 4px; }
.modal .qref .qteam i { font-style: normal; margin-left: 6px; color: #9aa4b2; font-weight: normal; font-size: 82%; }
.modal .qref .qrow { display: block; padding: 8px 6px; border-bottom: 1px solid rgba(255, 255, 255, .08); }
.modal .qref .qrow.me { background: rgba(240, 201, 107, .10); box-shadow: inset 3px 0 0 #f0c96b; }
.modal .qref .qh { display: block; }
.modal .qref .qh b { font-size: 105%; letter-spacing: .5px; }
.modal .qref .qme {
  margin-left: 6px; padding: 1px 6px; border-radius: 6px;
  background: #f0c96b; color: #241d05; font-size: 78%; font-weight: bold;
}
.modal .qref .qa { display: block; margin-top: 3px; color: #dbe2ee; font-size: 90%; line-height: 150%; }
.modal .qref .qwiki {
  display: inline-block; margin-top: 5px; color: #f0c96b; font-size: 82%; text-decoration: none;
  border-bottom: 1px dashed rgba(240, 201, 107, .5);
}
.modal .qtag {
  margin-left: 6px; padding: 0 5px; border: 1px solid #5a6273; border-radius: 5px;
  color: #aab4c4; font-size: 76%; font-weight: normal; white-space: nowrap;
}

/* 夜间顺序：左边序号、右边「名字 + 条件标签 + 技能一行小字」 */
.modal .qorder { display: block; width: 100%; max-height: 56vh; overflow-y: auto; text-align: left; }
.modal .qorder .qoh {
  display: block; margin: 14px 0 4px; padding-bottom: 3px;
  border-bottom: 1px solid rgba(255, 255, 255, .22);
  color: #f0c96b; font-weight: bold; letter-spacing: 2px;
}
.modal .qorder .qoh:first-child { margin-top: 4px; }
.modal .qorder .qo {
  display: flex; align-items: flex-start; gap: 8px;
  padding: 7px 4px; border-bottom: 1px solid rgba(255, 255, 255, .08);
}
.modal .qorder .qo > i {
  flex: 0 0 26px; text-align: right; padding-top: 2px;
  color: #f0c96b; font-style: normal; font-size: 88%;
}
.modal .qorder .qob { flex: 1 1 auto; min-width: 0; }
.modal .qorder .qon { color: #f2f5fa; font-weight: bold; }
.modal .qorder .qoa { margin-top: 2px; color: #b6bfcd; font-size: 84%; line-height: 145%; }

/* 官方速查图：弹层里只是**缩略**（适应宽度），点一下进全屏灯箱细看 */
.modal .qzoom {
  width: 100%; max-height: 62vh; overflow: auto; background: #11141a;
  border: 1px solid rgba(255, 255, 255, .12); border-radius: 8px;
  -webkit-overflow-scrolling: touch;
}
.modal .qzoom img { display: block; width: 100%; max-width: none; cursor: zoom-in; }

/* -------- 图片灯箱：点图全屏，双指/双击/滚轮缩放，放大后拖动（第三十三轮下半） -------- */
.imgbox {
  /* ⚠ 必须盖过 `.modal-backdrop` 的 z-index:200 —— 灯箱是**从弹层里点开的**，
     给 90 会被弹层压住看不见（`.modal > .top-right-buttons` 甚至给到 100）。 */
  position: fixed; inset: 0; z-index: 500; background: #000;
  display: flex; align-items: center; justify-content: center;
  overflow: hidden; touch-action: none;   /* 手势全自己吃，别让浏览器拿去滚页面 */
}
.imgbox img {
  max-width: 100vw; max-height: 100vh; width: auto; height: auto;
  transform-origin: center center; will-change: transform;
  user-select: none; -webkit-user-drag: none; touch-action: none;
}
.imgbox .imgbox-x {
  position: absolute; top: 12px; right: 12px; width: 40px; height: 40px;
  border: none; border-radius: 50%; background: rgba(255, 255, 255, .16);
  color: #fff; font-size: 20px; line-height: 40px; cursor: pointer;
  -webkit-tap-highlight-color: transparent;
}
.imgbox .imgbox-x:active { background: rgba(255, 255, 255, .32); }

/* -------- 角色选择弹窗（RoleModal.vue ul.tokens li） -------- */
.modal.roles ul.tokens li { border-radius: 50%; margin: 1%; transition: transform 500ms ease; position: relative; cursor: pointer; }
.modal.roles ul.tokens li.townsfolk { box-shadow: 0 0 10px var(--ts-townsfolk), 0 0 10px #004cff; }
.modal.roles ul.tokens li.outsider { box-shadow: 0 0 10px var(--ts-outsider), 0 0 10px var(--ts-outsider); }
.modal.roles ul.tokens li.minion { box-shadow: 0 0 10px var(--ts-minion), 0 0 10px var(--ts-minion); }
.modal.roles ul.tokens li.demon { box-shadow: 0 0 10px var(--ts-demon), 0 0 10px var(--ts-demon); }
.modal.roles ul.tokens li.traveler { box-shadow: 0 0 10px var(--ts-traveler), 0 0 10px var(--ts-traveler); }
.modal.roles ul.tokens li:hover { transform: scale(1.2); z-index: 10; }
.modal.roles ul.tokens li.on .fl-token { border-color: #ffd24a; box-shadow: 0 0 14px #ffd24a; }
.modal.roles ul.tokens li.none .fl-token::after {
  content: "无"; position: absolute; inset: 0; display: flex; align-items: center; justify-content: center;
  font-size: 120%; font-weight: bold; color: #333; font-family: Papyrus, serif;
}

/* -------- 提醒标记弹窗（逐条照抄 townsquare 的 ReminderModal.vue） --------
   官方原文（src/components/modals/ReminderModal.vue <style>）：
     ul.reminders .reminder { background: url(reminder.png) center center; background-size:100%;
       width:14vh; height:14vh; max-width:100px; max-height:100px;
       display:flex; justify-content:center; align-items:center; margin:1%;
       border-radius:50%; border:3px solid black; box-shadow:0 0 10px rgba(0,0,0,.5);
       cursor:pointer; line-height:100%; transition:transform 500ms ease; }
     .icon { position:absolute; top:0; width:90%; height:90%; background-size:100%;
             background-position:center center; background-repeat:no-repeat; }
     .text { color:black; font-size:65%; font-weight:bold; text-align:center;
             top:28%; width:80%; line-height:1; }
     &:hover { transform: scale(1.2); }
   → 与官方的差异只有两处，都是「官方靠祖先偶然成立、我们这边必须显式写」的：
     ① 给 li 补 `position: relative`（官方的 .icon 是 absolute 但没有定位基准，
        在它那套 DOM 里恰好落在祖先上；我们这边不补就会跑到 UI 别处去）；
     ② .text 官方写了 top:28% 却没写 position（对 static 元素无效），
        我们补成 absolute 让这个 28% 真正生效 —— 否则文字会居中压在角色图上。
*/
.modal ul.reminders li.fl-rem {
  background: url("/ts/reminder.webp?v=3") center center; background-size: 100%;
  background-color: #f6dfbd;   /* 兜底：圆片图挂掉也别变成黑洞 */
  width: 14vh; height: 14vh; max-width: 100px; max-height: 100px;
  display: flex; justify-content: center; align-items: center; margin: 1%;
  border-radius: 50%; border: 3px solid #000; box-shadow: 0 0 10px rgba(0, 0, 0, .5);
  cursor: pointer; line-height: 100%; transition: transform 500ms ease; position: relative;
}
.modal ul.reminders li.fl-rem:hover { transform: scale(1.2); }
/* 角色图标：官方是 90% 见方、盖在整张圆片上（不是只占上半部） */
.modal ul.reminders li.fl-rem .icon {
  position: absolute; top: 0; left: 5%; width: 90%; height: 90%;
  background-repeat: no-repeat; background-position: center center; background-size: 100%;
  pointer-events: none;
}
/* 文字：官方 65% 字号、居中、`top:28%` 压在圆片上部 */
.modal ul.reminders li.fl-rem .text {
  position: absolute; top: 28%; left: 10%; width: 80%;
  color: #000; font-size: 65%; font-weight: bold; text-align: center; line-height: 1;
}
/* ★ 同上：只写字、没有角色小图的圆片（自己写的标记 / 预览），文字垂直居中，
   不再沿用官方那个「给上半部的图让位」的 top:28%。 */
.modal ul.reminders li.fl-rem.txtonly .text { top: 50%; transform: translateY(-50%); }
.modal ul.reminders li.fl-rem.on { border-color: #ffd24a; box-shadow: 0 0 14px #ffd24a; }
/* 玩家自己写的标记：虚线圈 + 冷色底，一眼和官方固定标记区分开 */
.modal ul.reminders li.fl-rem.custom {
  background-color: #cfd6e2; border-style: dashed; border-color: #8892a6;
}
.modal ul.reminders li.fl-rem.custom.on { border-color: #7fb6ea; box-shadow: 0 0 14px #7fb6ea; }

/* ★ 2026-09-24 少爷：「自定义的提醒标记…我希望能**直接输完字就显示**，而不是『加上去』。」
   输入框下面这一条就是「马上挂上」的实时预览：打的字一出现，圆片就已经画好了，
   停 700ms（或回车/失焦）它自己落定 —— 全程不需要点任何按钮。 */
.modal ul.reminders#remPreview { margin-top: 10px; }
.modal ul.reminders#remPreview:empty { display: none; }
.modal ul.reminders#remPreview li.slabel {
  grid-column: 1 / -1; width: 100%; height: auto; max-width: none; max-height: none;
  margin: 0 0 4px; padding: 0; list-style: none; background: none; border: 0; box-shadow: none;
  cursor: default; color: #8892a6; font-size: 13px; text-align: left;
}
.modal ul.reminders#remPreview li.fl-rem.prev {
  border-color: #ffd24a;
  box-shadow: 0 0 0 2px rgba(255, 210, 74, .55), 0 0 18px rgba(255, 210, 74, .5);
  animation: remPrevPop 200ms ease-out;
}
@keyframes remPrevPop { from { transform: scale(.72); opacity: 0; } to { transform: scale(1); opacity: 1; } }

/* -------- 弹窗里的令牌（和圆环同一个 Token 外观） -------- */
.modal .fl-token {
  position: relative; width: 100%; border-radius: 50%;
  background: url("/ts/token.webp?v=3") center center; background-size: 100%;
  background-color: #e3d0ad;   /* 兜底：令牌图挂掉时仍是个米白圆盘 */
  border: 3px solid #000; box-shadow: 0 0 10px rgba(0, 0, 0, .5);
  display: flex; align-items: center; justify-content: center; overflow: visible;
}
.modal .fl-token::before { content: " "; display: block; padding-top: 100%; }
.modal .fl-token > span {
  position: absolute; width: 100%; height: 100%; background-size: 100%;
  pointer-events: none; border-radius: 50%;
}
.modal .fl-token > span.fl-icon { margin-top: 3%; background-repeat: no-repeat; background-position: center 30%; }
.modal .fl-token > span.fl-leaf-left { background-image: url("/ts/leaf-left.webp?v=3"); }
.modal .fl-token > span.fl-leaf-right { background-image: url("/ts/leaf-right.webp?v=3"); }
.modal .fl-token > span.fl-leaf-orange { background-image: url("/ts/leaf-orange.webp?v=3"); }
.modal .fl-token > span.fl-leaf-top1 { background-image: url("/ts/leaf-top1.webp?v=3"); }
.modal .fl-token > span.fl-leaf-top2 { background-image: url("/ts/leaf-top2.webp?v=3"); }
.modal .fl-token > span.fl-leaf-top3 { background-image: url("/ts/leaf-top3.webp?v=3"); }
.modal .fl-token > span.fl-leaf-top4 { background-image: url("/ts/leaf-top4.webp?v=3"); }
.modal .fl-token > span.fl-leaf-top5 { background-image: url("/ts/leaf-top5.webp?v=3"); }
.modal .fl-token .fl-namesvg { position: absolute; left: 0; top: 0; width: 100%; height: 100%; font-size: 24px; pointer-events: none; }
.modal .fl-token .fl-namesvg text {
  fill: #000; stroke: #fff; stroke-width: 2px; paint-order: stroke;
  font-family: Papyrus, "STKaiti", "KaiTi", "楷体", serif;
  font-weight: bold; letter-spacing: 1px;
}
.modal .fl-token:hover .fl-namesvg text { stroke: #000; fill: #fff; }

/* -------- 夜间行动里的角色令牌格（.role-tiles）--------
   不能在弹层外复用 `.modal .fl-token` 那套（出了 .modal 一律失效），所以这里自成一套。
   横向换行排列 + 圆形令牌图，和「点头像选角色」的网格观感一致。 */
.role-tiles {
  display: flex; flex-wrap: wrap; justify-content: center; align-items: flex-start;
  gap: 12px 10px; margin: 8px 0 6px; padding: 0; list-style: none;
}
.role-tiles > li {
  position: relative; width: min(19vw, 84px); cursor: pointer;
  border-radius: 50%; transition: transform 300ms ease;
}
.role-tiles > li:hover { transform: scale(1.12); z-index: 10; }
.role-tiles > li.townsfolk .fl-token { box-shadow: 0 0 10px var(--ts-townsfolk); }
.role-tiles > li.outsider .fl-token { box-shadow: 0 0 10px var(--ts-outsider); }
.role-tiles > li.minion .fl-token { box-shadow: 0 0 10px var(--ts-minion); }
.role-tiles > li.demon .fl-token { box-shadow: 0 0 10px var(--ts-demon); }
.role-tiles > li.on .fl-token { border-color: #ffd24a; box-shadow: 0 0 16px #ffd24a; }
.role-tiles .fl-token {
  position: relative; width: 100%; border-radius: 50%;
  background: url("/ts/token.webp?v=3") center center; background-size: 100%;
  background-color: #e3d0ad;
  border: 3px solid #000; box-shadow: 0 0 10px rgba(0, 0, 0, .5);
  display: flex; align-items: center; justify-content: center; overflow: visible;
}
.role-tiles .fl-token::before { content: " "; display: block; padding-top: 100%; }
.role-tiles .fl-token > span {
  position: absolute; width: 100%; height: 100%; background-size: 100%;
  pointer-events: none; border-radius: 50%;
}
.role-tiles .fl-token > span.fl-icon { margin-top: 3%; background-repeat: no-repeat; background-position: center 30%; }
.role-tiles .fl-token > span.fl-leaf-left { background-image: url("/ts/leaf-left.webp?v=3"); }
.role-tiles .fl-token > span.fl-leaf-right { background-image: url("/ts/leaf-right.webp?v=3"); }
.role-tiles .fl-token > span.fl-leaf-orange { background-image: url("/ts/leaf-orange.webp?v=3"); }
.role-tiles .fl-token > span.fl-leaf-top1 { background-image: url("/ts/leaf-top1.webp?v=3"); }
.role-tiles .fl-token > span.fl-leaf-top2 { background-image: url("/ts/leaf-top2.webp?v=3"); }
.role-tiles .fl-token > span.fl-leaf-top3 { background-image: url("/ts/leaf-top3.webp?v=3"); }
.role-tiles .fl-token > span.fl-leaf-top4 { background-image: url("/ts/leaf-top4.webp?v=3"); }
.role-tiles .fl-token > span.fl-leaf-top5 { background-image: url("/ts/leaf-top5.webp?v=3"); }
.role-tiles .fl-token .fl-namesvg {
  position: absolute; left: 0; top: 0; width: 100%; height: 100%;
  font-size: 24px; pointer-events: none;
}
.role-tiles .fl-token .fl-namesvg text {
  fill: #000; stroke: #fff; stroke-width: 2px; paint-order: stroke;
  font-family: Papyrus, "STKaiti", "KaiTi", "楷体", serif;
  font-weight: bold; letter-spacing: 1px;
}
/* 令牌下面的小名字：图挂掉时也能认出是哪个角色（手机上曾经整片图片被拦过） */
.role-tiles > li .rn {
  display: block; margin-top: 4px; text-align: center; font-size: 12px; font-weight: 600;
  color: #e8e2d4; text-shadow: 0 1px 2px #000; line-height: 1.2;
}

/* ==================== 大屏通知（说书人播报） ====================
   死亡 / 身份变化时铺一层半透明黑，中央弹出大字横幅。
   少爷原话：「我 11 号提名之后死亡，死亡不太明显啊，我都不知道自己死了」。 */
.fl-notice {
  position: fixed; inset: 0; z-index: 400; display: none;
  align-items: center; justify-content: center; cursor: pointer;
  background: rgba(0, 0, 0, .62); backdrop-filter: blur(2px);
}
.fl-notice.on { display: flex; animation: flNoticeIn 220ms ease-out; }
@keyframes flNoticeIn { from { opacity: 0; } to { opacity: 1; } }
.fl-notice .nt-wrap { text-align: center; padding: 0 18px; max-width: 92vw; }
/* 提名横幅往上挪，把环中央让给「提名者 → 被提名者」那根箭头 */
.fl-notice .nt-wrap.nom-mode { align-self: flex-start; padding-top: 7vh; }
.fl-notice .nt-kicker {
  font-size: 14px; letter-spacing: 8px; color: #b9b0a0; margin-bottom: 10px;
}
.fl-notice .nt {
  font-size: clamp(20px, 5.6vw, 40px); font-weight: 800; line-height: 1.5;
  color: #fff; text-shadow: 0 3px 10px #000; margin: 6px 0;
  animation: flNoticeRow 320ms ease-out both;
}
@keyframes flNoticeRow { from { transform: translateY(14px) scale(.94); opacity: 0; } to { transform: none; opacity: 1; } }
.fl-notice .nt.death { color: #ff6a5e; }
.fl-notice .nt.change { color: #ffc45a; }
.fl-notice .nt.over { color: #ffd24a; }
.fl-notice .nt.quiet { color: #a9b6c8; font-size: clamp(16px, 4.2vw, 26px); }
.fl-notice .nt.act { color: #dbe6f5; font-size: clamp(16px, 4.4vw, 28px); }
.fl-notice .nt.guess { color: #8fd0ff; font-size: clamp(15px, 3.6vw, 22px); }
.fl-notice .nt.nom {
  color: #ffe9a8; font-size: clamp(24px, 6.4vw, 46px);
  text-shadow: 0 0 18px rgba(255, 210, 74, .55), 0 3px 10px #000;
}
.fl-notice .nt-tip { margin-top: 14px; font-size: 13px; color: #8e8878; }

/* 提名时给「提名者 / 被提名者」两个令牌描边高亮（配合 nomSvg 的箭头） */
.fl-player.nom-by .fl-token,
.fl-player.nom-by .fl-face { box-shadow: 0 0 0 4px #7fb6ea, 0 0 22px rgba(127, 182, 234, .8) !important; }
.fl-player.nom-on .fl-token,
.fl-player.nom-on .fl-face { box-shadow: 0 0 0 4px #d0453c, 0 0 22px rgba(208, 69, 60, .85) !important; }
#nomSvg { animation: nomSvgIn 200ms ease-out; z-index: 401; }
@keyframes nomSvgIn { from { opacity: 0; } to { opacity: 1; } }

/* ---- 不认 webp 的老浏览器：把上面那些单层 webp 换回 png（同样只一层）----
   class 由 index.html 开头那段同步 canvas 探测打上（toDataURL 判格式）。
   只在真不支持时才命中，主流手机一条都不会走。 */
html.no-webp #tsBackdrop::after { background-image: url("/ts/clouds.png?v=3"); }
html.no-webp .fl-square .fl-player .fl-shroud::before { background-image: url("/ts/shroud.png?v=3"); }
html.no-webp .fl-square .fl-token { background-image: url("/ts/token.png?v=3"); }
html.no-webp .fl-square .fl-token.fl-life { background-image: url("/ts/life.png?v=3"); }
html.no-webp .fl-square .fl-token.fl-death { background-image: url("/ts/death.png?v=3"); }
html.no-webp .fl-square .fl-token.fl-unknown { background-image: url("/ts/token.png?v=3"); }
html.no-webp .fl-square .fl-token > span.fl-leaf-left { background-image: url("/ts/leaf-left.png?v=3"); }
html.no-webp .fl-square .fl-token > span.fl-leaf-right { background-image: url("/ts/leaf-right.png?v=3"); }
html.no-webp .fl-square .fl-token > span.fl-leaf-orange { background-image: url("/ts/leaf-orange.png?v=3"); }
html.no-webp .fl-square .fl-token > span.fl-leaf-top1 { background-image: url("/ts/leaf-top1.png?v=3"); }
html.no-webp .fl-square .fl-token > span.fl-leaf-top2 { background-image: url("/ts/leaf-top2.png?v=3"); }
html.no-webp .fl-square .fl-token > span.fl-leaf-top3 { background-image: url("/ts/leaf-top3.png?v=3"); }
html.no-webp .fl-square .fl-token > span.fl-leaf-top4 { background-image: url("/ts/leaf-top4.png?v=3"); }
html.no-webp .fl-square .fl-token > span.fl-leaf-top5 { background-image: url("/ts/leaf-top5.png?v=3"); }
html.no-webp .fl-square .fl-reminder { background-image: url("/ts/reminder.png?v=3"); }
html.no-webp .fl-square .fl-reminder.add .fl-pi { background-image: url("/ts/plus.png?v=3"); }
html.no-webp .fl-info { background-image: url("/ts/demon-head.png?v=3"); }
html.no-webp .modal ul.reminders li.fl-rem { background-image: url("/ts/reminder.png?v=3"); }
html.no-webp .modal .fl-token { background-image: url("/ts/token.png?v=3"); }
html.no-webp .modal .fl-token > span.fl-leaf-left { background-image: url("/ts/leaf-left.png?v=3"); }
html.no-webp .modal .fl-token > span.fl-leaf-right { background-image: url("/ts/leaf-right.png?v=3"); }
html.no-webp .modal .fl-token > span.fl-leaf-orange { background-image: url("/ts/leaf-orange.png?v=3"); }
html.no-webp .modal .fl-token > span.fl-leaf-top1 { background-image: url("/ts/leaf-top1.png?v=3"); }
html.no-webp .modal .fl-token > span.fl-leaf-top2 { background-image: url("/ts/leaf-top2.png?v=3"); }
html.no-webp .modal .fl-token > span.fl-leaf-top3 { background-image: url("/ts/leaf-top3.png?v=3"); }
html.no-webp .modal .fl-token > span.fl-leaf-top4 { background-image: url("/ts/leaf-top4.png?v=3"); }
html.no-webp .modal .fl-token > span.fl-leaf-top5 { background-image: url("/ts/leaf-top5.png?v=3"); }
html.no-webp .role-tiles .fl-token { background-image: url("/ts/token.png?v=3"); }
html.no-webp .role-tiles .fl-token > span.fl-leaf-left { background-image: url("/ts/leaf-left.png?v=3"); }
html.no-webp .role-tiles .fl-token > span.fl-leaf-right { background-image: url("/ts/leaf-right.png?v=3"); }
html.no-webp .role-tiles .fl-token > span.fl-leaf-orange { background-image: url("/ts/leaf-orange.png?v=3"); }
html.no-webp .role-tiles .fl-token > span.fl-leaf-top1 { background-image: url("/ts/leaf-top1.png?v=3"); }
html.no-webp .role-tiles .fl-token > span.fl-leaf-top2 { background-image: url("/ts/leaf-top2.png?v=3"); }
html.no-webp .role-tiles .fl-token > span.fl-leaf-top3 { background-image: url("/ts/leaf-top3.png?v=3"); }
html.no-webp .role-tiles .fl-token > span.fl-leaf-top4 { background-image: url("/ts/leaf-top4.png?v=3"); }
html.no-webp .role-tiles .fl-token > span.fl-leaf-top5 { background-image: url("/ts/leaf-top5.png?v=3"); }
