/* ============================================================
   我的个人 VLOG —— 分层复刻（复古手账 + 复古电视）
   场景 1920x1440（4:3），整体等比缩放居中
   ============================================================ */

:root {
  --design-w: 1920px;
  --design-h: 1440px;
  --cream: #f6f1e3;
  --ink: #3a2f2a;
}

* { box-sizing: border-box; margin: 0; padding: 0; }
html, body { height: 100%; }

body {
  font-family: "Segoe UI", "Microsoft YaHei", system-ui, sans-serif;
  color: var(--ink);
  background-color: var(--cream);
  overflow: hidden;
}

.page {
  position: fixed;
  inset: 0;
}

/* 波点背景：整屏一张铺满（cover 等比放大），不随场景缩放，无拼接分层 */
.backdrop {
  position: fixed;
  inset: 0;
  z-index: 0;
  background: url("../assets/img/bg.jpg") center / cover no-repeat;
}

/* ---------------- 场景：1920x1440，由 JS 精确缩放居中 ---------------- */
.scene {
  position: absolute;
  left: 0;
  top: 0;
  width: var(--design-w);
  height: var(--design-h);
  transform-origin: 0 0;
  /* 只做很短的整页淡入：长淡入会和下面各素材的错峰入场叠在一起，
     看起来像"整页慢慢亮起来"，错峰的层次感就糊掉了。 */
  animation: fadeIn .38s ease both;
}

.scene img {
  position: absolute;
  display: block;
  height: auto;
  pointer-events: none;
  -webkit-user-drag: none;
  user-select: none;
}

/* ============================================================
   素材「落位」入场 —— 每次翻页都会重放
   （外层壳换页走 iframe 的 location.replace = 整文档重载，CSS 动画自然重来）
   ------------------------------------------------------------
   机制：用 @starting-style 声明"首帧状态"，再由 transition 过渡到静止状态。

   为什么不用 animation：
     这些素材身上已经跑着 floatA / sway / zap / breathe 等持续动画，它们占用
     transform。入场若也用 animation 且 fill:both，会把 scale / translate 永久
     锁死 —— 第 4 页铁盒的 hover 抬起就会失效（这个坑 2026-09-11 踩过）。
     改用 transition 只碰 opacity / scale / translate / rotate，与那些动画的
     transform 天然隔离，所以下面一条已有 animation 都不用改。

   ⚠️ 给素材追加持续动画时，请继续用 transform / filter / box-shadow，
      不要用 animation 去动 opacity —— 会和这里的入场淡入抢同一个属性，
      动画优先级高于 transition，淡入会被整段吃掉。

   每个素材自己声明：
     --d          入场延迟
     --sx / --sy  起始位移（决定"从哪边来"）
     --sr         起始旋转
     --ss         起始缩放
   ============================================================ */
:root {
  --settle-dur: .78s;
  --settle-ease: cubic-bezier(.22, 1.02, .34, 1.1);
}

.content > * {
  transition:
    opacity   var(--settle-dur) ease               var(--d, 0s),
    scale     var(--settle-dur) var(--settle-ease) var(--d, 0s),
    translate var(--settle-dur) var(--settle-ease) var(--d, 0s),
    rotate    var(--settle-dur) var(--settle-ease) var(--d, 0s);
}
@starting-style {
  .content > * {
    opacity: 0;
    scale: var(--ss, .88);
    translate: var(--sx, 0px) var(--sy, -20px);
    rotate: var(--sr, -2deg);
  }
}

/* 成对图层（视频层 + 外壳贴图）必须一起动，否则入场时画面会从容器里滑出来。
   只给 translate、把 --ss 设为 1，是因为两者尺寸/中心都不同：
   若各自绕自身中心缩放，必然错位。 */
.tv-video, .tv { --ss: 1; --sr: 0deg; }

