/* ===== tokens（spec §4，勿改名） ===== */
:root {
  --bg: #0089FF;
  --glass: rgba(255,255,255,.62);
  --glass-stroke: rgba(255,255,255,.85);
  --card: #FFFFFF;
  --blue: #3B82F6;
  --mint: #34D399;
  --ink: #1E293B;
  --ink-2: #64748B;
  --shadow-soft: 0 12px 40px rgba(15,23,42,.10), 0 2px 8px rgba(15,23,42,.06);
  --shadow-float: 0 18px 50px rgba(15,23,42,.14), 0 4px 12px rgba(15,23,42,.08);
  --inset: 12px;
  --radius-stage: 26px;
  --radius-card: 20px;
}
* { box-sizing: border-box; }
html, body { height: 100%; }
body {
  margin: 0;
  font: 14px/1.5 -apple-system, "PingFang SC", "Noto Sans SC", "Microsoft YaHei", system-ui, sans-serif;
  background: var(--bg);
  color: var(--ink);
  overflow: hidden;
}

/* ===== 舞台与外框（spec §4：纯视觉边界，禁硬件仿真） ===== */
.stage {
  position: fixed; inset: var(--inset);
  border-radius: var(--radius-stage);
  overflow: hidden;
  background: #E8EDF1;
  border: 1px solid rgba(15,23,42,.12);   /* no-JS 回退线框 */
  box-shadow: var(--shadow-soft);
}
.stage.frame-ready {
  border-color: transparent; /* JS 弧线 SVG 接管 */
  clip-path: var(--frame-clip, none); /* 裁剪与弧线同形：地图只出现在曲线内，不从边中段月牙带漏出 */
}
#frame {
  position: fixed; inset: var(--inset);
  width: calc(100% - 2 * var(--inset));
  height: calc(100% - 2 * var(--inset));
  pointer-events: none; z-index: 900;
  filter: drop-shadow(0 24px 60px rgba(15,23,42,.10));
}
#frame path { fill: none; stroke: rgba(15,23,42,.30); stroke-width: 1.5; }

