/* ===================================================================
   光屋谱库 · 共享样式(base.css)
   -------------------------------------------------------------------
   【从哪来】2026-10-20 从 web/index.html 的 <style> 块**原样**抽出来的。
   学员端(web/index.html)和老师端(web/teacher.html)共用这一份。

   ⚠ 改这个文件 = 同时改了两个页面的外观。
     学员端那边动完必须跑一遍验收(check_syntax / check_round7 / check_detail /
     check_driftwall),别只看老师端对不对。
   ⚠ 版本号 ?v= 要跟着往后挪 —— 浏览器会永久缓存同名文件,
     挪了版本号学员才会拿到新的。
   =================================================================== */

/* ===================================================================
   光屋谱库 · 学员端(桌面网页)
   视觉:暖灰白底 + 墨黑 + 低饱和粉彩,衬线标题,无渐变无阴影无 emoji
   适老:正文 >=17px,按钮 >=52px,可点区 >=44px
   =================================================================== */
:root{
  --bg:#F7F6F3; --card:#FFFFFF; --card2:#F1F0EC;
  --ink:#1A1A1A; --ink2:#5C5A55; --ink3:#8B8983;
  --line:#EAEAEA; --line2:#D8D6D0;
  --solid:#111111; --solid2:#2E2E2E;
  --r-red:#FDEBEC;  --r-red-t:#9F2F2D;
  --r-blue:#E1F3FE; --r-blue-t:#1F6C9F;
  --r-green:#EDF3EC;--r-green-t:#346538;
  --r-yellow:#FBF3DB;--r-yellow-t:#8A5D00;
  --r-gray:#EFEFEC; --r-gray-t:#5C5A55;
  --r:10px;
  --sans:"SF Pro Display",-apple-system,BlinkMacSystemFont,"Segoe UI",
        "PingFang SC","Microsoft YaHei","Hiragino Sans GB",sans-serif;
  --serif:"Lyon Text","Newsreader","Playfair Display","Songti SC",
          "STSong","Source Han Serif SC","Noto Serif SC","SimSun",serif;
  --mono:"SF Mono","JetBrains Mono","Consolas",monospace;
  --hd:72px;
}
*{box-sizing:border-box;margin:0;padding:0;-webkit-tap-highlight-color:transparent}
html{scroll-behavior:smooth}
body{
  background:var(--bg);color:var(--ink);
  font:17px/1.7 var(--sans);-webkit-font-smoothing:antialiased;
  min-height:100vh;
}
button,input{font-family:inherit;font-size:inherit;color:inherit;border:none;background:none;cursor:pointer}
input{-webkit-appearance:none}
svg{display:block;flex:0 0 auto}
.ic{width:20px;height:20px;stroke:currentColor;stroke-width:1.6;fill:none;
    stroke-linecap:round;stroke-linejoin:round}
.ic.sm{width:16px;height:16px;stroke-width:1.9}
.ic.xs{width:13px;height:13px;stroke-width:2.4}
.ic.lg{width:34px;height:34px;stroke-width:1.2}

/* 进场动效 —— 慢、低对比,40-70 岁看不晕 */
@keyframes rise{from{opacity:0;transform:translateY(14px)}to{opacity:1;transform:none}}
@keyframes fade{from{opacity:0}to{opacity:1}}
.rise{animation:rise .5s cubic-bezier(.16,1,.3,1) both}

/* ---------- 顶栏 ---------- */
.hd{
  position:sticky;top:0;z-index:60;background:rgba(247,246,243,.94);
  backdrop-filter:saturate(1.4) blur(14px);border-bottom:1px solid var(--line);
}
.hd-in{
  max-width:1440px;margin:0 auto;padding:0 32px;height:var(--hd);
  display:flex;align-items:center;gap:28px;
}
.brand{font-family:var(--serif);font-size:23px;font-weight:600;letter-spacing:-.02em;
  line-height:1.1;flex:0 0 auto}
.brand em{display:block;font-family:var(--mono);font-style:normal;font-size:10px;
  font-weight:400;color:var(--ink3);letter-spacing:.04em;margin-top:3px}