/* 带 hover 反馈的贴纸：scale / rotate 留给悬停，延迟必须为 0，否则悬停发涩。
   入场只走 opacity / translate；起始 scale 会在 opacity 还处于延迟期时就
   悄悄完成（那会儿还看不见），所以视觉上仍是干净的淡入 + 位移。 */
.txt.notes, .txt.title, .txt.vlog {
  transition:
    scale     .22s ease 0s,
    rotate    .22s ease 0s,
    opacity   var(--settle-dur) ease               var(--d, 0s),
    translate var(--settle-dur) var(--settle-ease) var(--d, 0s);
}

/* ---- 第一页入场编排（视线：左页 → 右页，上 → 下）---- */
.notebook       { --d: 0s;   --sy: 18px;  --ss: .982; --sr: 0deg; }
.txt.title      { --d: .10s; --sx: -16px; --sy: -14px;   --sr: -2.5deg; }
.txt.apple      { --d: .20s; --sy: -22px;                --sr: 3deg; }
.txt.vlog       { --d: .26s; --sx: -22px; --sy: 0px;     --sr: -2deg; }
.tv-video,
.tv             { --d: .32s; --sy: 24px; }
.txt.lightning  { --d: .42s; --sx: 18px;  --sy: -10px;   --sr: 4deg; }
.txt.notes      { --d: .50s; --sx: 16px;  --sy: -16px;   --sr: 2deg; }
.typewriter,
.typed          { --d: .58s; --sy: 26px;  --ss: .97; --sr: 0deg; }
.badge-btn      { --d: .68s; --sx: 22px;  --sy: 0px;   --ss: .8;  --sr: 4deg; }

/* ---------------- 全站统一 logo（Joe's Portfolio） ---------------- */
/* 用 position:fixed 走视口坐标，避开各页 .scene 的 letterbox（特别是 P1 的 4:3 画布），
   保证五页在任意视口下 logo 位置和大小完全一致。 */
.site-logo {
  position: fixed;
  /* left 152px：第3/4/5页左上角有 .back-chip（"← 返回上一页"，实占 x22~141.9 / y20~57，
     z-index 50 高于 logo 的 40），logo 原在 left:72 会被它压住左上角 70×9px。
     152 = 141.9 + 10px 间隙，横向彻底让开。
     top 24px：横向让开后就不必靠纵向躲 chip 了，上移是为了避开手账本 ——
     书的顶边随视口缩放会往上跑（1280×720 时到 y=99，1366×768 时 y=105.6），
     logo 底边若仍在 119.5 就会压到书壳皮革上。上移到 24 后底边 95.5，全尺寸都不碰书。
     改这两个值前请先量 chip 与 notebook 的矩形，别凭感觉。 */
  left: 152px;
  top: 24px;
  width: 340px;
  z-index: 40;          /* 高于 backdrop(0)、scene(1)，低于 back-chip(50)/vm-card(80) */
  pointer-events: auto;
  transition: transform .22s ease, filter .22s ease;
}
.site-logo:hover {
  transform: scale(1.04) rotate(-1.5deg);
  filter: drop-shadow(0 6px 14px rgba(60, 40, 20, .18));
}

.scene { z-index: 1; }

/* 内容整体放大：书+贴纸等比例放大约 12%，页面更饱满（坐标保持原布局不变） */
.content {
  position: absolute;
  inset: 0;
  transform: scale(1.12);
  transform-origin: 50% 50%;
}

/* ---------------- 手账本 ---------------- */
.notebook { left: 169px; top: 254px; width: 1622px; z-index: 1; }

/* ---------------- 复古电视：视频播放器在电视框之下 ---------------- */
.tv-video {
  position: absolute;
  left: 417px;
  top: 773px;
  width: 292px;
  height: 232px;
  z-index: 2;
}
.tv-video video {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  object-fit: cover; /* 画面铺满电视屏幕，不留黑边（左右裁切） */
  background: #000;
  border-radius: 24px;
  opacity: 0;
  transition: opacity .35s ease;
  pointer-events: auto;
}
.tv-video.has-video video { opacity: 1; }