#map { position: absolute; inset: 0; background: #E8EDF1; }
.leaflet-container { font: inherit; background: #E8EDF1; }
.leaflet-control-attribution {
  background: rgba(255,255,255,.55) !important;
  backdrop-filter: blur(6px); -webkit-backdrop-filter: blur(6px);
  font-size: 9px; color: var(--ink-2);
  border-radius: 8px 0 0 0; padding: 1px 6px;
}
.leaflet-control-attribution a { color: var(--ink-2); }

/* ===== 玻璃通用 ===== */
.glass {
  background: var(--glass);
  backdrop-filter: blur(18px); -webkit-backdrop-filter: blur(18px);
  border: 1px solid var(--glass-stroke);
  box-shadow: var(--shadow-soft);
}

/* ===== 顶部状态胶囊（spec §5.1） ===== */
.capsule {
  position: absolute; top: calc(14px + env(safe-area-inset-top, 0px));
  left: 50%; transform: translateX(-50%);
  z-index: 500; display: flex; align-items: center; gap: 10px;
  padding: 8px 16px 8px 8px; border-radius: 999px;
  max-width: calc(100% - 88px);
}
/* 电量描边环（替代 ⚡ 胶囊）：弧自罗盘 220°（左下）顺时针经顶到 120°（右下），
   缺口 100° 落在正下方；电量 0–100 ↔ 弧的涂满比例，<20% 转橘红。
   几何常量与 assets/js/ring.js 同源：r=17 → 周长 106.81，260° 弧长 77.15 */
.avatar-ring { position: relative; width: 40px; height: 40px; flex: none; }
.avatar-ring .avatar {
  position: absolute; inset: 6px; width: 28px; height: 28px;
  border-radius: 50%; object-fit: cover;
  border: none; box-shadow: 0 1px 4px rgba(15,23,42,.25);
}
.ring { position: absolute; inset: 0; width: 100%; height: 100%; }
.ring circle { fill: none; stroke-width: 3; }
.ring-track { stroke: rgba(100,116,139,.30); }
.ring-gap { stroke: #111; stroke-dashoffset: 29.67; } /* 底部 120°→220° 缺口段涂黑；揭幕前收起不亮出 */
/* 揭幕「一笔画环」：120° 起笔画黑过正下（linear 恒定角速），220° 黑绿交界接笔画绿到电量处。
   两段时长由 ring.js revealTiming 按弧长占比分配（--gap-dur/--fill-dur），接笔处速度连续 */
body.intro-done .ring-gap {
  stroke-dashoffset: 0;
  animation: gap-in var(--gap-dur, 361ms) linear;
}
@keyframes gap-in {
  from { stroke-dashoffset: 29.67; } /* 100° 缺口段弧长 2π×17×100/360 */
  to   { stroke-dashoffset: 0; }
}
.ring-fill {
  stroke: #34D399; stroke-linecap: round;
  stroke-dashoffset: 77.15; /* 揭幕前保持空弧：不提前亮出完整电量，揭幕时从 0 动画到位 */
  transition: stroke-dashoffset .9s cubic-bezier(.33,1,.68,1), stroke .3s;
}
/* 揭幕（loading 蓝幕移除）后才落到目标值并套挂载动画：动画起点=空弧，与揭幕前同态，无跳变。
   delay=黑段时长（接笔）；both=延迟期间保持 from 隐藏态；近线性起笔保证接笔速度连续，尾部缓收 */
body.intro-done .ring-fill {
  stroke-dashoffset: var(--off, 77.15);
  animation: ring-in var(--fill-dur, 939ms) cubic-bezier(.25,.25,.25,1) var(--gap-dur, 361ms) both;
}
.ring-fill.ring-low { stroke: #F97316; }
@keyframes ring-in {
  from { stroke-dashoffset: 77.15; } /* 0%：整段弧收起 */
  to   { stroke-dashoffset: var(--off, 77.15); }
}
@media (prefers-reduced-motion: reduce) { .ring-fill, .ring-gap, .np-swap { animation: none; transition: none; } }
.avatar-fallback {
  display: flex; align-items: center; justify-content: center;
  font-weight: 700; color: #fff;
  background: linear-gradient(135deg, var(--blue), var(--mint));
}
.status { min-width: 0; transform: translateY(3px); } /* 两行文本视觉重心偏上：3px 光学下移 */
.status strong { font-size: 13px; line-height: 1.3; white-space: nowrap; }
.status small { font-size: 11px; color: var(--ink-2); white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
/* 状态两视图（歌曲/Last Seen）共用同一布局层：切换即换节点 */
.np-swap { display: flex; flex-direction: column; min-width: 0; }
body.intro-done .np-swap { animation: status-swap .28s cubic-bezier(.33,1,.68,1); }
@keyframes status-swap {
  from { opacity: 0; transform: translateY(6px); }
  to   { opacity: 1; transform: none; }
}

/* ===== 右侧控制列（spec §5.2） ===== */
.controls {
  position: absolute; right: 14px; top: 50%; transform: translateY(-50%);
  /* 850：压过 Leaflet 全部图层（marker 600 / tooltip 650 / popup 700）与提示条(600)/胶囊/sheet(500)，
     图钉和气泡不得盖住按钮。更高的只剩 #frame(900)/#skeleton(950)/#intro(1200)——
     三者是装饰边线与开场加载幕，属整屏过渡层，不该被控件浮在上面 */
  z-index: 850; display: flex; flex-direction: column; gap: 10px;
}
.ctl {
  width: 42px; height: 42px; border-radius: 999px;
  border: 1px solid var(--glass-stroke);
  background: rgba(255,255,255,.75);
  backdrop-filter: blur(18px); -webkit-backdrop-filter: blur(18px);
  box-shadow: var(--shadow-soft);
  display: flex; align-items: center; justify-content: center;
  font-size: 18px; color: var(--ink); cursor: pointer; padding: 0;
}
button.ctl { font: inherit; }
.ctl:active { transform: scale(.94); }

/* 保留 you-pop 关键帧：.flight-end.flight-pop（🛫/🛬 起降点登场）仍在引用 */
@keyframes you-pop {
  0% { transform: scale(0); }
  60% { transform: scale(1.15); }
  100% { transform: scale(1); }
}

/* ===== ✈️ 飞行视图（连线按钮上方的开关 + sheet 切换为航班数据） ===== */
.flight-track { animation: flight-in .8s ease-out both; }
@keyframes flight-in { from { opacity: 0; } to { opacity: 1; } }
.flight-end { /* 🛫/🛬 起降点：白底圆形徽章 */
  width: 40px; height: 40px; border-radius: 50%;
  background: rgba(255,255,255,.92); border: 2px solid var(--blue);
  box-shadow: 0 6px 16px rgba(15, 23, 42, .18);
  display: flex; align-items: center; justify-content: center; font-size: 19px;
}
.flight-end.flight-pop { animation: you-pop .6s cubic-bezier(.34, 1.56, .64, 1) both; }
.sheet-flight { padding: 12px 16px 10px; }
.flight-head { /* 标题区：呼号一行、日期另起一行居左，多航班时的下拉居右 */
  display: flex; align-items: center; justify-content: space-between; gap: 10px;
  padding-bottom: 8px; border-bottom: 1px dashed #E2E8F0; margin-bottom: 10px;
}
.flight-title { min-width: 0; } /* 左侧两行标题，允许收缩避免顶开下拉 */
.flight-date { display: block; font-size: 12px; font-weight: 600; color: var(--ink-2); white-space: nowrap; padding-top: 2px; }
.flight-head-actions { display: flex; align-items: center; gap: 6px; flex-shrink: 0; }
/* 「全部轨迹」切换：与 .flight-trigger 同款玻璃药丸；开着时蓝底（同控制列开关高亮语言） */
.flight-all-btn {
  font: inherit; font-size: 12px; font-weight: 600; color: var(--ink);
  background: var(--glass); backdrop-filter: blur(18px); -webkit-backdrop-filter: blur(18px);
  border: 1px solid var(--glass-stroke); border-radius: 999px;
  padding: 4px 10px; cursor: pointer; white-space: nowrap;
}
.flight-all-btn.on { background: var(--blue); border-color: var(--blue); color: #fff; }
.flight-all-btn:disabled { opacity: .55; cursor: default; }
/* 自定义航班下拉：计数式触发药丸 + 向上弹出的玻璃菜单（sheet 贴屏底，向下无空间） */
/* 菜单上沿伸出 sheet 顶边，而高度回弹动画会给 sheet 挂内联 overflow:hidden（入场后约 1s 内），
   期间菜单被裁剪、点击穿透到地图——菜单在场时强制不裁剪（溢出内容落在视口外，无视觉影响） */
.sheet:has(.flight-menu) { overflow: visible !important; z-index: 860; } /* 须压过回弹动画挂的内联 hidden；z 提到 860>控制列(850)：sheet 是层叠上下文，菜单要盖过右侧按钮必须整体提层（菜单收起即回落 500） */
.flight-picker { position: relative; flex-shrink: 0; }
.flight-trigger {
  font: inherit; font-size: 12px; font-weight: 600; color: var(--ink);
  background: var(--glass); backdrop-filter: blur(18px); -webkit-backdrop-filter: blur(18px);
  border: 1px solid var(--glass-stroke); border-radius: 999px;
  padding: 4px 10px; cursor: pointer; white-space: nowrap;
}
.flight-trigger[aria-expanded="true"] { background: #fff; }
.flight-menu {
  position: absolute; right: 0; bottom: calc(100% + 8px); z-index: 860; /* 须压过右侧控制列(850)：菜单是瞬态弹层，展开期间不得被按钮遮挡/挡点击 */
  min-width: 210px; max-height: min(46vh, 300px); overflow-y: auto; padding: 4px; /* 航班多时限高滚动，不再向上顶进控制列 */
  background: var(--glass); backdrop-filter: blur(18px); -webkit-backdrop-filter: blur(18px);
  border: 1px solid var(--glass-stroke); border-radius: 14px;
  box-shadow: 0 8px 24px rgba(30, 41, 59, .16);
  animation: flight-menu-in .18s ease-out;
}
@keyframes flight-menu-in { from { opacity: 0; transform: translateY(6px); } }
@media (prefers-reduced-motion: reduce) { .flight-menu { animation: none; } }
.flight-opt {
  display: block; width: 100%; min-height: 40px; text-align: left; font: inherit;
  border: 0; background: none; border-radius: 10px; padding: 7px 10px; cursor: pointer;
}
.flight-opt.on { background: rgba(255,255,255,.78); }
.flight-opt-date { display: block; font-size: 13px; font-weight: 700; color: var(--ink); }
.flight-opt-sub { display: block; font-size: 11px; color: var(--ink-2); margin-top: 1px; }
.flight-empty { padding: 10px 0 6px; color: var(--ink-2); font-size: 12px; }

/* ===== 底部抽屉（spec §5.4） ===== */
.sheet {
  position: absolute; left: 50%; transform: translateX(-50%);
  bottom: calc(14px + env(safe-area-inset-bottom, 0px));
  z-index: 500; width: min(calc(100% - 20px), 480px);
  background: var(--card); border-radius: var(--radius-card);
  box-shadow: var(--shadow-float); padding: 14px 16px;
}
.sheet-grid { display: grid; grid-template-columns: 1fr 1fr; gap: 10px 14px; }
.stat-num { display: block; font-size: 17px; font-weight: 700; }
.stat-num small { font-size: 12px; font-weight: 600; }
.stat-cap { display: block; font-size: 11px; color: var(--ink-2); margin-top: 2px; }
.stat-mood { grid-column: 1 / -1; border-top: 1px dashed #E2E8F0; padding-top: 10px; }

/* 拥挤让位：作用于 Leaflet 图标根节点（内层缩放不干扰定位 transform） */
.leaflet-marker-icon { transition: opacity .25s ease, visibility .25s; }

/* ===== 提示 / 骨架 / noscript（spec §10） ===== */
#tile-toast {
  position: absolute; top: 66px; left: 50%; transform: translateX(-50%);
  z-index: 600; border-radius: 999px; padding: 6px 14px;
  font-size: 12px; color: var(--ink-2);
}
#skeleton {
  position: absolute; inset: 0; z-index: 950;
  background: linear-gradient(110deg, #EEF2F5 30%, #F7FAFC 45%, #EEF2F5 60%);
  background-size: 200% 100%;
  animation: shimmer 1.4s linear infinite;
}
@keyframes shimmer { to { background-position: -200% 0; } }
.noscript { position: fixed; left: 0; right: 0; bottom: 20px; text-align: center; color: var(--ink-2); }

/* ===== 开场加载动画（intro） ===== */
/* 全屏覆盖层，层级须高于骨架(950)与外框(900)，否则蓝幕被骨架盖住只能"渐显"；
   蓝幕从屏幕正中一点向四周扩散铺满；数据就绪后头像 pop up，蓝幕再收缩回中心入头像
   —— 始于一点、终于一点。stage 与视口同心（inset 对称），头像槽即页面正中 */
#intro {
  position: fixed; inset: 0; z-index: 1200; overflow: hidden; /* 裁掉超出视口的 200vmax 圆幕 */
  /* JS 兜底：若模块加载失败未移除 intro，12s 后自动淡出避免卡蓝屏 */
  animation: intro-failsafe 1s ease 12s forwards;
}
@keyframes intro-failsafe { to { opacity: 0; visibility: hidden; } }
/* WebKit（Safari）对 clip-path 圆形的 % 半径插值不可靠（老版本还需 -webkit- 前缀），
   蓝幕扩散/收缩会退化为直接呈现最终态。改用圆形元素 + transform: scale() 驱动
   —— transform 补间全引擎稳定，视觉等价：始于中心一点、终于中心一点 */
.intro-veil {
  position: absolute; left: 50%; top: 50%;
  width: 200vmax; height: 200vmax;      /* 半径 100vmax ≥ 视口对角线之半，铺满任意屏 */
  margin: -100vmax 0 0 -100vmax;        /* 圆心即 #intro 正中（头像槽），transform 留给动画用 */
  background: var(--bg); border-radius: 50%;
  transform: scale(1);                  /* 待命态铺满；揭幕收缩（隐式 from）也读它 */
}
/* 扩散不由样式表直接起播：CSS 动画在首次样式解析即上表走时，而 Safari（WebKit）首帧
   常晚于该时刻（同步脚本阻塞/慢网），bloom 会在首帧前放完——蓝幕"直接就有"。
   改由 JS 双 rAF 加 .go 起播，起始时刻锚定真实渲染帧，各端必可见 */
#intro.go .intro-veil { animation: intro-bloom 1.1s cubic-bezier(.4, 0, .2, 1) both; }
@keyframes intro-bloom {
  from { transform: scale(0); }
  to   { transform: scale(1); }
}
/* 收得从容：缓起蓄势 → 中段加速掠过 → 落回中心时缓收 */
#intro.reveal .intro-veil { animation: intro-collapse 1.2s cubic-bezier(.7, 0, .2, 1) forwards; }
@keyframes intro-collapse { to { transform: scale(0); } }

.intro-anchor {
  position: absolute; left: 50%; top: 50%;
  width: 56px; height: 56px;            /* 头像槽：揭幕后地图头像点无缝接棒 */
  transform: translate(-50%, -50%);
  color: #fff; text-align: center; user-select: none;
}
.intro-welcome, .intro-title, .intro-loading {
  position: absolute; left: 50%; transform: translateX(-50%);
  white-space: nowrap; margin: 0;
}
.intro-welcome {
  bottom: calc(100% + 16px); font-size: 17px; font-weight: 500; opacity: .92;
  animation: intro-rise .5s cubic-bezier(.25, 1.2, .45, 1) .65s both;
}
.intro-title {
  top: calc(100% + 12px); font-size: 30px; font-weight: 800;
  text-shadow: 0 2px 14px rgba(0, 60, 120, .35);
  animation: intro-rise .5s cubic-bezier(.25, 1.2, .45, 1) .8s both;
}
.intro-loading {
  top: calc(100% + 60px); font-size: 12px; font-weight: 500;
  animation:
    intro-rise .5s cubic-bezier(.25, 1.2, .45, 1) .95s both,
    intro-blink 1.1s ease-in-out 1.8s infinite;
}
@keyframes intro-rise { from { transform: translate(-50%, 18px); opacity: 0; } }
@keyframes intro-blink { 0%, 100% { opacity: .85; } 50% { opacity: .15; } }
/* 揭幕收缩时文字与蓝幕同曲线渐隐（同 intro-collapse 1.2s/缓动），不孤零零挂到移除才消失；
   头像不参与，保留接棒地图点。后置动画同属性优先，渐隐可压过 blink 的持续闪烁 */
#intro.reveal .intro-welcome {
  animation: intro-rise .5s cubic-bezier(.25, 1.2, .45, 1) .65s both,
             intro-fade 1.2s cubic-bezier(.7, 0, .2, 1) forwards;
}
#intro.reveal .intro-title {
  animation: intro-rise .5s cubic-bezier(.25, 1.2, .45, 1) .8s both,
             intro-fade 1.2s cubic-bezier(.7, 0, .2, 1) forwards;
}
#intro.reveal .intro-loading {
  animation:
    intro-rise .5s cubic-bezier(.25, 1.2, .45, 1) .95s both,
    intro-blink 1.1s ease-in-out 1.8s infinite,
    intro-fade 1.2s cubic-bezier(.7, 0, .2, 1) forwards;
}
@keyframes intro-fade { to { opacity: 0; } }

.intro-avatar { width: 100%; height: 100%; border-radius: 50%; transform: scale(0); }
#intro.pop .intro-avatar { animation: intro-pop .45s cubic-bezier(.34, 1.56, .64, 1) forwards; }
@keyframes intro-pop { from { transform: scale(0); } to { transform: scale(1); } }
/* inset 6 / 44px / 白圈：移除 intro 时与地图头像点逐像素对齐 */
.intro-avatar img {
  position: absolute; inset: 6px; width: 44px; height: 44px;
  border-radius: 50%; object-fit: cover; display: block;
  border: 2.5px solid #fff; box-shadow: 0 4px 12px rgba(15, 23, 42, .25);
}
.intro-avatar.intro-avatar-fb {
  display: flex; align-items: center; justify-content: center;
  font-size: 22px; font-weight: 700;
  background: linear-gradient(135deg, var(--blue), var(--mint));
  border: 2.5px solid #fff; box-shadow: 0 4px 12px rgba(15, 23, 42, .25);
}

/* 外层骨架遮罩：等蓝幕滑入落定（JS 加 .out）后淡出，衔接不再生硬 */
#skeleton.out { animation: skeleton-fade .6s ease forwards; }
@keyframes skeleton-fade { to { opacity: 0; visibility: hidden; } }

@media (prefers-reduced-motion: reduce) {
  #intro.go .intro-veil { animation: none; } /* 跳过扩散，直接铺满 */
  .intro-welcome, .intro-title, .intro-loading { animation: none; }
  #intro.reveal .intro-welcome, #intro.reveal .intro-title, #intro.reveal .intro-loading { animation: none; } /* 揭幕渐隐一并停（JS 实际不会走到，保险） */
  #intro.pop .intro-avatar, #intro.reveal .intro-veil { animation-duration: .01s; }
  .flight-track, .flight-end.flight-pop { animation: none; } /* 飞行轨迹浮现与起降点 pop 直落终态 */
}

@supports (padding: env(safe-area-inset-top, 0px)) {
  .capsule { max-width: calc(100% - 76px); }
}

/* ===== 三端断点（spec §3） ===== */
@media (min-width: 768px) {
  .capsule { padding: 10px 20px 10px 10px; }
  .sheet { width: 560px; }
}
/* 手机端高面板避让：飞行 sheet 内容高，居中底卡会顶进右侧控制列（42px 钮 + 14px 边距）下方
   被按钮盖住。改为左置（与桌面同款锚法），右侧让出 56px 控制列宽度，任何屏高/内容增长都不再重叠。
   ≥768px 居中 560px 卡两侧留白足够；≥1280px 桌面本就左置 */
@media (max-width: 767.98px) {
  .sheet-flight {
    left: 10px; transform: none; width: calc(100% - 20px - 56px);
  }
}
@media (min-width: 1280px) {
  :root { --inset: 16px; }
  .capsule { top: 20px; }
  .controls { right: 20px; }
  .sheet { left: 28px; transform: none; bottom: 28px; width: 380px; }
}

#tile-toast.glass { border-radius: 999px; }