.nav{display:flex;gap:4px;align-items:center}
.nav button{
  padding:11px 20px;border-radius:var(--r);font-size:16px;color:var(--ink2);
  transition:background .18s,color .18s;min-height:46px;
}
.nav button:hover{background:var(--card2);color:var(--ink)}
.nav button.on{background:var(--solid);color:#fff;font-weight:500}
.hd-sp{margin-left:auto;display:flex;align-items:center;gap:12px}
.hd-btn{
  display:flex;align-items:center;gap:8px;padding:10px 16px;border-radius:var(--r);
  border:1px solid var(--line2);font-size:15px;color:var(--ink2);
  transition:border-color .18s,background .18s;min-height:44px;
}
.hd-btn:hover{border-color:var(--ink);color:var(--ink)}
.hd-search{position:relative;width:300px}
.hd-search input{
  width:100%;height:44px;border:1px solid var(--line2);border-radius:var(--r);
  padding:0 40px 0 38px;font-size:16px;background:var(--card);
  transition:border-color .2s,box-shadow .2s;
}
.hd-search input:focus{outline:none;border-color:var(--ink);
  box-shadow:0 0 0 3px rgba(26,26,26,.07)}
.hd-search .sic{position:absolute;left:13px;top:50%;transform:translateY(-50%);
  color:var(--ink3);pointer-events:none}
.hd-search .clr{position:absolute;right:6px;top:50%;transform:translateY(-50%);
  width:32px;height:32px;display:none;align-items:center;justify-content:center;color:var(--ink3)}
.hd-search.has .clr{display:flex}
.hd-search.on{width:420px}
.hd-search.on input{height:48px;font-size:17px}

/* ---------- 首页门面 ---------- */
.hero{
  position:relative;overflow:hidden;border-bottom:1px solid var(--line);
  /* 上下不留白 —— 梁Sir 原话「不要上面和下面的留白,漂移墙下面直接接按什么找」。
     原来 padding:52px/56px 是给三行墙时代的:那时墙矮,靠 padding 撑出一块地方。
     改成五行之后,墙本身就把首页撑满了,再留 padding 就等于墙上下各空一条带子。
     现在上下内边距为 0,首页高度直接等于缸高(见 dwFitHero),
     墙的第一行贴着顶栏、最后一行贴着「按什么找」。 */
  padding:0 32px;
  /* 高度别硬撑。原来 min-height:600px,而 driftwall.js 会把卡**垂直居中**放进缸里
     —— 600px 的缸装 360px 高的卡,上下各空 120px,看着就是「上面挤成堆、下面一片空」。
     现在首页高度由 dwFitHero 按缸的实际高度写死,不再靠 padding 凑。 */
  display:flex;align-items:center;justify-content:center;
}
/* 五线谱背景:极低对比,60 秒缓慢滚动。
   z-index:-1 把它压到最底 —— 墙要盖在它上面,不是跟它抢。 */
.hero::before{
  content:"";position:absolute;inset:-20% -4%;pointer-events:none;z-index:-1;
  background:repeating-linear-gradient(to bottom,
    transparent 0 21px, rgba(26,26,26,.055) 21px 22px);
  background-size:100% 132px;
  animation:staff 44s linear infinite;
  -webkit-mask-image:linear-gradient(to bottom,transparent,#000 35%,#000 65%,transparent);
          mask-image:linear-gradient(to bottom,transparent,#000 35%,#000 65%,transparent);
}
@keyframes staff{from{background-position-y:0}to{background-position-y:132px}}
.hero-in{position:relative;max-width:1440px;margin:0 auto;text-align:center}

/* 漂移墙 = 整面 hero 的背景。不是贴在底下,不是浮在上面,
   是**铺满整个 hero、压在文字下面**。
   三件事必须同时成立,少一件就废:
   ① 墙 pointer-events:auto —— 否则它变成纯装饰,卡片点不动。
   ② .hero-in 整块 pointer-events:none,只让真正要点的东西(搜索框、找谱按钮)
      各自恢复 auto。否则 .hero-in 铺满整个 hero,会把下面的墙整片吃掉鼠标事件。
   ③ 卡片常态淡、hover 才实 —— 这是"当背景"和"当主角"的分界。 */
/* 文字后面垫一层柔光。
   墙铺满整个 hero 之后,卡片会从标题后面穿过去,黑字压在半透明卡片上很难读,
   所以要一层柔光把文字区清干净。

   形状是**窄高的椭圆**,只罩住中间那条文字带(标题/数字/搜索框),
   不是罩住整个 hero。罩大了数字那行背后还透着卡片,读起来像重影。

   ⚠ pointer-events 必须 none。柔光是插在墙(z:2)和文字(z:3)中间的一层,
     默认会照常接收鼠标事件 —— 它自己虽然没内容,但会把光标**整个吃掉**,
     学员点背景里的歌名就点不动。
     实测过:加上 pointer-events:none 之后,沿 y=200 从 x=100 扫到 x=1420,
     卡片一路命中到 x=660,点卡片能正常进详情。

   stop 排得密、每档之间只差几个点,眼睛读出来才是一条平滑的衰减;
   一旦跳得狠(比如 88% 直接掉到 0),中间就出现一圈能看出来的"边" ——
   卡片像被一块玻璃圆片压着,那不是柔光,是盖子。 */
.hero-veil{
  position:absolute;inset:0;z-index:1;pointer-events:none;
  /* ⚠ 别拿 getComputedStyle(...).backgroundImage 里有没有 "at" 来判断
     这条渐变定位正不正常 —— 那是错的,会误判。
     CSSOM 规定 background-image 序列化时**一律省略 at 那一段**,
     省略时默认就是 50% 50%。实测七种写法(带 at / 不带 ellipse /
     像素尺寸 / circle / 不写 at)读回来全都是 `radial-gradient(44% 56%, …)`,
     一条都看不出差别。所以"读不到 at"完全正常,柔光一直是好的。

     这条用百分比尺寸 + at 居中,定位在 .hero-in 那一带的中心,
     也就是标题/数字/搜索框所在的位置。 */
  background:radial-gradient(44% 56% at 50% 50%,
    rgba(247,246,243,.92)  0%,
    rgba(247,246,243,.85) 26%,
    rgba(247,246,243,.78) 42%,
    rgba(247,246,243,.68) 56%,
    rgba(247,246,243,.55) 68%,
    rgba(247,246,243,.40) 78%,
    rgba(247,246,243,.24) 88%,
    rgba(247,246,243,.10) 95%,
    rgba(247,246,243,0)  100%);
}
.hero-wall-bg{
  position:absolute;inset:0;z-index:2;
  pointer-events:auto;
  /* 上下淡出:卡片从上下边缘淡进淡出,而不是被 hero 的 overflow 硬切一刀。

     ⚠ 淡出带必须 **≥ 缸内实际留白**,否则留白会露在渐变之外,
     变成上下两条硬边白带 —— 梁Sir 管它叫「上面和下面的留白」,就是这么来的。
     现在的账:缸 561px、轨道内容 439px,上下共留 122px(各约 60px),
     所以渐变带取 12%(67px)刚好盖住,再宽就会把第一排卡也一起淡掉。

     收窄到 6% 是我算错了:留白没跟着变小,只是把它从渐变里放了出来。 */
  -webkit-mask-image:linear-gradient(to bottom,transparent,#000 12%,#000 88%,transparent);
          mask-image:linear-gradient(to bottom,transparent,#000 12%,#000 88%,transparent);
}
.hero-wall-bg .tank{
  /* ⚠ 这里**不能**写 inset:0。
     driftwall.js 拿到缸之后,会按 fitTank() 的解**强制改写缸高**
     (startDriftWall 里 root.style.height = FIT.h),那是三排卡真正需要的高度。
     这里要是再写死 inset:0 把缸撑满 hero,两个值就打架:
     缸被拉到 hero 那么高,卡却按 fitTank 的解排在上面 ——
     表现就是上面挤成三排、底下空一大片(实测上留白 26px、下留白 150px)。

     所以背景层只负责**把缸摆到 hero 中间**,高度交给 JS。 */
  position:absolute;left:0;right:0;top:50%;
  transform:translateY(-50%);
}
.hero-wall-bg .dw-tile{
  /* 背景态:压低对比,让它读起来是"底子"不是"内容"。
     用 opacity 而不是 filter —— filter 会让每帧给 60 多张卡各建一个合成层,
     实测掉帧。 */
  opacity:.5;
  transition:opacity .16s ease;
}
/* 被顶起的那一张由 driftwall.js 打 is-hot,这里把它拉回前景。
   缓动比墙自己那套顶起(0.08s)略慢一点,才有"浮上来"的感觉而不是"啪一下"。 */
.hero-wall-bg .dw-tile.is-hot{
  opacity:1;transition:opacity .1s ease;
  /* 上浮的卡要能盖住 .hero-in 里的文字。driftwall.js 每帧写 style.transform,
     z 只能在这里给 —— 它每帧重写 transform,顺手写 z-index 会和 CSS 打架。 */
  z-index:5;
}
/* 走廊那套(每排各自的纵向 mask + 悬停时整排解锁)已经撤掉了。
   撤掉的理由:mask 画在 .dw-lane 上,卡片是它的子元素,父级怎么裁子级就得
   跟着裁 —— 悬停的卡只剩上半截,下半被齐刷刷切平,看着像卡住。
   为了补这个又加了「悬停哪排、哪排整体松开」,结果一移开鼠标整排又压暗,
   一亮一暗地闪,比不做更闹。
   现在只留柔光这一层:全墙一视同仁地淡,不区分哪一排、哪一张。 */
/* .hero-in 整块不吃鼠标,只把该点的放行。
   文字层最后画(z:3),永远压在墙和柔光之上,标题不会被卡片压住;
   它本身不吃指针,所以光标能穿过文字区落到墙的卡片上,
   只有搜索框那几处真的能点。 */
.hero-in{pointer-events:none;z-index:3}
.hero-in .hero-search,
.hero-in .hero-search *{pointer-events:auto}

.hero h1{
  font-family:var(--serif);font-size:66px;font-weight:600;letter-spacing:-.035em;
  line-height:1.08;margin-bottom:20px;
}
.hero .sub{font-size:20px;color:var(--ink2);margin-bottom:32px}
.hero .nums{
  display:flex;justify-content:center;gap:0;align-items:baseline;
  margin-bottom:32px;font-family:var(--mono);
}
.hero .nums div{padding:0 34px;border-left:1px solid var(--line2)}
.hero .nums div:first-child{border-left:none}
.hero .nums b{display:block;font-size:42px;font-weight:600;color:var(--ink);
  letter-spacing:-.02em;line-height:1.1}
.hero .nums span{font-size:12px;color:var(--ink3);letter-spacing:.05em}
.hero-search{max-width:620px;margin:0 auto}
.hero-search .box{
  display:flex;align-items:center;gap:12px;height:64px;padding:0 8px 0 22px;
  background:var(--card);border:1px solid var(--line2);border-radius:14px;
  transition:border-color .22s,box-shadow .22s;
}
.hero-search .box:focus-within{border-color:var(--ink);box-shadow:0 0 0 4px rgba(26,26,26,.06)}
.hero-search input{flex:1;height:100%;font-size:18px;background:none}
.hero-search input::placeholder{color:var(--ink3)}
.hero-search .go{
  height:48px;padding:0 26px;border-radius:9px;background:var(--solid);color:#fff;
  font-size:16px;font-weight:500;flex:0 0 auto;
  transition:background .18s;
}
.hero-search .go:hover{background:var(--solid2)}
.hero-hint{margin-top:16px;font-size:14px;color:var(--ink3)}

.sec{max-width:1440px;margin:0 auto;padding:0 32px}
.sec-hd{display:flex;align-items:baseline;gap:14px;margin:44px 0 20px}
.sec-hd h2{font-family:var(--serif);font-size:27px;font-weight:600;letter-spacing:-.02em}
.sec-hd .sub{font-family:var(--mono);font-size:11px;color:var(--ink3);
  letter-spacing:.06em;text-transform:uppercase}
.sec-hd .more{margin-left:auto;font-size:15px;color:var(--ink2);
  border-bottom:1px solid var(--ink3);padding-bottom:2px;transition:color .18s}
.sec-hd .more:hover{color:var(--ink)}

/* 新增曲谱 */
.newbar{
  background:var(--card);border:1px solid var(--line);border-radius:14px;
  padding:22px 24px;display:flex;align-items:center;gap:20px;margin-bottom:8px;
}
.newbar .n{font-family:var(--serif);font-size:34px;font-weight:600;line-height:1}
.newbar .l{font-size:14px;color:var(--ink2);margin-top:4px}
.newbar .sp{margin-left:auto}
.dot-new{display:inline-flex;align-items:center;gap:7px;font-size:13px;color:var(--r-green-t)}
.dot-new::before{content:"";width:7px;height:7px;border-radius:50%;
  background:var(--r-green-t);animation:pulse 2.4s ease-in-out infinite}
@keyframes pulse{0%,100%{opacity:1}50%{opacity:.35}}

/* 横滑条 */
.hscroll{
  display:flex;gap:12px;overflow-x:auto;padding:2px 2px 14px;
  scrollbar-width:none;scroll-snap-type:x proximity;
}
.hscroll::-webkit-scrollbar{display:none}
.hcard{
  flex:0 0 214px;scroll-snap-align:start;background:var(--card);text-align:left;
  border:1px solid var(--line);border-radius:12px;padding:18px;
  transition:border-color .2s,transform .2s,background .2s;
}
.hcard:hover{border-color:var(--ink);transform:translateY(-3px)}
.hcard .kk{
  font-family:var(--serif);font-size:26px;font-weight:600;line-height:1;
  color:var(--kc,#6E6259);margin-bottom:12px;
}
.hcard .n{font-family:var(--serif);font-size:18px;font-weight:600;line-height:1.4;
  margin-bottom:4px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
.hcard .a{font-size:14px;color:var(--ink2);margin-bottom:12px;
  overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
.hcard .f{display:flex;gap:6px;flex-wrap:wrap}

/* 标签 */
.tag{font-size:12px;padding:3px 10px;border-radius:9999px;font-weight:500;
  white-space:nowrap;letter-spacing:.01em}
.tag.k{background:var(--r-gray);color:var(--r-gray-t)}
.tag.s0{background:var(--r-green);color:var(--r-green-t)}
.tag.s1{background:var(--r-yellow);color:var(--r-yellow-t)}
.tag.s2{background:var(--r-red);color:var(--r-red-t)}
/* 1-10 难度五档:入门/简单/中等/偏难/难 */
.tag.lv1{background:#E4EFE4;color:#3C6B46}
.tag.lv2{background:#E8F0DE;color:#55693A}
.tag.lv3{background:#F1EEDC;color:#6E6438}
.tag.lv4{background:#F2EBDD;color:#7A6034}
.tag.lv5{background:#F1E5D9;color:#7E5B3C}
.tag.lv6{background:#EFE0D6;color:#7A5545}
.tag.lv0{background:var(--r-gray);color:var(--gray-t,#6B6862)}
.tag.lvnum{font-variant-numeric:tabular-nums;opacity:.72;margin-left:4px}
.tag.dec{background:var(--r-blue);color:var(--r-blue-t)}
.tag.pop{background:var(--r-red);color:var(--r-red-t)}
/* 超热 = 全库最红的 526 首,给个实心底,跟「热门」一眼分得开。
   学员扫一眼卡片就知道哪几首是压箱底的。 */
.tag.pop.top{background:#B23A2E;color:#fff}

/* 四入口 */
.entries{display:grid;grid-template-columns:repeat(4,1fr);gap:12px}
.entry{
  background:var(--card);border:1px solid var(--line);border-radius:12px;
  padding:26px 24px;text-align:left;display:flex;flex-direction:column;gap:9px;
  transition:border-color .2s,transform .2s;min-height:126px;justify-content:center;
}
.entry:hover{border-color:var(--ink);transform:translateY(-3px)}
.entry .t{font-family:var(--serif);font-size:22px;font-weight:600;letter-spacing:-.01em}
.entry .d{font-size:14px;color:var(--ink3);line-height:1.5}
.entry .ic{margin-bottom:2px;color:var(--ink2)}

/* 推荐网格 */
.grid{display:grid;grid-template-columns:repeat(3,1fr);gap:12px}
@media(max-width:1100px){.grid{grid-template-columns:repeat(2,1fr)}}
.card{
  background:var(--card);border:1px solid var(--line);border-radius:12px;
  padding:20px 22px;text-align:left;position:relative;overflow:hidden;
  transition:border-color .2s,transform .2s;min-width:0;
}
.card:hover{border-color:var(--ink);transform:translateY(-3px)}
.card .n{display:block;font-family:var(--serif);font-size:21px;font-weight:600;line-height:1.3;
  overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
.card .a{display:block;font-size:15px;color:var(--ink2);margin-top:6px;
  overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
.card .f{display:flex;gap:6px;flex-wrap:wrap;margin-top:14px;align-items:center}
.card .kc{
  position:absolute;right:16px;top:14px;font-family:var(--serif);font-size:22px;
  font-weight:600;color:var(--kc,#6E6259);opacity:.2;
}
.big-btn{
  display:inline-flex;align-items:center;justify-content:center;gap:10px;
  height:58px;padding:0 30px;border-radius:var(--r);background:var(--solid);color:#fff;
  font-size:17px;font-weight:500;transition:background .18s;
}
.big-btn:hover{background:var(--solid2)}
.big-btn.ghost{background:var(--card);color:var(--ink);border:1px solid var(--line2)}
.big-btn.ghost:hover{border-color:var(--ink)}

/* ---------- 浏览页:左筛选 + 右结果 ---------- */
.browse{max-width:1440px;margin:0 auto;padding:28px 32px 80px;
  display:grid;grid-template-columns:236px 1fr;gap:34px;align-items:start}
/* 左侧筛选栏:整栏固定在视口内、高度不超过视口,内部独立滚动。
   之前整页一起滚,滚到歌手栏时它已经跑到屏幕外,而且鼠标在左栏里滚的是整个页面。 */
.side{position:sticky;top:calc(var(--hd) + 28px);
  max-height:calc(100vh - var(--hd) - 56px);
  overflow-y:auto;overscroll-behavior:contain;
  padding-right:8px;margin-right:-8px;
  scrollbar-width:thin}
.side::-webkit-scrollbar{width:8px}
.side::-webkit-scrollbar-thumb{background:var(--line2);border-radius:4px}
.side::-webkit-scrollbar-track{background:transparent}
.side-hd{font-family:var(--mono);font-size:11px;color:var(--ink3);
  letter-spacing:.06em;text-transform:uppercase;margin-bottom:12px}
.fgrp{margin-bottom:26px}
.fgrp>h4{font-size:14px;font-weight:600;color:var(--ink);
  display:flex;align-items:center;gap:8px;margin-bottom:10px}
.fgrp>h4 .c{font-family:var(--mono);font-size:11px;color:var(--ink3);font-weight:400}
.fgrp>h4 .why{margin-left:auto;font-size:12px;color:var(--ink3);
  border-bottom:1px solid var(--line2);padding-bottom:1px}
.fgrp>h4 .why:hover{color:var(--ink);border-color:var(--ink)}
/* 已选区的「全部取消」—— 放在标题行右边,固定位置,选几个条件它都在那儿。
   原来那个「全部清除」压在最后一个 chip 底下,选了三四个条件以后
   视线要往下跑才找得到;学员反馈「没办法取消所有已选条件」。
   做成一个正经的按钮(不是一行小字),40-70 岁学员不用瞄准。 */
.fgrp>h4 .why.cancel{
  display:inline-flex;align-items:center;justify-content:center;
  min-height:38px;padding:0 14px;border:1px solid var(--line2);border-radius:8px;
  font-size:14px;color:var(--ink2);background:var(--card);
  transition:border-color .16s,color .16s,background .16s}
.fgrp>h4 .why.cancel:hover{border-color:var(--ink);color:var(--ink);background:var(--card2)}

/* ---- 和弦难度 1-10 级 ---- */
/* 一级一行,写清楚「第几级」+ 这一级的首数。原来是一条 10 格刻度滑杆,
   40-70 岁的学员分不清哪几格是哪一级,改成一级一行的大按钮。
   十级比五级长,所以行高收到 44px(还是够手指点的下限)。 */
.tierbox{display:flex;flex-direction:column;gap:5px}
.tierb{
  display:flex;align-items:center;gap:11px;width:100%;text-align:left;
  min-height:44px;padding:8px 13px;border-radius:8px;
  border:1px solid var(--line);background:var(--card);
  font-size:16px;color:var(--ink2);
  transition:border-color .16s,color .16s,background .16s}
.tierb:hover{border-color:var(--ink3);color:var(--ink)}
.tierb.on{border-color:var(--ink);background:var(--solid);color:#fff;font-weight:600}
.tierb em{display:block;flex:0 0 auto;width:11px;height:11px;border-radius:3px}
.tierb .tb-t{flex:1 1 auto;font-size:16px}
.tierb .tb-n{flex:0 0 auto;font-family:var(--mono);font-size:13px;color:var(--ink3)}
.tierb.on .tb-n{color:rgba(255,255,255,.65)}
.tier-out{margin-top:10px;padding:11px 13px;background:var(--card);border-radius:8px;
  border:1px solid var(--line);font-size:15px;line-height:1.55;color:var(--ink2)}
.tier-out b{font-weight:600;color:var(--ink)}

/* 筛选组里的可滚动名单(歌手 2868 位,不能全铺开) */
.fscroll{max-height:238px;overflow-y:auto;overscroll-behavior:contain;
  padding-right:6px;margin-right:-6px;scrollbar-width:thin}
.fscroll.short{max-height:198px}
.fscroll::-webkit-scrollbar{width:7px}
.fscroll::-webkit-scrollbar-thumb{background:var(--line2);border-radius:4px}
.fscroll::-webkit-scrollbar-track{background:transparent}
.fscroll .fopt{margin-bottom:2px}

.fopt{
  display:flex;align-items:center;gap:10px;width:100%;text-align:left;
  padding:9px 12px;border-radius:8px;font-size:15px;color:var(--ink2);
  transition:background .15s,color .15s;min-height:40px;
}
.fopt:hover{background:var(--card);color:var(--ink)}
.fopt.on{background:var(--solid);color:#fff;font-weight:500}
.fopt .n{margin-left:auto;font-family:var(--mono);font-size:11px;color:var(--ink3)}
.fopt.on .n{color:rgba(255,255,255,.6)}
.fopt .sw{width:9px;height:9px;border-radius:2px;flex:0 0 auto}
.fmore{font-size:13px;color:var(--ink3);padding:8px 12px;text-decoration:underline}
.side-clear{
  width:100%;height:44px;border-radius:8px;border:1px solid var(--line2);
  font-size:14px;color:var(--ink2);transition:border-color .18s;
}
.side-clear:hover{border-color:var(--ink);color:var(--ink)}

.res-hd{display:flex;align-items:baseline;gap:14px;margin-bottom:16px;
  padding-bottom:16px;border-bottom:1px solid var(--line)}
.res-hd h2{font-family:var(--serif);font-size:24px;font-weight:600;letter-spacing:-.02em}
.res-hd .q{font-size:15px;color:var(--ink2)}
.res-hd .q b{color:var(--ink);font-weight:600}
.chip-x{font-size:15px;color:var(--ink);font-weight:600;border:1px solid var(--line);
  border-radius:9999px;padding:6px 14px;min-height:36px;margin-left:8px}
.chip-x:hover{background:var(--solid);color:#fff;border-color:var(--solid)}
.picked{display:flex;gap:8px;flex-wrap:wrap;margin-bottom:18px}
.chip{
  display:flex;align-items:center;gap:9px;background:var(--solid);color:#fff;
  font-size:14px;padding:8px 12px 8px 15px;border-radius:9999px;min-height:38px;
}
.chip button{color:rgba(255,255,255,.75);display:flex;padding:2px}
.chip button:hover{color:#fff}

/* 曲库顶上的「当前条件」提示条 */
.lib-cond{
  display:flex;align-items:center;justify-content:space-between;gap:20px;flex-wrap:wrap;
  background:var(--card);border:1px solid var(--line);border-radius:12px;
  padding:16px 20px;margin-bottom:28px;
}
.lib-cond-t{font-size:16px;color:var(--ink);line-height:1.5}
.lib-cond-t b{font-weight:600}
.lib-cond-s{font-size:13px;color:var(--ink3);margin-top:4px;line-height:1.5}
.lib-cond-a{display:flex;gap:10px;flex-shrink:0}

/* 列表行 */
.rows{border:1px solid var(--line);border-radius:12px;overflow:hidden;background:var(--card)}
.row{
  display:grid;grid-template-columns:44px 1fr 96px 92px 96px;align-items:center;gap:16px;
  padding:15px 20px;border-bottom:1px solid var(--line);width:100%;text-align:left;
  transition:background .15s;min-width:0;
}
.row:last-child{border-bottom:none}
.row:hover{background:var(--card2)}
/* 展开的行不再重复卡片里的信息。
   原来行还在、卡片在下面另起一块,看着就是「列表下面冒出一张新卡片」,
   不是「这一行变成了一张卡片」。现在行收成一条细把手,和下面的卡片
   背景、边框连成一整块。 */
.row.open{
  display:flex;align-items:center;justify-content:flex-end;
  padding:8px 22px;background:var(--card2);border-bottom:none;
}
.row.open .kk,.row.open .mid,.row.open .cell,.row.open .pr{display:none}
.row.open::after{content:'收起';font-size:13px;color:var(--ink3)}
.row.open:hover::after{color:var(--ink2)}
.row .kk{
  font-family:var(--serif);font-size:19px;font-weight:600;color:var(--kc,#6E6259);
  text-align:center;line-height:1;
}
/* 曲名在上、歌手在下,两行。
   这两个 span 原来没写 display:block,行内元素是横着排的 ——
   「1000个太阳林昕阳」直接粘成一行,而且 .a 上的 margin-top 对行内元素无效,
   怎么调都拉不开。 */
.row .mid{min-width:0}
.row .n{display:block;font-family:var(--serif);font-size:18px;font-weight:600;line-height:1.3;
  overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
.row .a{display:block;font-size:14px;color:var(--ink2);margin-top:5px;
  overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
.row .cell{font-size:15px;color:var(--ink2)}
.row .cell.mono{font-family:var(--mono);font-size:12px;color:var(--ink3)}
.row .pr{display:flex;align-items:center;gap:6px;font-size:13px;color:var(--ink3);
  justify-content:flex-end}
.row .pr b{font-family:var(--mono);font-size:15px;color:var(--ink);font-weight:600}
.row .pr.has b{color:var(--r-green-t)}
.row .chev{color:var(--ink3);transition:transform .25s;justify-self:end}
.row.open .chev{transform:rotate(180deg)}

/* 展开的卡片 —— 接在上面的把手下面,两者背景连成一块 */
.detail{
  background:var(--card2);border-bottom:1px solid var(--line);
  padding:0 24px 22px;animation:fade .3s ease both;
}
.dl{min-width:0}
.detail-in{
  background:var(--card);border:1px solid var(--line);border-top:none;
  border-radius:0 0 12px 12px;
  padding:20px 22px 22px;display:grid;grid-template-columns:minmax(0,1fr) 268px;gap:26px;
}
@media(max-width:1100px){.detail-in{grid-template-columns:1fr}}
.dl h3{font-family:var(--serif);font-size:23px;font-weight:600;letter-spacing:-.02em;
  line-height:1.25}
.dl .by{font-size:15px;color:var(--ink2);margin-top:4px}
.dl .kv{display:grid;grid-template-columns:92px 1fr;gap:7px 16px;margin-top:16px;
  font-size:15px}
/* 调号/年代/热门这几行:整个 dd 做成浅底标签。
   【为什么】原来是 dt 灰色小字 + dd 纯黑文字,四个参数一行行排下来像一张
   灰色的清单,学员扫一眼抓不住重点 —— 而这三个恰恰是他挑曲子最先看的
   (什么调、适合我这把年纪吗、是不是大家都弹的)。
   【为什么克制】底色只用 --card2(#F1F0EC)这种几乎看不出颜色的灰,
   靠左侧一根 2px 的竖线区分;不铺彩色块。四行并排时页面还是干净的,
   不会变成花花绿绿的表单。 */
.dl .kv dt{font-family:var(--mono);font-size:11px;color:var(--ink3);
  letter-spacing:.05em;padding-top:3px}
/* 「原调」下面那句解释。
   【为什么必须写】体验官看到「降D大调」直接不敢点 —— 40-70 岁学员看到
   四个升降号的第一反应是「我不会这个调」。但谱库里全是**级数谱**,
   弹什么调由学员自己按手型定,谱面上写的是原曲的调,不是他必须弹的调。
   不说清楚,一个字段就把人劝退了;说清楚了他反而知道可以改成自己顺手的调。
   灰色小字放色块外面 —— 塞进 klab 里会把梁Sir 三轮调定稿的色块撑高撑宽。 */
.dl .kvhint{font-size:14px;color:var(--ink3);margin:10px 0 0 108px;line-height:1.5}
.dl .kv dd{color:var(--ink)}
.dl .kv dd.klab{
  /* 底色是**颜色**,不是长度。
     两头都试错过:
       贴着文字 → 底色只剩开头一小条,梁Sir:「要的是底色,不是开头那一丢丢」
       铺满整行 → 三条 581px 的大色带,梁Sir:「不需要那么长,我要的是颜色,不是长度」
     折中:底色包住文字(左右内边距给足,颜色成块),宽度跟着内容走,
     不再横穿整行 —— 一张卡片右侧拉三条同长的灰条,读起来像表单,不像参数。 */
  display:inline-block;padding:7px 16px;border-radius:8px;
  /* ⚠ 这一行不能省。
     .dl .kv 是 grid,grid 子项默认**拉伸**填满整列,
     而且 display:inline-block 会被 grid「块化」,写不写都一样 ——
     结果色块照样横穿整行 581px。之前只写 inline-block 就漏在这里,
     颜色改了、宽度没改,看着像「改了没生效」。 */
  justify-self:start;
  background:var(--card2);border-left:2px solid var(--line2);
  line-height:1.5;
}
/* 三类参数各给一根不同色的竖线 + 一层各自的浅色底 ——
   色相不同、饱和度都极低:远看只是「三块颜色很淡的标签」,不抢眼,
   但学员眼睛一扫就知道这行说的是调号还是年代还是热门。
   底色比竖线再淡一档 —— 底色是底,竖线才是那道「区分」的线。 */
.dl .kv dd.klab.k-key{border-left-color:#B9AE9E;background:#EFE9DD}
.dl .kv dd.klab.k-era{border-left-color:#A8B5BE;background:#E7EDF1}
.dl .kv dd.klab.k-pop{border-left-color:#C7A79E;background:#F3E5DF}
/* 和弦难度 —— 第四块标签,淡绿调,跟前三块区分得开又不抢眼 */
.dl .kv dd.klab.k-lv{border-left-color:#A9B89E;background:#EDF1E6}
.dl .tags{display:flex;gap:7px;flex-wrap:wrap;margin-top:14px}

/* ---------- 1-10 难度条 ---------- */
.lvwrap{margin-top:18px}
.lvbar{display:flex;gap:4px;height:10px}
.lvbar i{flex:1;border-radius:2px;background:var(--line2);transition:background .2s}
.lvbar i.on.lv1{background:#A9C7A6}
.lvbar i.on.lv2{background:#BCCDA2}
.lvbar i.on.lv3{background:#CFD3A3}
.lvbar i.on.lv4{background:#DCD2A8}
.lvbar i.on.lv5{background:#DFC3A0}
.lvbar i.on.lv6{background:#D9B396}
.lvtip{display:flex;align-items:baseline;gap:10px;margin-top:8px;flex-wrap:wrap}
.lvtip b{font-size:15px;font-weight:600}
.lvtip span{font-size:13px;color:var(--ink2)}
.lvbar-none{font-size:14px;color:var(--ink3);margin-top:16px}

/* ---------- 和弦分级 ---------- */
.chwrap{margin-top:18px;border-top:1px solid var(--line);padding-top:16px}
.ch-head{display:flex;flex-direction:column;gap:3px;margin-bottom:12px}
.ch-head b{font-size:15px;font-weight:600}
.ch-head span{font-size:13px;color:var(--ink3)}
.ch-list{display:flex;flex-direction:column;gap:9px}
.ch-row{display:grid;grid-template-columns:48px 48px 1fr;align-items:start;gap:10px;
  padding:8px 0;border-top:1px solid var(--line)}
.ch-row:first-child{border-top:none}
/* 「最难的那一档」摘要块:给它一块极浅的底 + 一行小标题,
   跟下面展开的完整清单分开。不加这个的话两块紧贴着,
   展开后同一档(最难那档)在开头和结尾各出现一次,看着像重复了。 */
.ch-mini{background:var(--card2);border-radius:9px;padding:10px 13px 4px;margin-bottom:4px}
.ch-mini-lb{font-size:12px;color:var(--ink3);margin-bottom:2px;letter-spacing:.03em}
.ch-mini .ch-row{border-top:none;padding:4px 0 8px}
.ch-lv{font-family:var(--mono);font-size:13px;font-weight:600;text-align:center;
  padding:4px 0;border-radius:5px;min-width:48px}
.ch-n{font-size:13px;color:var(--ink3);padding-top:4px;white-space:nowrap}
.ch-items{display:flex;flex-wrap:wrap;gap:6px;padding-top:2px;min-width:0}
.ch{font-family:var(--mono);font-size:14px;background:var(--card2);
  border:1px solid var(--line);border-radius:6px;padding:5px 9px;line-height:1.2}
.ch.more{font-family:inherit;color:var(--ink3);background:transparent;border-style:dashed}
.ch-empty{font-size:14px;color:var(--ink3)}
/* 和弦默认只露最难的一档,全量收在这后面;两下都能来回切 */
.ch-all[hidden]{display:none}
.ch-toggle{
  margin-top:12px;width:100%;min-height:44px;padding:9px 16px;
  background:var(--card2);border:1px solid var(--line);border-radius:10px;
  font-size:14px;color:var(--ink2);font-family:inherit;cursor:pointer;
  transition:border-color .18s,color .18s;
}
.ch-toggle:hover{border-color:var(--ink);color:var(--ink)}

/* 难度说明弹层 */
.lvhint{display:flex;flex-direction:column;margin:4px 0 14px}
.lvh-row{display:grid;grid-template-columns:48px 52px 1fr;align-items:center;gap:12px;
  padding:8px 0;border-top:1px solid var(--line)}
.lvh-row:first-child{border-top:none}
.lvh-band{font-size:14px;font-weight:600}
.lvh-d{font-size:13px;color:var(--ink2);line-height:1.5}
.lvh-note{font-size:13px;color:var(--ink2);line-height:1.65;
  background:var(--card2);border-radius:8px;padding:12px 14px;margin-top:8px}
.lvh-note b{font-weight:600;color:var(--ink)}
.dr{display:flex;flex-direction:column;gap:10px}
.acts{display:grid;gap:10px}
.practice{
  background:var(--card2);border-radius:10px;padding:13px 14px;margin-bottom:4px;
}
.practice .lb{font-size:13px;color:var(--ink2);margin-bottom:11px}
.practice .lb b{font-family:var(--mono);font-size:22px;color:var(--ink);font-weight:600}
.pbtn{
  height:52px;border-radius:9px;background:var(--solid);color:#fff;font-size:16px;
  font-weight:500;width:100%;transition:background .18s;display:flex;
  align-items:center;justify-content:center;gap:9px;
}
.pbtn:hover{background:var(--solid2)}
.pbtn .n{font-family:var(--mono);font-weight:600}
.pbtn.ghost{background:var(--card);color:var(--ink);border:1px solid var(--line2)}
.pbtn.ghost:hover{border-color:var(--ink)}
.pbtn.star.on{background:var(--solid);color:#fff;border-color:var(--solid)}
.pbtn.star.on .ic{fill:#fff}

/* ===================================================================
   求谱
   -------------------------------------------------------------------
   学员 40-70 岁:正文 ≥18px、次要 ≥14px、点哪儿都得 ≥44px。
   状态标签一律浅底,不用彩色 —— 学员分不清花里胡哨的颜色,只分得出字。
   =================================================================== */
.entry-req{
  width:100%;margin-top:12px;display:flex;align-items:center;gap:18px;text-align:left;
  background:var(--card);border:1px solid var(--line);border-radius:12px;padding:22px 24px;
  transition:border-color .2s,transform .2s;min-height:76px;
}
.entry-req:hover{border-color:var(--ink);transform:translateY(-2px)}
.entry-req .ic{color:var(--ink2);flex:0 0 auto}
.entry-req .ti{display:flex;flex-direction:column;gap:4px;min-width:0}
.entry-req .t{font-family:var(--serif);font-size:21px;font-weight:600}
.entry-req .d{font-size:15px;color:var(--ink3)}
.entry-req .ar{margin-left:auto;font-size:26px;color:var(--ink3);line-height:1}

.rq-lead{font-size:18px;color:var(--ink2);line-height:1.65;margin:2px 0 22px;max-width:640px}
.rq-go{
  height:58px;padding:0 40px;border-radius:10px;background:var(--solid);color:#fff;
  font-size:19px;font-weight:500;font-family:inherit;cursor:pointer;
}
.rq-go:hover{background:var(--solid2)}
.rq-wait{font-size:15px;color:var(--ink3);margin:14px 0 0}
.rq-err{font-size:16px;color:var(--r-red-t);margin:14px 0 0}
.rq-hd{margin:48px 0 16px}
.rq-hd h2{font-family:var(--serif);font-size:23px;font-weight:600}

/* 热度榜 —— 整行都是按钮,点了滚到下面那条 */
.rq-hot{display:flex;flex-direction:column;border:1px solid var(--line);border-radius:12px;overflow:hidden}
.hq{
  display:flex;align-items:center;gap:16px;width:100%;text-align:left;
  background:var(--card);border:0;border-bottom:1px solid var(--line);
  padding:16px 22px;min-height:64px;cursor:pointer;font-family:inherit;
}
.hq:last-child{border-bottom:0}
.hq:hover{background:var(--bg)}
.hq-r{font-family:var(--mono);font-size:15px;color:var(--ink3);flex:0 0 22px}
.hq-ti{display:flex;flex-direction:column;gap:3px;min-width:0}
.hq-t{font-family:var(--serif);font-size:20px;font-weight:600;line-height:1.3;
  overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
.hq-a{font-size:15px;color:var(--ink2)}
.hq-n{margin-left:auto;flex:0 0 auto;font-size:15px;color:var(--ink3);white-space:nowrap}

/* 最热 / 最新 */
.rq-sort{display:flex;gap:10px;margin:44px 0 20px}
.rq-sb{
  min-height:48px;padding:0 28px;border-radius:10px;border:1px solid var(--line2);
  background:var(--card);color:var(--ink2);font-size:16px;font-family:inherit;cursor:pointer;
}
.rq-sb:hover{border-color:var(--ink)}
.rq-sb.on{background:var(--solid);border-color:var(--solid);color:#fff;font-weight:500}

/* 求谱卡片 */
.rq-list{display:flex;flex-direction:column;gap:12px}
.rq{background:var(--card);border:1px solid var(--line);border-radius:12px;padding:20px 22px}
.rq.closed{opacity:.55}
.rq.flash{animation:reqflash 1.4s ease-out}
@keyframes reqflash{
  0%,22%{background:#F1EEE7;border-color:var(--ink)}
  100%{background:var(--card);border-color:var(--line)}
}
.rq-top{display:flex;align-items:flex-start;gap:16px}
.rq-ti{min-width:0;flex:1}
.rq h3{font-family:var(--serif);font-size:22px;font-weight:600;line-height:1.3;
  overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
.rq-ar{font-size:16px;color:var(--ink2);margin-top:4px;
  overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
.rq-st{flex:0 0 auto;font-size:14px;padding:5px 11px;border-radius:6px;white-space:nowrap}
.rq.open   .rq-st{background:#F3E9D2;color:#6B5A2E}
.rq.found  .rq-st{background:#E2EDE2;color:#3E5C3E}
.rq.closed .rq-st{background:#ECEBE8;color:#6E6E6E}
.rq-note{font-size:18px;line-height:1.6;color:var(--ink2);margin-top:14px}
.rq-au{display:inline-block;margin-top:12px;font-size:16px;color:var(--ink);
  border-bottom:1px solid var(--line2);padding-bottom:2px}
.rq-au:hover{border-color:var(--ink)}
.rq-ft{display:flex;align-items:center;gap:14px;margin-top:18px;
  padding-top:16px;border-top:1px solid var(--line)}
.rq-who{display:flex;align-items:center;gap:10px;min-width:0}
.rq-nm{font-size:16px;white-space:nowrap}
.rq-sno{font-family:var(--mono);font-size:14px;color:var(--ink3);white-space:nowrap}
.rq-vote{
  margin-left:auto;flex:0 0 auto;min-height:44px;padding:0 20px;border-radius:9px;
  border:1px solid var(--line2);background:var(--card);color:var(--ink2);
  font-size:16px;font-family:inherit;cursor:pointer;white-space:nowrap;
}
.rq-vote:hover{border-color:var(--ink);color:var(--ink)}
.rq-vote.on{background:var(--solid);border-color:var(--solid);color:#fff}

/* 提谱表单 */
.rf{margin-bottom:18px}
.rf label{display:block;font-size:14px;color:var(--ink3);margin-bottom:7px}
.rf input[type=text],.rf input[type=tel],.rf input:not([type]),.rf textarea{
  width:100%;border:1px solid var(--line2);border-radius:8px;background:var(--bg);
  padding:0 14px;font-size:19px;color:var(--ink);font-family:inherit;
}
.rf input:not([type]){height:56px;display:block}
.rf input[type=file]{
  width:100%;font-size:16px;color:var(--ink2);background:var(--bg);
  border:1px solid var(--line2);border-radius:8px;padding:14px;
}
.rf textarea{min-height:104px;padding:14px;line-height:1.6;resize:vertical;display:block}
.rf input:focus,.rf textarea:focus{outline:none;border-color:var(--ink);box-shadow:0 0 0 4px rgba(26,26,26,.06)}
.rf input:disabled{background:var(--card2);cursor:not-allowed}
.rf-tip{font-size:16px;line-height:1.55;color:var(--ink2);min-height:0;margin:0}
.rf-tip.err{color:var(--r-red-t)}
.rf-tip:empty{display:none}

/* ---------- 我的 ---------- */
.mine{max-width:900px;margin:0 auto;padding:40px 32px 90px}
.mine h2{font-family:var(--serif);font-size:26px;font-weight:600;letter-spacing:-.02em;
  margin-bottom:18px}
.panel{background:var(--card);border:1px solid var(--line);border-radius:12px;padding:4px 24px}
.acc{background:var(--card);border:1px solid var(--line);border-radius:12px;padding:22px 24px;margin-top:18px}
.acc .ttl{font-family:var(--mono);font-size:11px;letter-spacing:.05em;color:var(--ink3);margin:0 0 4px}
.acc .who{font-size:21px;font-weight:600;letter-spacing:-.01em}
.acc .fld{margin-top:14px}
.acc .fld label{display:block;font-family:var(--mono);font-size:11px;letter-spacing:.04em;color:var(--ink3);margin-bottom:7px}
.acc .fld input{width:100%;height:54px;border:1px solid var(--line2);border-radius:8px;background:var(--bg);
  padding:0 14px;font-size:19px;color:var(--ink);font-family:inherit}
.acc .fld input:focus{outline:none;border-color:var(--ink);box-shadow:0 0 0 4px rgba(26,26,26,.06)}
.acc .btns{display:flex;gap:12px;margin-top:18px;flex-wrap:wrap}
.acc .btns .big-btn{flex:1 1 150px}
.acc .msg{margin-top:14px;font-size:16px;line-height:1.55;min-height:0}
.acc .msg[hidden]{display:none}
.acc .msg.err{color:var(--r-red-t)}

/* ---------- 身份条(已登录的学号/昵称/头像) ---------- */
.acc-id{display:flex;align-items:center;gap:16px}
.acc-av,.acc-av-img,.acc-av-block{display:inline-flex;align-items:center;justify-content:center;
  border-radius:50%;overflow:hidden;flex:0 0 auto}
.acc-av-img{object-fit:cover;background:var(--bg)}
.acc-av-block{color:#fff;font-weight:600;font-size:17px;letter-spacing:.02em;
  font-family:var(--mono)}
.acc-txt{display:flex;flex-direction:column;gap:3px;line-height:1.4;min-width:0}
.acc-txt b{font-size:21px;font-weight:600;letter-spacing:-.01em}
.acc-txt i{font-style:normal;font-family:var(--mono);font-size:14px;color:var(--ink2)}
.acc-txt i.dim{color:var(--ink3)}
.acc .hint{margin-top:14px}
.acc .hint b{font-weight:600}

/* ---------- 登录闸门 ---------- */
body.locked{overflow:hidden}
.gate{position:fixed;inset:0;background:var(--bg);z-index:60;overflow:auto;
  display:flex;align-items:center;justify-content:center;padding:40px 24px}
.gate-card{width:100%;max-width:440px;background:var(--card);border:1px solid var(--line);
  border-radius:12px;padding:38px 36px 32px;text-align:left}
.gate-logo{font-family:var(--serif);font-size:32px;font-weight:600;letter-spacing:-.02em;margin:0}
.gate-sub{margin:8px 0 26px;font-size:17px;color:var(--ink2);line-height:1.6}
.gate-card .fld{margin-top:16px}
.gate-card .fld label{display:block;font-family:var(--mono);font-size:11px;
  letter-spacing:.04em;color:var(--ink3);margin-bottom:7px}
.gate-card .fld input{width:100%;height:56px;border:1px solid var(--line2);border-radius:8px;
  background:var(--bg);padding:0 15px;font-size:20px;color:var(--ink);font-family:inherit}
.gate-card .fld input:focus{outline:none;border-color:var(--ink);box-shadow:0 0 0 4px rgba(26,26,26,.06)}
.gate-go{width:100%;height:58px;margin-top:24px;font-size:18px}
.gate-demo{display:block;width:100%;margin-top:12px;height:48px;font-size:16px;
  color:var(--ink3);background:none;border:1px solid var(--line);border-radius:12px;cursor:pointer}
.gate-demo:hover{color:var(--ink);border-color:var(--ink3)}
.gate-tip{margin:20px 0 0;font-size:15px;line-height:1.7;color:var(--ink3)}
.gate-card .msg{margin:0 0 4px;font-size:16px;line-height:1.55}
.gate-card .msg[hidden]{display:none}
.gate-card .msg.err{color:var(--r-red-t)}
.acc .msg.ok{color:#4E7A4E}
.acc .hint{font-size:15px;line-height:1.6;color:var(--ink3);margin-top:12px}
.panel .kv{display:grid;grid-template-columns:110px 1fr;gap:16px;padding:17px 0;
  border-bottom:1px solid var(--line);font-size:16px}
.panel .kv:last-child{border-bottom:none}
.panel .kv dt{font-family:var(--mono);font-size:11px;color:var(--ink3);
  letter-spacing:.05em;padding-top:4px}

/* ---------- 致命错误条(只在真出错时出现) ---------- */
#errbar{position:fixed;left:0;right:0;top:0;z-index:9999;background:#8C2B22;color:#fff;
  font:13px/1.5 var(--mono);padding:10px 16px;white-space:pre-wrap;word-break:break-all}
#errbar[hidden]{display:none}
.stats{display:grid;grid-template-columns:repeat(3,1fr);gap:12px;margin-bottom:14px}
.stat{background:var(--card);border:1px solid var(--line);border-radius:12px;padding:22px}
.stat b{display:block;font-family:var(--serif);font-size:34px;font-weight:600;line-height:1}
.stat span{display:block;font-size:14px;color:var(--ink2);margin-top:7px}

/* ---------- 空态 / 提示 ---------- */
.empty{text-align:center;padding:90px 24px;color:var(--ink3)}
.empty .ic{margin:0 auto 18px;color:var(--line2)}
.empty p{font-size:17px}

/* 搜不到时的求谱入口 —— 上面那句「没找到」是死路,这里给个出口 */
.askreq{max-width:520px;margin:0 auto 90px;padding:26px 24px 28px;
  border:1px solid var(--line);border-radius:12px;background:#fff;text-align:center}
.askreq-t{font-family:var(--serif);font-size:22px;font-weight:600;margin:0 0 8px}
.askreq-d{font-size:16px;color:var(--ink3);margin:0 0 20px}

/* 最近流行 —— 漂移墙(DriftWall)。
   照 React Bits 的 DriftWall 翻写的纯 JS 版:一面带透视的墙,
   一条条横排卡的长行上下叠成墙,每行独立速度从右往左飘,
   鼠标移上去整面墙跟着轻轻转,某张卡会顶起来。
   跟之前那版最大的不同:速度用 JS 伪随机生成,不是我手写的固定数列 ——
   固定数列再怎么排都看得出规律,伪随机才像真的在漂。 */
.hotrail{margin:44px 0 10px}
.hotrail-hd{display:flex;align-items:baseline;gap:12px;margin:0 2px 18px}
.hotrail-hd h2{font-family:var(--serif);font-size:21px;font-weight:600;margin:0}
.hotrail-hd .go{margin-left:auto;font-size:15px;color:var(--ink2);
  background:none;border:none;cursor:pointer;font-family:inherit}
.hotrail-hd .go:hover{color:var(--ink)}

/* 缸高只是初值,driftwall.js 建完墙会按实际行高把它改掉。
   两边各写一个数字、指望它们永远对得上,是最容易埋雷的做法。 */
.tank{position:relative;height:296px;overflow:hidden;
  perspective:1200px;perspective-origin:50% 50%}
/* 左右淡出用两块静态渐变挡板,不用 mask-image。
   mask + mask-composite 是逐像素离屏合成,缸里每帧都在动,
   等于每帧都要重算一整块遮罩 —— 旧显卡上这一项最费。
   两块不动的渐变块只是叠在上面,GPU 只做合成,几乎不要钱。 */
/* 两头要更宽的渐变:横向纵深把最左/最右的卡放到最近和最远,
   放大和缩小的幅度都在两头,卡片被缸口切出来的边缘最明显。
   96px 挡不住,加宽到 132px,学员看到的就只剩一次淡淡的淡出。 */
.dw-fade{position:absolute;top:0;bottom:0;width:132px;pointer-events:none;z-index:3}
.dw-fade.l{left:0;background:linear-gradient(90deg,var(--paper),rgba(247,246,243,0))}
.dw-fade.r{right:0;background:linear-gradient(270deg,var(--paper),rgba(247,246,243,0))}
/* 墙 = 几条横向长行上下叠。
   preserve-3d 必须一路传到卡片身上,悬停「顶起来」才有意义:
   .dw-plane → .dw-lane → .dw-track 三层都得开,少一层,
   里面的 translateZ 就被压平成纯 2D 位移,卡片根本不往眼前凑 ——
   表现是「点了没反应」,学员只会觉得反着。
   只有这三层(3+3 个元素)开,不开 .dw-tile 那一层(几十个元素),
   3D 上下文一共 6 个,旧机器上也扛得住。 */
.dw-plane{position:absolute;top:0;left:0;width:100%;
  display:flex;flex-direction:column;gap:6px;
  transform-style:preserve-3d;will-change:transform;
  /* 平面本身不许接鼠标。这是一整块铺满墙的 3D 面,停在 z=0;
     最上面那排卡片被推到 z<0(推远),几何上就跑到这块面的**后面**去了。
     浏览器做 3D 命中测试时,谁的平面离眼睛近谁吃掉光标 ——
     于是那一排的鼠标事件全被平面截走,卡片连 mouseover 都收不到,
     悬停彻底失效,而且不报任何错。实测:第一排命中 .dw-plane 0/300 帧,
     第二三排正常 300/300。学员看到的现象就是「第一排怎么悬停都没反应」。
     子元素显式写 pointer-events:auto,不受父级 none 的影响,所以卡片照常能点。 */
  pointer-events:none}
/* 挡住光标的是平面,不是行,所以行和卡片都恢复正常接收 */
.dw-lane{flex:0 0 auto;transform-style:preserve-3d;pointer-events:auto}
.dw-track{display:flex;flex-direction:row;height:100%;
  transform-style:preserve-3d;will-change:transform}

/* 行高用 JS 算好后塞进 --laneh,不用 height:100% ——
   百分比高度要一层层往上解析,中间任何一层没确定高度就整条链断掉,
   结果是卡片由内容撑开:曲名长一行就顶破行距,行和行挤在一起。 */
/* 宽度不在这里定:driftwall.js 按每张卡那行字有多长算好写在 style 上。
   写死一刀切的话,按最长的歌名定,短歌名就白占一截,一整排像复制粘贴的。 */
.dw-tile{position:relative;padding:4px;
  flex:0 0 auto;height:var(--laneh,92px);cursor:pointer}
   /* 注意:这里**不能**写 transition:transform。
      横向纵深是 driftwall.js 每帧按卡片位置写上去的 transform
      (左边最近、右边最远),要是再让 CSS 去插值,每帧就被拉去追一个
      永远追不上的目标,卡片会发黏、还会跟横向纵深互相拉扯。
      悬停「顶起来」的缓动也一起搬到 JS 里做了(见 applyDepth)。 */
/* 热区比卡片大一圈:右边那些卡被缩得小,卡跟卡的间隙看起来比左边宽,
   光标扫过去经常「擦着过去」什么也没发生 —— 学员说的「越靠右越不灵光」。
   这里每张卡加一圈透明命中区(不吃版式,只是个空盒子),
   鼠标擦到边上也算悬停。左右各 6px、上下各 7px。 */
.dw-tile::after{content:"";position:absolute;inset:-7px -6px;border-radius:14px}
/* 卡本体:平时半透明,悬停时顶起来 + 变实,像浮出墙面。
   overflow:hidden 在这儿是保命的:行高是按内容算死的,一旦曲名长到换行,
   多出来那行会顶出卡片 —— 没这个 hidden,字就直接压在下一行轨道上。 */
.dw-in{background:#fff;border:1px solid var(--line);border-radius:12px;
  padding:11px 15px;height:100%;
  display:flex;flex-direction:column;gap:2px;justify-content:center;
  opacity:.66;transform:translateZ(0);
  overflow:hidden;
  transition:opacity .42s cubic-bezier(.22,1,.36,1),
             transform .42s cubic-bezier(.22,1,.36,1),
             box-shadow .42s cubic-bezier(.22,1,.36,1),
             border-color .42s cubic-bezier(.22,1,.36,1)}
/* 悬停的那一张要「离学员最近」,所以顶起来的两件事由 driftwall.js 的
   applyDepth() 一起算,直接写进 style.transform:
     translateZ(+90px) 把它往眼睛的方向推
     scale(1.14)       让它明显大一号,凑到眼前
   之所以不写在 CSS 里,是因为卡片身上已经有一层每帧都在变的
   「横向纵深 z」,CSS 再插值一次就会跟它抢同一个 transform。
   幅度是压着缸口算的:最下面那排、横向最左的那张悬停时刚好不越出缸底。
   左右两邻张都比它远,立体排序自动把它压在邻居上面,
   不需要 z-index(在 preserve-3d 里 z-index 是不生效的)。
   这个类名只负责卡面的样子(描边、阴影、透明度),transform 归 JS。 */
.dw-tile.is-hot .dw-in{opacity:1;border-color:var(--ink);
  box-shadow:0 20px 46px -16px rgba(0,0,0,.28)}

/* 只有两行:曲名 + 一行副标题(歌手 · 调性)。
   调性原本自己占一行,三行内容就超出了卡片高度,而 .dw-in 是 overflow:hidden
   —— 结果最下面那行被裁掉一半,远排尤其明显(它缩得最小,裁得最多)。
   合并成一行既解决了裁字,也让卡片矮一截、整面墙更紧凑。 */
.dw-t{font-family:var(--serif);font-size:22px;font-weight:600;line-height:1.22;
  color:var(--ink);display:block;
  white-space:nowrap;overflow:hidden;text-overflow:ellipsis}
/* 副标题 17px:横向纵深最远那张会缩到 0.86,17 × 0.86 = 14.6px,
   正好卡在学员端次要文字的下限上。之前用 16px,最远处只剩 13.3px ——
   40-70 岁学员看着就是一团糊,说「越靠右越不灵光」。
   别再往小调:16px 保不住 14px,15px 更保不住。 */
.dw-s{font-size:17px;color:var(--ink3);display:block;line-height:1.3;
  white-space:nowrap;overflow:hidden;text-overflow:ellipsis;opacity:.92}
/* 大卡只管字号,不管宽度 —— 宽度由 JS 按字号算好了,
   这里再改一遍宽度,就会跟 JS 算出来的对不上(每张卡的字宽都不一样)。 */
.dw-tile.big .dw-t{font-size:25px}

@media (prefers-reduced-motion:reduce){
  .dw-plane,.dw-track{will-change:auto}
}
/* 学员系统里关了动画效果(Windows 的「动画效果」开关)时,漂移墙是不能漂的。
   硬留着就是一堵不动的卡片墙占着地方,看着像页面卡住了 ——
   所以这时候干脆不建墙,老老实实摆成静态网格,信息照样看得全。 */
.hotflat{display:flex;flex-wrap:wrap;gap:10px}
.hf{flex:1 1 calc((100% - 40px)/5);border:1px solid var(--line);border-radius:12px;
  background:#fff;padding:11px 15px;min-height:70px;cursor:pointer;
  display:flex;flex-direction:column;gap:2px;justify-content:center}
.hf:hover{border-color:var(--ink)}
.note{max-width:1440px;margin:0 auto;padding:0 32px;font-family:var(--mono);
  font-size:12px;color:var(--ink3);line-height:1.8;letter-spacing:.02em}

/* ---------- 偏好弹层 ---------- */
.mask{position:fixed;inset:0;background:rgba(26,26,26,.34);z-index:90;
  display:none;align-items:center;justify-content:center;padding:24px}
.mask.show{display:flex}
/* 弹层:头 / 内容(滚动) / 底(按钮常驻) 三段固定。
   之前按钮排在选项列表后面,歌手一多按钮就被顶到屏幕外,学员根本点不到。 */
.sheet{background:var(--bg);width:100%;max-width:560px;
  max-height:min(86vh,720px);display:flex;flex-direction:column;
  border-radius:14px;animation:rise .3s cubic-bezier(.16,1,.3,1) both}
.sheet-hd{flex:0 0 auto;padding:30px 32px 0}
.sheet-hd h3{font-family:var(--serif);font-size:26px;font-weight:600;letter-spacing:-.02em;
  margin-bottom:8px}
.sheet-hd .dsc{font-size:15px;color:var(--ink2);margin-bottom:18px}
.sheet-bd{flex:1 1 auto;overflow-y:auto;-webkit-overflow-scrolling:touch;
  padding:0 32px;min-height:0;position:relative}
/* 歌手名单这类长列表:固定一块高度,里面滚,底部按钮不动 */
.sheet-bd.tall{max-height:min(46vh,380px);padding-bottom:6px}
.sheet-bd.tall::-webkit-scrollbar{width:8px}
.sheet-bd.tall::-webkit-scrollbar-thumb{background:var(--line2);border-radius:4px}
.sheet-bd.tall::-webkit-scrollbar-track{background:transparent}
.sheet-ft{flex:0 0 auto;padding:16px 32px 26px;background:var(--bg);
  border-top:1px solid var(--line);display:flex;gap:10px}
.sheet-ft .pbtn{margin:0 !important;flex:1 1 auto}
/* ---------- 谱面弹层:全站唯一一个占大屏的弹层 ----------
   【为什么必须占大屏】谱面是要**照着弹**的,不是浏览一下的。560px 的窄弹层里
   塞一张 A4 谱,五线谱只剩指头宽,等于没给。实测 Chrome 把 PDF 放进 iframe
   之后工具条是全的 —— 页码、缩放 −/100%/＋、翻页、打印、下载都在
   (见 check_detail.js 的对照读数),所以内嵌不影响「放大看谱」这件正事。
   【为什么不用 blob / pdf.js 自己画】自己画就得重写缩放、翻页、旋转、打印,
   给一个「看谱」功能凭空造一整套 PDF 阅读器,不值得;浏览器自带的已经很好。 */
/* 谱面弹层打开时藏掉页面自己的滚动条。
   弹层是 position:fixed,它的宽度按「视口减去滚动条」算 —— 实测 1600 宽的
   窗口上弹层只有 1585,右边永远空着一条,梁Sir 要的「一定全屏」就没做到。
   反正弹层期间整个页面都被盖住、也点不到,把滚动条收掉才是真全屏。 */
html.nowheet{overflow:hidden}
html.nowheet body{overflow:hidden}
.sheet.wide{width:100%;height:100%;max-width:none;max-height:none;border-radius:0;
  animation:none}
/* ⚠ 三个都不能省,少一个就会露馅:
   ① width/height 用 100% 不用 100vw/100vh —— 100vw 把**滚动条也算进去**,
      实测 1600 宽的窗口上 sheet 变成 1585、右侧露一条,永远对不齐;
   ② animation:none —— 基础 .sheet 有 animation:rise .3s ... both,
      fill-mode both 会把动画终态(translateY(14px))**永久留住**,
      实测 sheetTop 恒等于 14,弹层永远差 14px 没贴到顶,底下那排按钮
      有一半在屏幕外 —— 这正是梁Sir「放大一点按钮不见了」那个现象的成因;
   ③ 外层 .mask 的 padding 也要清(见 .mask.wide),否则 mask 24px + 满屏
      弹层 = 超出窗口,上下同时被切。
   梁Sir 原话:「弹窗一定要全屏,尤其上下,不然本来就看不清谱子,
   还不能看整页,更难受了」——所以宽也一起要满。 */
.mask.wide{padding:0;align-items:stretch;justify-content:stretch}
.sheet.wide .sheet-hd{padding:20px 26px 0;display:flex;align-items:baseline;gap:14px}
.sheet.wide .sheet-hd h3{font-size:21px;margin-bottom:0}
.sheet.wide .sheet-hd .dsc{font-size:14px;margin-bottom:0;color:var(--ink3)}
.sheet.wide .sheet-bd{padding:14px 26px 0;overflow:hidden;display:flex}
.sheet.wide .sheet-ft{padding:12px 26px 18px;justify-content:flex-end}
/* ⚠ width:auto 不能省。
   .pbtn 本身是 width:100%(列表里的按钮要撑满一格)。只写 flex:0 0 auto
   的话,flex-basis:auto 会去取 width:100% —— 两个按钮各占满一整行,
   「在新标签页打开」被挤出弹层看不见,底下只剩一个横贯全宽的「关掉」。
   截图里就是这么发现的:DOM 断言读 buttons 数组两个都在,肉眼看只剩一个。 */
.sheet.wide .sheet-ft .pbtn{flex:0 0 auto;width:auto;min-width:170px}

/* ---------- 谱面阅读区(后端转图,前端只摆 <img>) ----------
   【为什么不用 iframe 直接嵌 PDF】梁Sir 用 Edge 打开,弹层里是一片灰。
   实测 Edge 136 新开实例能渲染,但这说明不了什么 —— 版本、用户设置、
   企业策略随便哪一样变了就不渲染,还有的直接把 PDF 下载掉。
   浏览器自带的 PDF 阅读器在 iframe 里的行为是**不可控**的,而这是学员
   看谱的唯一入口,不能赌。
   【为什么不用 pdf.js 在网页里画 PDF】pdf.js 会随机卡死:同一台机器、
   同一份文件,大约一半的运行里它停在渲染中途,promise 不 resolve 也不
   reject,页面上永远只有「正在打开谱面…」。我为它试过五种办法全都没用。
   「看谱」是学员唯一的主路径,不能赌一个会随机失效的第三方库。
   【现在的做法】后端把 PDF 逐页转成 PNG(GET /api/sheet-img/{uid}?page=N&w=宽度),
   前端就是 <img> —— 没有 worker、没有主线程解析、没有随机失效。
   全库本来就有 181 首是图片谱,这条路是现成的。 */
.pvw{flex:1 1 auto;min-height:0;min-width:0;display:flex;flex-direction:column;gap:10px}
.pvw-bar{flex:0 0 auto;display:flex;align-items:center;gap:8px;flex-wrap:wrap}
.pvw-bar .vbtn{height:44px;min-width:44px;padding:0 14px;border-radius:9px;
  background:var(--card);border:1px solid var(--line);color:var(--ink);
  font-size:16px;display:inline-flex;align-items:center;gap:6px;cursor:pointer}
.pvw-bar .vbtn:hover{border-color:var(--ink3)}
.pvw-bar .vbtn:disabled{opacity:.38;cursor:default}
.pvw-bar .vbtn.wide{padding:0 18px;font-weight:500}
.pvw-bar .pg{margin-left:auto;font-family:var(--mono);font-size:15px;color:var(--ink2)}
.pvw-bar .pg b{color:var(--ink);font-weight:600}
.pvw-stage{flex:1 1 auto;min-height:0;overflow:auto;background:#525659;
  border:1px solid var(--line);border-radius:10px;display:flex;
  align-items:flex-start;justify-content:center;padding:14px;
  /* 放大以后要能抓着图拖 —— 见下面 showSheetViewer 里的拖动实现。
     touch-action:none 是为了让指针事件(鼠标/触屏)都归我们处理,
     不然浏览器会自己先滚一下,图就跟着手跑偏。 */
  cursor:grab;touch-action:none}
.pvw-stage.dragging{cursor:grabbing}
.pvw-stage img{user-select:none;-webkit-user-drag:none}
.pvw-hint{font-size:13px;color:var(--ink3);margin-left:4px}
/* 双页对开。两页并排,像谱子翻开着 ——
   梁Sir 要的:「增加一个显示双页」。整页对整页,左右对照着看。
   ⚠ min-width:0 不能省(见 .pvw 那条):双页放大后两页加起来两三千 px 宽,
     如果让 flex 链跟着长,.pvw-stage 本身会变成 2874px 宽,
     overflow:auto 就永远不会触发 —— 右页直接跑到屏幕外够不着,
     工具条也跟着被撑出屏幕(这就是「放大一点按钮不见了」的真正成因)。
     加上 min-width:0 之后 stage 宽度被钉死在屏幕内,超出部分才真的能滚。 */
/* ⚠ justify-content 必须写 safe center,不能只写 center。
   双页放大之后,两页加起来比舞台宽。这时候 flex 居中会把内容往**两边**推,
   左页有一截跑到舞台左边外面(实测起点 -492px)——
   而滚动容器只能往右滚,负方向那一截**永远够不着**,
   学员看到的就是左页被切掉一截,而且怎么拖都拖不出来。
   safe 的意思是:装得下就居中(好看),装不下就靠左(够得着)。 */
.pvw-pages{display:flex;gap:18px;align-items:flex-start;
  justify-content:center;justify-content:safe center;min-width:0}
.pvw-msg{color:var(--ink2);font-size:16px;align-self:center;text-align:center;
  line-height:1.9;padding:20px}
.pvw img{display:block;max-width:100%;height:auto;background:#fff;
  box-shadow:0 2px 14px rgba(0,0,0,.22);border-radius:2px}
/* ⚠ max-width:none + flex:0 0 auto 不能省。
   缩放是靠改 CSS 宽度做的,要是留着 max-width:100%,图片会被夹回舞台宽度 ——
   「放大一点」点了没反应:元素在、尺寸也在,只有学员看得出来。
   flex:0 0 auto 同理:双页放大后两页加起来比舞台宽,不许被 flex 压扁。 */
.pvw-pages img{max-width:none;flex:0 0 auto}
.prog{height:3px;background:var(--line);border-radius:2px;margin-bottom:20px;overflow:hidden}
.prog i{display:block;height:100%;background:var(--ink);border-radius:2px;
  transition:width .35s ease}
.opt{
  display:flex;align-items:center;gap:12px;width:100%;text-align:left;
  background:var(--card);border:1px solid var(--line);border-radius:var(--r);
  padding:12px 18px;margin-bottom:7px;min-height:54px;font-size:17px;
  transition:border-color .16s,background .16s;
}
.opt:hover{border-color:var(--ink3)}
.opt.on{border-color:var(--ink);font-weight:600}
.opt .mk{width:20px;flex:0 0 auto;color:var(--ink);display:flex}
.opt .n{margin-left:auto;font-family:var(--mono);font-size:12px;color:var(--ink3);font-weight:400}

/* ===================================================================
   交互反馈层
   全部按 40-70 岁学员做过降速降刺激处理:
   彩色粒子炸开 → 同色小扩散;圆形转场 400ms → 180ms;滚轮不做过冲回弹过度。
   =================================================================== */

/* ---- 01 完成反馈按钮:挤 → 勾 → 扩散(同色,不炸彩色粒子) ---- */
.pbtn{position:relative;overflow:visible}
@keyframes fbSqueeze{0%{transform:scale(1)}28%{transform:scale(.945)}
  66%{transform:scale(1.014)}100%{transform:scale(1)}}
@keyframes fbDraw{from{stroke-dashoffset:26}to{stroke-dashoffset:0}}
@keyframes fbRing{0%{transform:scale(.5);opacity:.5}
  100%{transform:scale(2.15);opacity:0}}
.icw{position:relative;display:flex;flex:0 0 auto}
.icw .fbov{position:absolute;inset:0;display:flex;align-items:center;
  justify-content:center;opacity:0;pointer-events:none}
.fbring{position:absolute;left:50%;top:50%;width:56px;height:56px;
  margin:-28px 0 0 -28px;border-radius:50%;border:2px solid var(--ink);
  pointer-events:none;opacity:0}
.pbtn.fb{animation:fbSqueeze .44s cubic-bezier(.34,1.56,.5,1);pointer-events:none}
.pbtn.fb .fbov{opacity:1}
.pbtn.fb .fbov .ic{stroke-dasharray:26;stroke-dashoffset:26;
  animation:fbDraw .38s cubic-bezier(.3,.9,.3,1) .13s forwards}
.pbtn.fb .fbring{animation:fbRing .66s cubic-bezier(.16,1,.3,1) .28s forwards}

/* ---- 02 数字纵向翻页(勾选联动的第二处响应) ---- */
.odo{position:relative;display:inline-block;overflow:hidden;vertical-align:bottom;
  line-height:1.24;font-variant-numeric:tabular-nums}
.odo-sz{visibility:hidden;display:block;height:1.24em}
.odo-in{position:absolute;left:0;top:0;right:0;
  transition:transform .62s cubic-bezier(.22,1,.28,1)}
.odo-in>span{display:block;height:1.24em;line-height:1.24em}
.odo-in.noanim{transition:none}

/* ---- 03 结果区平滑重排(勾选联动的第三处响应) ---- */
@keyframes rowIn{from{opacity:0;transform:translateY(9px)}to{opacity:1;transform:none}}
.row.enter{animation:rowIn .34s cubic-bezier(.16,1,.3,1) both}
@keyframes fpop{0%{transform:scale(1)}38%{transform:scale(1.04)}100%{transform:scale(1)}}
.fopt.pop{animation:fpop .36s cubic-bezier(.34,1.56,.5,1)}

/* ---- 04 难度吸附条:拖 → 两端吸在刻度 → 停稳了才换结果 ---- */
.snap-track{display:flex;align-items:flex-end;gap:3px;height:40px;
  cursor:pointer;touch-action:none;user-select:none;-webkit-user-select:none}
.snap-t{flex:1;height:26px;border-radius:3px;background:var(--line2);
  transition:background .16s,transform .16s}
.snap-t.in{background:var(--ink)}
.snap-t.edge{transform:scaleY(1.34)}
.snap-nums{display:flex;gap:3px;margin-top:5px}
.snap-n{flex:1;text-align:center;font-family:var(--mono);font-size:11px;color:var(--ink3);
  transition:color .16s}
.snap-n.on{color:var(--ink);font-weight:600}
.snap-bands{display:grid;grid-template-columns:repeat(3,1fr);gap:4px;margin-top:9px}
.snap-b{min-height:46px;padding:7px 4px;border-radius:6px;background:var(--card);
  border:1px solid var(--line);font-size:12px;color:var(--ink2);line-height:1.25;
  transition:border-color .16s,color .16s}
.snap-b:hover{border-color:var(--ink3)}
.snap-b.on{border-color:var(--ink);color:var(--ink);font-weight:600}
.snap-b em{display:block;font-style:normal;height:3px;border-radius:2px;margin:0 auto 5px}
.snap-b i{display:block;font-style:normal;font-family:var(--mono);font-size:11px;
  color:var(--ink3);margin-top:2px}
.snap-out{margin-top:11px;padding:11px 13px;background:var(--card);border-radius:8px;
  border:1px solid var(--line);font-size:15px;line-height:1.55}
.snap-out b{font-weight:600}

/* ---- 05 惯性滚轮 ---- */
.wheel{position:relative;height:228px;overflow:hidden;cursor:grab;touch-action:none;
  user-select:none;-webkit-user-select:none;
  -webkit-mask-image:linear-gradient(to bottom,transparent,#000 26%,#000 74%,transparent);
          mask-image:linear-gradient(to bottom,transparent,#000 26%,#000 74%,transparent)}
.wheel:active{cursor:grabbing}
.wheel-in{position:absolute;left:0;right:0;will-change:transform}
.wheel-pad{height:88px}
.wheel-it{height:52px;display:flex;align-items:center;gap:12px;padding:0 14px;
  font-size:17px;color:var(--ink3);opacity:.5;transition:opacity .18s,color .18s}
.wheel-it.on{color:var(--ink);font-weight:600;opacity:1}
.wheel-it i{width:6px;height:6px;border-radius:50%;background:transparent;flex:0 0 auto}
.wheel-it.on i{background:var(--ink)}
.wheel-it .n{margin-left:auto;font-family:var(--mono);font-size:12px;font-weight:400;
  color:var(--ink3)}
.wheel-mk{position:absolute;left:0;right:0;top:50%;height:52px;margin-top:-26px;
  border-top:1px solid var(--line2);border-bottom:1px solid var(--line2);pointer-events:none}

/* 歌手:首字母吸顶分组 + 快速跳转条 */
.azbar{display:flex;flex-wrap:wrap;gap:3px;margin-bottom:9px}
.azbar button{min-width:31px;height:32px;padding:0 5px;border-radius:6px;
  border:1px solid var(--line);font-size:12px;color:var(--ink2);background:var(--card);
  transition:border-color .16s}
.azbar button:hover{border-color:var(--ink);color:var(--ink)}
.sgrp{position:sticky;top:0;z-index:2;background:var(--bg);padding:12px 0 5px;
  font-family:var(--mono);font-size:12px;color:var(--ink3)}
.sfav{display:flex;flex-wrap:wrap;gap:7px;margin-bottom:14px}
.sfav .tag{font-size:13px;padding:7px 12px;min-height:38px;display:inline-flex;
  align-items:center}

/* ---- 07 导航圆形扩散转场(降速版 170ms) ---- */

/* toast */
#toast{
  position:fixed;left:50%;bottom:40px;transform:translateX(-50%);
  background:var(--ink);color:#fff;padding:14px 28px;border-radius:26px;
  font-size:16px;z-index:200;display:none;
}
/* ===================================================================
   手机 / 平板适配（2026-10-20）
   -------------------------------------------------------------------
   【为什么现在做，不是「以后优化」】
   梁Sir 的学员 40-70 岁，手机是**主设备**。这个站之前只有 1100px 一个断点，
   `.browse` 的 236px 侧栏是写死的，`.row` 是 5 列固定网格
   （44 + 96 + 92 + 96 + 4×16 = 392px 光固定列就超了）。
   在 375px 的屏幕上：侧栏 236 + 结果区最小 392 = 628px > 屏宽 →
   **整页横向溢出**，学员看到的是一片挤在一起的字，横向拖都拖不到头。

   【三档】
     ≤1024px  侧栏改成**抽屉**（点「筛选」拉出来），结果区占满宽度
     ≤600px   顶部导航改成**底部导航**（拇指够得着），字号/点按区按中老年放大
   【字号】正文 17→18px，次要 13/15→14/16px，可点区一律 ≥44px
   =================================================================== */

/* ---- 抽屉的零件（桌面上一律不显示） ---- */
.side-toggle{display:none;align-items:center;gap:8px;min-height:44px;
  padding:0 16px;border:1px solid var(--line2);border-radius:var(--r);
  font-size:16px;color:var(--ink);background:var(--card);white-space:nowrap}
.side-toggle:hover{border-color:var(--ink)}
.res-hd .side-toggle{margin-left:auto}
.side-mask{display:none;position:fixed;inset:0;background:rgba(26,26,26,.34);z-index:110}
.side-mask.show{display:block}
.side-close{display:none}

@media (max-width:1024px){
  /* ---- 侧栏 → 抽屉 ---- */
  .browse{grid-template-columns:1fr;gap:18px;padding:18px 18px 76px}
  .side{
    position:fixed;left:0;top:0;bottom:0;width:min(88vw,340px);
    max-height:none;z-index:120;margin:0;
    background:var(--bg);border-right:1px solid var(--line);
    padding:14px 16px 40px;
    transform:translateX(-103%);
    transition:transform .22s cubic-bezier(.16,1,.3,1);
    overscroll-behavior:contain;
  }
  body.side-open .side{transform:none;box-shadow:0 6px 40px rgba(26,26,26,.18)}
  body.side-open{overflow:hidden}
  .side-toggle{display:inline-flex}
  .side-close{display:flex;align-items:center;gap:8px;width:100%;
    min-height:44px;margin-bottom:12px;padding-bottom:8px;
    border-bottom:1px solid var(--line);font-size:16px;color:var(--ink2)}
  .res-hd{flex-wrap:wrap;row-gap:10px}
  .res-hd h2{font-size:22px}
  /* ---- 顶栏在 768 上装不下(实测)----
     品牌 92 + 5 个导航 376 + 搜索框 300 + 间距 116 + 内边距 64 ≈ 894px。
     平板竖屏只有 768,多出来的 126px 把**整个页面**撑到 894 ——
     浏览器再把视口按 894 排版,平板上等于把整页缩着看,字全糊了。
     这里:搜索框改成可伸缩(300 起,不够就缩),导航收紧,品牌缩一号。
     算下来固定部分 80+296+28=404,732-404=328 给搜索框,放得下。 */
  .hd-in{padding:0 18px;gap:14px}
  .brand{font-size:20px}
  .nav button{padding:9px 13px;font-size:15px}
  .hd-sp{flex:1 1 auto;min-width:0;gap:10px}
  .hd-search{flex:0 1 300px;width:auto;min-width:0}
  .hd-search.on{width:auto}
  /* 抽屉里手指头点的,点按区放大一号 */
  .fopt{min-height:46px;font-size:16px}
  .tierb{min-height:48px;font-size:17px}
  .fgrp>h4{font-size:15px}
  .fgrp>h4 .why.cancel{min-height:44px;font-size:15px}
  .fscroll{max-height:none;overflow:visible}
  .fscroll.short{max-height:none}
}

@media (max-width:600px){
  :root{--hd:60px}
  body{font-size:18px;padding-bottom:74px}
  .hd-in{padding:0 14px;gap:10px}
  .brand{font-size:19px}
  .brand em{display:none}
  .hd-sp{gap:0}
  .hd-search{width:auto;flex:1 1 auto;min-width:0}
  .hd-search.on{width:auto}
  .hd-search input{height:48px;font-size:17px}
  /* ⚠ .hd 上的 backdrop-filter 必须去掉:
     backdrop-filter 会给 .hd 造一个 containing block，里面的 position:fixed
     就不再相对视口了 —— 底部导航会跑进顶栏里。手机上用纯色底,不做毛玻璃。 */
  .hd{backdrop-filter:none;background:var(--bg)}
  /* ---- 顶部导航 → 底部导航 ---- */
  .nav{position:fixed;left:0;right:0;bottom:0;z-index:80;
    display:grid;grid-template-columns:repeat(5,1fr);gap:0;
    background:rgba(247,246,243,.97);
    border-top:1px solid var(--line);
    padding:4px 2px calc(4px + env(safe-area-inset-bottom,0px))}
  .nav button{padding:9px 2px;font-size:16px;min-height:54px;
    border-radius:8px;text-align:center;line-height:1.25}
  /* ---- 内容区 ---- */
  .browse{padding:14px 12px 20px;gap:14px}
  .hero{padding:0 12px}
  .sec{padding:0 12px}
  .note{padding:0 12px}
  .sec-hd h2{font-size:23px}
  .res-hd h2{font-size:21px}
  .res-hd .q{font-size:14px}
  /* ---- 列表行:5 列网格 → 两行 ---- */
  .row{display:grid;grid-template-columns:38px minmax(0,1fr) auto;
    grid-template-areas:"kk mid cl" "kk ck pr";
    column-gap:10px;row-gap:3px;align-items:center;padding:13px 14px}
  .row .kk{grid-area:kk;align-self:start;margin-top:3px}
  .row .mid{grid-area:mid}
  .row .ck{grid-area:ck;justify-self:start;font-size:15px;color:var(--ink2)}
  .row .cl{grid-area:cl;justify-self:end}
  .row .pr{grid-area:pr;justify-self:end}
  /* ---- 两列/三列的网格 → 一列(两列) ---- */
  .grid{grid-template-columns:1fr}
  .entries{grid-template-columns:repeat(2,1fr)}
  .stats{grid-template-columns:1fr}
  .hf{flex:1 1 calc((100% - 20px)/2)}
  /* ---- 详情卡 ---- */
  .detail-in{grid-template-columns:1fr;padding:16px 16px 18px}
  .dl h3{font-size:21px}
  .dl .by{font-size:16px}
  .dl .kv{grid-template-columns:84px 1fr;font-size:16px}
  .ch-row{grid-template-columns:44px 44px 1fr;gap:8px}
  .lvh-row{grid-template-columns:44px 48px 1fr;gap:8px}
  /* ---- 弹层:手机上全屏 ---- */
  /* ⚠ align-items 必须跟着改成 stretch:
     基座是 align-items:center,弹层高度到 100vh 时再居中一次,
     顶部会被推到视口外头一点点(手机上表现为标题栏被切)。 */
  .mask{padding:0;align-items:stretch;justify-content:stretch}
  .sheet{max-width:none;border-radius:0;max-height:100vh;height:100vh}
  .sheet-hd{padding:22px 18px 0}
  .sheet-hd h3{font-size:23px}
  .sheet-bd{padding:0 18px}
  .sheet-ft{padding:14px 18px calc(16px + env(safe-area-inset-bottom,0px))}
  /* ---- 谱面弹层(wide)要更紧凑 ----
     手机上读谱,屏幕就是本钱。实测:667px 的屏,标题栏 + 工具条(5 个按钮
     375 宽放不下,折成两行)+ 底部按钮条 一共吃掉 290px,谱子区只剩 377px
     (56%)—— 谱子看得比巴掌还小。
     这里把边距收掉(标题栏 20→10、底部 30→18、内容 14→8),谱子区回到 60%+。
     ⚠ 点按区一律还是 ≥44px(中老年学员手指头粗),收的是**留白**不是按钮。 */
  .sheet.wide .sheet-hd{padding:10px 16px 0}
  .sheet.wide .sheet-hd h3{font-size:19px}
  .sheet.wide .sheet-bd{padding:8px 16px 0}
  .sheet.wide .sheet-ft{padding:8px 16px calc(10px + env(safe-area-inset-bottom,0px))}
  /* 底部两个按钮在 375 上各占 170px 放不下,改成平分宽度 */
  .sheet.wide .sheet-ft .pbtn{flex:1 1 0;min-width:0;width:auto;height:48px}
  .pvw{gap:6px}
  .pvw-bar{gap:6px}
  /* ---- 字号与点按区 ---- */
  .chip{font-size:15px;min-height:44px}
  .big-btn{min-height:52px;font-size:17px}
  .pbtn{height:54px;font-size:17px}
  .entry .t{font-size:18px}
  .card .t{font-size:18px}
}

/* ===================================================================
   三个端的切换器(只有 15268345650 看得到)
   =================================================================== */
.gw-sw{display:inline-flex;align-items:center;gap:2px;padding:3px;
  border:1px solid var(--line2);border-radius:10px;background:var(--card);
  flex:0 0 auto}
/* ⚠ 这一条不能省:.gw-sw 的 display:inline-flex **会盖掉** [hidden] 的
   浏览器默认规则(作者样式 > UA 样式,特异度还一样)⇒ 不写这条,
   容器上 hidden=true 也照样显示出来 —— 非本人账号就看见切换器了。 */
.gw-sw[hidden]{display:none}
.gw-sw-i{display:inline-flex;align-items:center;height:30px;padding:0 12px;
  border-radius:8px;font-size:13px;line-height:1;color:var(--ink2);
  text-decoration:none;white-space:nowrap;transition:background .16s,color .16s}
.gw-sw-i:hover{background:var(--card2);color:var(--ink)}
.gw-sw-i.on{background:var(--solid);color:#fff;font-weight:500}

/* ⚠ 学员端顶栏在 1024 以下就装不下了(base.css 上头那段注释写了实测:
   品牌 + 5 个导航 + 搜索框 ≈ 894px)⇒ 窄屏上**不显示**这个切换器。
   它是给梁Sir 自己用的跳转,学员手机上不需要,挤坏了反而碍事。 */
@media (max-width:1024px){ .hd .gw-sw{display:none} }