.tv-placeholder {
  position: absolute;
  inset: 0;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: 12px;
  padding: 8px;
  text-align: center;
  pointer-events: none;
}
.play-btn {
  width: 62px;
  height: 62px;
  border-radius: 50%;
  border: none;
  background: rgba(173, 32, 32, 0.92);
  color: #fff;
  display: grid;
  place-items: center;
  cursor: pointer;
  pointer-events: auto;
  box-shadow: 0 6px 18px rgba(0,0,0,.28);
  transition: transform .18s ease, background .18s ease;
}
.play-btn:hover { transform: scale(1.08); background: #b62a2a; }

.tv-hint {
  font-size: 13px;
  color: #7a6d5c;
  line-height: 1.5;
  max-width: 230px;
  pointer-events: none;
}
.tv-hint b { color: #9c4a3a; font-weight: 600; }

.tv { left: 370px; top: 727px; width: 475px; z-index: 3; }

/* ---------------- 左页元素 ---------------- */
.txt.title    { left: 364px; top: 400px; width: 444px; z-index: 4; }
.txt.apple    { left: 768px; top: 318px; width: 92px;  z-index: 4; }
.txt.vlog     { left: 389px; top: 540px; width: 348px; z-index: 4; }
.txt.lightning{ left: 764px; top: 640px; width: 133px; z-index: 4; }

/* ---------------- 右页元素 ---------------- */
.txt.notes    { left: 1073px; top: 399px; width: 425px; z-index: 4; }
.typewriter   { left: 985px;  top: 696px; width: 554px; z-index: 3; }

/* ============================================================
   打字机纸张：任何人都能在这打字
   ------------------------------------------------------------
   纸面是一块**真 textarea**，不是自绘文字层。
   为什么必须用真输入框：中文输入法的拼音串/候选词得看得见，
   "自绘文字 + 全透明输入框"会把拼音藏掉，等于中文没法打。

   两层结构，绝对定位叠在一起、字体相关属性逐项对齐：
     .typed-text   视觉层 —— 画字迹 + 待机闪烁光标（未聚焦时显示）
     .typed-input  输入层 —— 真 textarea，聚焦时才显字、显示原生光标
   两层样式一致，所以聚焦/失焦切换时字不会跳。
   ============================================================ */
.typed {
  position: absolute;
  /* 纸面实测（typewriter.png 1130×1036，显示宽 554 → 系数 0.4903）：
     纸 x 293~932 → content x 1128.7~1442；纸顶 image y≈10 → content y 701。
     手撕毛边占掉顶部约 45px，所以文字从 742 起排；纸面下沿约到 925。 */
  left: 1136px;
  top: 742px;
  width: 296px;
  height: 176px;
  /* ⚠️ 必须高于 .goto-page2(z-index:5) —— 那是"点右页翻到第二页"的整块热区，
     层级低了点击会被它吃掉，字根本打不进去。
     徽章在 x1525 起、与纸面 x1134~1436 不重叠，所以抬高到 8 不影响它。 */
  z-index: 8;
  pointer-events: auto;   /* 原为 none，现在纸面要接点击 */
  transform: rotate(-1.5deg);
  --tw-base-fs: 24px;
  --tw-fs: var(--tw-base-fs);
}
.typed-text,
.typed-input {
  position: absolute;
  inset: 0;
  margin: 0;
  padding: 0;
  border: 0;
  font-family: "Segoe Script", "Brush Script MT", "Comic Sans MS", "KaiTi", "Microsoft YaHei", cursive;
  font-size: var(--tw-fs);
  font-weight: 600;
  letter-spacing: 1px;
  line-height: 1.5;
  text-align: center;
  color: #2f2a24;
  text-shadow: 0 0 1px rgba(0,0,0,.06);
  white-space: pre-wrap;
  overflow-wrap: anywhere;
  overflow: hidden;
}
/* 视觉层：光标是 inline 元素，自动落在最后一行的行尾 —— 不需要 JS 算位置 */
.typed-text { pointer-events: none; user-select: none; }
.typed-text .caret {
  display: inline-block;
  width: 2px;
  height: 1em;
  vertical-align: -.18em;
  margin-left: 2px;
  background: #2f2a24;
  animation: blink 1s steps(1) infinite;
}
@keyframes blink { 50% { opacity: 0; } }
/* 空纸时的一句引导（只在未聚焦时出现，聚焦后交给真光标） */
.typed-text .typed-ph {
  color: rgba(47,42,36,.34);
  font-style: italic;
  font-size: .82em;
  letter-spacing: .5px;
}

/* 输入层：接点击 / 键盘 / 手机输入法 */
.typed-input {
  pointer-events: auto;
  resize: none;
  outline: 0;
  background: transparent;
  cursor: text;
  color: transparent;        /* 未聚焦：字由视觉层画，这里只留一块可点区域 */
  caret-color: transparent;
  -webkit-user-select: text;
  user-select: text;
  touch-action: manipulation; /* 移动端别把点击判成双击缩放 */
}
/* 聚焦后：输入层显字 + 原生光标（深朱红，像打字机色带），视觉层让位 */
.typed:focus-within .typed-input { color: #2f2a24; caret-color: #9c4a3a; }
/* 选中色也换成暖金 —— 系统默认的蓝块和这页的奶油/牛皮色调完全不搭，
   而"第一次点纸面会整段选中"是主要交互之一，这一块会经常入镜 */
.typed-input::selection { background: rgba(214,170,104,.5); color: #2f2a24; }
.typed:focus-within .typed-text  { visibility: hidden; }

/* 悬停/聚焦反馈：纸面浮出一圈暖金虚线。
   浅米色底上纯白光晕看不见（这个坑 2026-09-11 记过），所以取暖金。
   只改 opacity / 颜色，不动几何尺寸。 */
.typed::before {
  content: '';
  position: absolute;
  inset: -12px -10px;
  border-radius: 10px;
  z-index: -1;
  pointer-events: none;
  /* ⚠️ 这里有个缩放陷阱：整个 .scene 是按视口等比缩放的（1600×900 时只有 0.7 倍），
     border-width 也跟着缩，2px 画出来只剩 1.4px 还带模糊 —— 米色底上等于看不见。
     所以改用"靠体积不靠线宽"的做法：一圈暖色光晕 + 加粗到 3px 的虚线。
     光晕和边框都写死在基态，只过渡 opacity —— 比逐帧改 box-shadow 便宜得多。 */
  border: 3px dashed rgba(178,116,48,.70);
  background: radial-gradient(112% 104% at 50% 46%,
    rgba(255,234,192,.32), rgba(255,229,176,.10) 68%, rgba(255,229,176,0) 100%);
  box-shadow: 0 0 22px 5px rgba(214,170,104,.38);
  opacity: 0;
  transition: opacity .26s ease;
}
.typed:hover::before,
.typed.nudge::before { opacity: .92; }
.typed:focus-within::before { opacity: 1; }

/* 提示药丸：平时藏着，悬停时浮出；一聚焦就收起（都在打字了不用再提示）。
   rotate 反向补 1.5° —— .typed 整体是倾斜的，药丸得摆平才像 UI 而不是贴纸。 */
.typed-hint {
  position: absolute;
  left: 50%;
  bottom: 0;
  translate: -50% 0;
  rotate: 1.5deg;
  padding: 4px 11px;
  border: 1px solid rgba(190,138,74,.5);
  border-radius: 999px;
  background: rgba(255,253,246,.94);
  box-shadow: 0 2px 8px rgba(0,0,0,.10);
  color: #9c4a3a;
  font-family: "Microsoft YaHei", "PingFang SC", system-ui, sans-serif;
  font-size: 13px;
  font-weight: 600;
  letter-spacing: .2px;
  line-height: 1.35;
  white-space: nowrap;
  pointer-events: none;
  opacity: 0;
  transition: opacity .28s ease;
}
.typed:hover .typed-hint,
.typed.nudge .typed-hint { opacity: 1; }
.typed:focus-within .typed-hint { opacity: 0; }

/* ---------------- 右侧搭扣徽章：点击"关于我" ---------------- */
.badge-btn {
  position: absolute;
  left: 1525px;
  top: 730px;
  width: 119px;
  height: 119px;
  z-index: 6;
  border: none;
  background: transparent;
  padding: 0;
  cursor: pointer;
  /* transform 留给悬停（scale/rotate 独立属性与它叠加，不冲突）；
     入场走 opacity / scale / translate / rotate。
     ⚠️ 这条 transition 必须带上入场属性 —— 它与 .content > * 同特异性，
        写在后面会整条覆盖掉入场过渡（漏了就会"啪"地直接出现）。 */
  transition:
    transform .2s ease 0s,
    opacity   var(--settle-dur) ease               var(--d, 0s),
    scale     var(--settle-dur) var(--settle-ease) var(--d, 0s),
    translate var(--settle-dur) var(--settle-ease) var(--d, 0s),
    rotate    var(--settle-dur) var(--settle-ease) var(--d, 0s);
}
.badge-btn:hover { transform: scale(1.06) rotate(-3deg); }
.badge-btn img { position: static; width: 100%; height: auto; pointer-events: none; }

/* ---------------- 右页跳转第二页 ---------------- */
/* 覆盖右页区域（手账本中缝右侧），z-index 低于徽章(6)，徽章交互不受影响 */
.goto-page2 {
  position: absolute;
  left: 990px;
  top: 254px;
  width: 801px;   /* 至手账本右缘 1791 */
  height: 1035px;
  z-index: 5;
  cursor: pointer;
}
/* 悬停时右下角浮现"下一页"提示 */
.goto-page2::after {
  content: "下一页 →";
  position: absolute;
  right: 24px;
  bottom: 20px;
  padding: 7px 14px;
  background: rgba(255, 253, 246, .95);
  border: 1px solid #e0d3bd;
  border-radius: 999px;
  box-shadow: 0 4px 12px rgba(0,0,0,.14);
  color: #9c4a3a;
  font-size: 14px;
  font-weight: 600;
  opacity: 0;
  transform: translateY(6px);
  transition: opacity .22s ease, transform .22s ease;
  pointer-events: none;
}
.goto-page2:hover::after { opacity: 1; transform: translateY(0); }

/* 入场动画 */
@keyframes fadeIn { from { opacity: 0; } to { opacity: 1; } }

/* ---------------- 视频浮层（全站共用） ----------------
   两处用到同一套类名，样式只在这里定义一处，各页不再重复：
     · 第五页 —— 点六个文件夹弹出
     · 第四页 —— 点胶卷框右上角放大标弹出
   第四页的做法是把框里的 <video> 元素**直接搬进 .vm-stage**（移动 DOM，不是新建
   一个 video），好处是同一个元素、同一份缓冲：不重复下载切片，播放进度也不丢。

   ⚠️ 浮层是「整屏剧场」，不是居中的小卡片。
   早先做成 78vw + 白卡片，竖屏素材一放就像个"画中画"小窗，完全不像全屏观看。
   现在铺满整个视口、背景压到近黑、视频按原比例 letterbox 撑满可用区；
   右上角另有 ⛶ 按钮走浏览器真全屏（Fullscreen API），连浏览器 UI 一起隐掉。 */
.video-modal {
  position: fixed;
  inset: 0;
  z-index: 80;
  background: rgba(14, 11, 9, .94);
  animation: vmFade .22s ease both;
}
@keyframes vmFade { from { opacity: 0; } }
.video-modal[hidden] { display: none; }
/* 只负责「点空白处关闭」的热区，颜色交给 .video-modal */
.vm-backdrop { position: absolute; inset: 0; }
.vm-card {
  position: relative;
  display: flex;
  flex-direction: column;
  width: 100%;
  height: 100%;
  padding: 0;
  background: none;
  border-radius: 0;
  box-shadow: none;
}
/* 视频容器：占满除标题外的整屏 */
.vm-stage {
  flex: 1 1 auto;
  min-height: 0;
  display: flex;
}
/* 视频一律按原比例撑满可用区（object-fit: contain 负责留黑边，不拉变形） */
.vm-card > video,
.vm-stage > video {
  flex: 1 1 auto;
  min-width: 0;
  min-height: 0;
  display: block;
  width: 100%;
  height: 100%;
  max-height: none;
  object-fit: contain;
  background: transparent;
  border-radius: 0;
}
.vm-title {
  flex: 0 0 auto;
  padding: 2px 16px 14px;
  color: rgba(255, 253, 246, .82);
  font-size: 15px;
  font-weight: 600;
  text-align: center;
}
/* 没配片名时整行收起，别白占一条高度、也别在画面下方留一段黑边 */
.vm-title:empty { display: none; }
/* 右上角两枚按钮：全屏 / 关闭 */
.vm-close,
.vm-full {
  position: absolute;
  top: 18px;
  z-index: 3;
  display: flex;
  align-items: center;
  justify-content: center;
  width: 42px;
  height: 42px;
  padding: 0;
  border: 1px solid rgba(255, 253, 246, .38);
  border-radius: 50%;
  background: rgba(255, 253, 246, .10);
  color: #fffdf6;
  cursor: pointer;
  transition: background .2s ease, transform .2s ease;
}
.vm-full { right: 72px; }
.vm-close { right: 18px; font-size: 17px; line-height: 1; }
.vm-full .ic-exit { display: none; }
.vm-full.is-full .ic-expand { display: none; }
.vm-full.is-full .ic-exit { display: block; }
.vm-close:hover { transform: rotate(90deg); background: rgba(156, 74, 58, .88); }
.vm-full:hover { background: rgba(156, 74, 58, .88); }

/* 关于我 弹层 */
.tip {
  position: fixed;
  z-index: 50;
  background: #fffdf6;
  border: 1px solid #e0d3bd;
  border-radius: 12px;
  box-shadow: 0 12px 30px rgba(0,0,0,.20);
  padding: 14px 16px;
  width: 220px;
  color: #5a4a3a;
  font-size: 14px;
  line-height: 1.6;
  opacity: 0;
  transform: translateY(6px);
  pointer-events: none;
  transition: opacity .25s ease, transform .25s ease;
}
.tip.show { opacity: 1; transform: translateY(0); }
.tip b { color: #9c4a3a; }

/* ---------------- 动态效果（加强版） ---------------- */
/* 苹果：加大悬浮幅度 */
@keyframes floatA {
  0%, 100% { transform: translateY(0) rotate(0deg); }
  50%      { transform: translateY(-16px) rotate(3.5deg); }
}
/* 闪电：电流震荡 */
@keyframes zap {
  0%, 100% { transform: translate(0, 0) rotate(0deg); }
  20%      { transform: translate(2px, -1.5px) rotate(2deg); }
  40%      { transform: translate(-2.5px, 1px) rotate(-2.2deg); }
  60%      { transform: translate(1.5px, 2px) rotate(1.5deg); }
  80%      { transform: translate(-1px, -2px) rotate(-1.2deg); }
}
/* VLOG 轻微摇摆 */
@keyframes sway {
  0%, 100% { transform: rotate(-1deg); }
  50%      { transform: rotate(1.2deg); }
}
/* 整体呼吸：幅度必须很小。手账本宽 1135px，0.6% 已经有 ±3.4px 的边缘位移，
   足够被眼睛察觉；再大就从"呼吸"变成"晃动"了。 */
@keyframes breathe {
  0%, 100% { transform: scale(1); }
  50%      { transform: scale(1.006); }
}
/* 浮 B：比 floatA 更慢更轻，用于"贴在纸面上、只是微微起伏"的素材 */
@keyframes floatB {
  0%, 100% { transform: translateY(0) rotate(-1deg); }
  50%      { transform: translateY(-11px) rotate(1.6deg); }
}
/* 闪烁：必须动 filter 而不是 opacity —— 本页素材的 opacity 被入场淡入占着，
   用 animation 动 opacity 会把整段入场淡入吃掉（动画优先级高于 transition）。 */
@keyframes twinkle {
  0%, 100% { filter: brightness(1); }
  50%      { filter: brightness(1.3); }
}
/* 徽章光环：浅米底上不用白色光晕（看不见），用品牌红的低透明度外发光 */
@keyframes halo {
  0%, 100% { filter: drop-shadow(0 0 0 rgba(156, 74, 58, 0)); }
  50%      { filter: drop-shadow(0 0 10px rgba(156, 74, 58, .5)); }
}

.txt.apple     { animation: floatA 4.2s ease-in-out infinite; }
.txt.lightning { animation: zap .55s linear infinite; }
.txt.vlog      { animation: sway 7s ease-in-out .4s infinite; }
.notebook      { animation: breathe 10s ease-in-out infinite; }
.txt.notes     { animation: sway 9s ease-in-out .6s infinite; }
.badge         { animation: halo 3.6s ease-in-out infinite; }

/* 鼠标触碰反馈（文字贴纸 + 便签；scale/rotate 独立属性与浮动动画叠加，不冲突）
   ⚠️ 这条必须带上 opacity / translate 的入场过渡：本文件靠前的
      `.txt.notes, .txt.title, .txt.vlog` 入场规则与它同特异性，
      后写的这条会整条覆盖它。少写属性 = 这几个贴纸的淡入会直接跳变。
      而 scale / rotate 的延迟必须留在 0s，否则悬停要等一次入场延迟才响应。 */
.txt.notes, .txt.title, .txt.vlog {
  pointer-events: auto;
  transition:
    scale     .22s ease 0s,
    rotate    .22s ease 0s,
    opacity   var(--settle-dur) ease               var(--d, 0s),
    translate var(--settle-dur) var(--settle-ease) var(--d, 0s);
}
.txt.notes:hover { scale: 1.04; rotate: 1.5deg; }
.txt.title:hover { scale: 1.06; rotate: -2deg; }
.txt.vlog:hover  { scale: 1.07; rotate: 2deg; }

/* 尊重系统"减少动态效果"设置 */
@media (prefers-reduced-motion: reduce) {
  .scene, .txt.apple, .txt.lightning, .txt.vlog,
  .notebook, .txt.notes, .badge { animation: none; }

  /* 打字机：待机光标不闪、悬停虚线的过渡去掉（引导语也直接给全文，见 main.js） */
  .typed-text .caret { animation: none; }
  .typed::before, .typed-hint { transition: none; }

  /* 浮层卡片的缩放弹出 */
  .video-modal { animation: none; }

  /* 「素材落位」入场走的是 transition 而不是 animation，
     所以上面那条 animation:none 关不掉它，必须单独把过渡去掉。
     （@starting-style 只用于给出过渡的起始值，没有过渡 = 直接用静止值，
       不会留下残影，也不会闪一下。） */
  .content > * { transition: none; }
}

/* 工具类 */
.hidden { display: none !important; }
.tv-hint.flash { color: #9c4a3a; animation: flash .9s ease; }
@keyframes flash { 0%,100% { opacity: 1; } 50% { opacity: .25; } }

@media (max-width: 640px) {
  /* 只改字号基准，--tw-fs 由 JS 按纸面高度自适应 */
  .typed { --tw-base-fs: 18px; }
}