/* ============================================================================
   Studio 世界（/studio）—— 原「Tsunami Studio 个人主页」的全部样式
   ----------------------------------------------------------------------------
   合并时这个文件做过的三件事：

     1. 原来的 :root / light / dark 三段 token 已整体并进
        public/styles/global.css。两个世界现在共用同一套玻璃、圆角、动效曲线；
        个人站独有的那批（--grad / --grad-btn / --title-* / --avatar-* /
        --chip-bg / --vig / --wash / --t-*）也搬到了那边的主题块里，
        按主题各定义一份 —— 这里不再有第二份副本，避免两套主题各说各话。

     2. token 改名对齐共用词表：
          --glass-fill        → --glass-bg
          --glass-fill-strong → --glass-bg-strong
          --blur              → --glass-blur
          --shadow            → --glass-shadow
        个人站的指针变量 --mx/--my 改成 --st-mx/--st-my：服务器站那边同名，
        但语义不同（那边存 px 绝对值，这边存百分比）。同名不会立刻出错，
        只会在某天两处样式叠在一起时冒出一个极难查的偏差。

     3. 背景三层（.bg / #motes / .vignette）改由 Layout 的氛围层承载
        （.ambient-image--studio / .ambient-motes / .ambient-vignette），
        并与 Network 世界的全屏动态壁纸共用同一套骨架结构。

   设计语言没变：苹果式毛玻璃，动效只允许动 transform / opacity。
   ============================================================================ */

* {
	margin: 0;
	padding: 0;
	box-sizing: border-box;
	-webkit-tap-highlight-color: transparent;
}

html {
	scroll-behavior: smooth;
	/* 溢出裁剪放根元素并传播到视口; 放 body 上会被 Chrome 当成滚动容器,
	   导致 view() 滚动驱动时间轴失活 */
	overflow-x: clip;
}

::selection {
	background: rgba(10, 132, 255, .3);
}

body {
	font-family: -apple-system, BlinkMacSystemFont, "SF Pro Display", "SF Pro Text",
		"Segoe UI", "PingFang SC", "HarmonyOS Sans SC", "Microsoft YaHei", sans-serif;
	color: var(--text);
	/* 兜底底色。氛围层铺满时看不到它，但首帧、以及氛围层被裁掉的极端情况，
	   这一句决定露出来的是不是一道黑边 */
	background: var(--bg);
	min-height: 100vh;
	line-height: 1.6;
	-webkit-font-smoothing: antialiased;
	text-rendering: optimizeLegibility;
}

/* ---------- 氛围层：Studio 世界的天空 ----------
   骨架（.ambient / 定位 / 层级）由 Layout 统一提供，两个世界共用；
   这里只给「天空」这一套参数。个人站原本是三个固定定位 + 负 z-index 的层，
   现在挂进 .ambient 里，和 Network 世界的全屏动态壁纸走同一套结构 ——
   这是「同一个站点、两种氛围」在样式上的落点。 */

.ambient .ambient-image--studio {
	/* 比视口大一圈，ken-burns 推近时四边不会露底 */
	inset: -7%;
	width: auto;
	height: auto;
	background-image: url(/studio/img/background.webp);
	/* 个人站原本就把壁纸模糊过：一是别抢正文，二是给毛玻璃垫一层
	   「没有细节」的底。这两条在合并后依然成立，保留。 */
	filter: blur(30px) saturate(1.15) brightness(1.02);
	animation: kenburns 80s ease-in-out infinite alternate;
}

[data-theme="dark"] .ambient .ambient-image--studio {
	/* ------------------------------------------------------------------
	   深色主题下这张壁纸怎么处理（2026-09-26 定稿为**原个人站那一套**）。
	   ------------------------------------------------------------------
	   这组数值前后动过很多次，根因是一段误诊，先把它讲清楚：

	   站主最早反复说「首屏那块大黑边和其他不契合」。当时一路在查壁纸滤镜，
	   量到画面左三分之一确实有一团近乎全黑的云影（<60/255 的像素占 30%，
	   最暗的 1% 只有 12），于是把 contrast(.52) + brightness(1.20) 压上去，
	   硬把最暗的抬到 71、让「黑块」消失 —— **但站主说的其实不是它**：
	   真正那块「黑边」是从 global.css 借来的 `.hero::before` 压暗层
	   （见本文件「合并适配」节，已整个关掉）。滤镜这几轮改的是一张不存在的脸。

	   压窄对比度的代价是**整张壁纸被抬成一层发白的灰雾**：实测（同一块区域）
	   均值 127 / 饱和度 0.43 / 最暗 1% 43，而站主给的参照（原个人站深色）
	   是 74 / 0.59 / 15 —— 对比之下就是「背景图上蒙了一层白」。
	   站主 2026-09-26 的原话：「对比图 1（My-Blog）去掉背景图里那种白色的蒙版」。

	   所以现在**直接照搬原个人站深色那一组的公式**（那边同源同图，
	   一直被站主当成标准）：blur 32 / saturate 1.1 / brightness .78 ——
	   不压 contrast。左半边那团暗云会回来，但参照图里也有它，
	   而且压暗层已经撤掉，文字不再靠滤镜托底（首屏小字对比度反而因此变好）。
	   浅色那一组本来就是同一套公式（见上面 73 行），不用动。
	   ⚠️ 别再为了「某块看着黑」往这里加 contrast/brightness —— 先查那是不是
	   又是某层带形状的东西漏过来了（坑 39）。 */
	filter: blur(32px) saturate(1.1) brightness(.78);
}

@keyframes kenburns {
	from { transform: scale(1) translate(0, 0); }
	to { transform: scale(1.09) translate(-1.4%, -1%); }
}

/* 光点层：主题感知的粒子，画布由 studio.js 驱动 */
.ambient-motes {
	position: absolute;
	inset: 0;
	width: 100%;
	height: 100%;
	pointer-events: none;
}

/* 暗角：把视线收到中间。--vig 按主题给不同强度（亮色几乎不需要） */
.ambient-vignette {
	position: absolute;
	inset: 0;
	pointer-events: none;
	/* ⚠️ 这里曾经在末尾加过第三层：一层半透明浅蓝的「托底提亮层」
	   （--ambient-lift），想把壁纸左半边那团近黑云影抬起来。
	   数值上确实有效（<60 的像素 16.6% → 3.8%、<80 从 54.8% → 26.0%），
	   但**站主不要这个做法** —— 那等于给整张壁纸蒙了一层浅蓝，
	   深色主题的质感被洗掉了。2026-09-26 已撤掉，别再往回加。
	   这条抱怨的成因、三次返工的取舍见交接文档「Studio 壁纸那条大黑边」。 */
	background: var(--wash), radial-gradient(130% 110% at 50% 28%, transparent 52%, rgba(8, 10, 22, var(--vig)) 100%);
	transition: background .6s var(--ease-out);
}

.cursor-glow {
	position: fixed;
	left: 0;
	top: 0;
	width: 460px;
	height: 460px;
	margin: -230px 0 0 -230px;
	border-radius: 50%;
	background: radial-gradient(circle, rgba(255, 255, 255, .16), transparent 62%);
	mix-blend-mode: soft-light;
	pointer-events: none;
	z-index: 3;
	opacity: 0;
	transition: opacity .6s var(--ease-out);
	will-change: transform;
}

html[data-theme="dark"] .cursor-glow {
	background: radial-gradient(circle, rgba(190, 220, 255, .22), transparent 62%);
}

.cursor-glow.on { opacity: 1; }

@media (pointer: coarse) {
	.cursor-glow { display: none; }
}

/* ---------- 卡片光泽跟踪 + 身份卡边框流光 ---------- */

.sheen { position: relative; overflow: hidden; }

.sheen::after {
	content: "";
	position: absolute;
	inset: 0;
	border-radius: inherit;
	/* ⚠️ 这里原本是 `var(--st-mx), 50%)` —— 合并时批量改 token 名的脚本
	   把 var() 的收尾括号挪到了外面，整条 background 变成非法值、被浏览器整个丢掉。
	   后果是这层光泽**从合并那天起就没渲染过**（元素还在、opacity 也在变，只是没有背景）。
	   如果哪天又发现「某个效果明明写了却没出现」，先怀疑这里。 */
	background: radial-gradient(420px circle at var(--st-mx, 50%) var(--st-my, 50%),
		rgba(255, 255, 255, .12), transparent 60%);
	opacity: 0;
	transition: opacity .45s var(--ease-out);
	pointer-events: none;
}

/* 触屏上 :hover 会「粘住」—— 点一下之后高亮一直留着，直到点别处才散。
   苹果的做法是这类指针效果只在真有指针的设备上生效，所以整组套 hover 门控。
   注意 :focus-visible 单独留着、不进媒体查询 —— 键盘用户在触屏设备上也要能用。 */
@media (hover: hover) and (pointer: fine) {
	.sheen:hover::after { opacity: 1; }
}

.identity::before {
	content: "";
	position: absolute;
	inset: -1px;
	border-radius: inherit;
	padding: 1.2px;
	background: radial-gradient(240px circle at var(--st-mx, 50%) var(--st-my, 50%),
		var(--edge-glow), transparent 65%);
	-webkit-mask: linear-gradient(#fff 0 0) content-box, linear-gradient(#fff 0 0);
	-webkit-mask-composite: xor;
	mask: linear-gradient(#fff 0 0) content-box, linear-gradient(#fff 0 0);
	mask-composite: exclude;
	opacity: 0;
	transition: opacity .4s var(--ease-out);
	pointer-events: none;
}

@media (hover: hover) and (pointer: fine) {
	.identity:hover::before { opacity: 1; }
}

/* ---------- iOS 大标题折叠: 迷你身份药丸 ---------- */

.mini-pill {
	position: fixed;
	top: 16px;
	left: 16px;
	z-index: 80;
	display: flex;
	align-items: center;
	gap: 9px;
	padding: 7px 15px 7px 8px;
	border-radius: 999px;
	font-size: 13px;
	font-weight: 700;
	letter-spacing: -.01em;
	translate: 0 -84px;
	opacity: 0;
	transition: translate .65s var(--ease-spring), opacity .35s var(--ease-out);
	pointer-events: none;
}

.mini-pill img {
	width: 26px;
	height: 26px;
	/* 跟身份卡头像同一套轻微圆角, 药丸里是它的缩小版(小尺寸按视觉取 6px) */
	border-radius: 6px;
}

.mini-pill.show {
	translate: 0 0;
	opacity: 1;
}

/* ---------- 标题逐字母弹簧 ---------- */

.js .title .ltr {
	opacity: 0;
	translate: 0 30px;
	/* 流动用 gradFlowLetters(只走纵向, 保住横向的逐字母色阶); 负延迟让各字母相位错开,
	   颜色像一道微光从词头淌到词尾。这条规则特异性高于下面的 .title .ltr,
	   别把动画改成横向横扫 —— 横扫会把各字母刚拉开的色阶重新抹平 */
	animation: ltrIn .85s var(--ease-spring) calc(var(--i, 0) * 55ms + .15s) forwards,
		gradFlowLetters 10s ease-in-out calc(var(--i, 0) * -0.35s) infinite alternate;
}

@keyframes ltrIn {
	to { opacity: 1; translate: 0 0; }
}

/* ---------- 卡片入场: iOS 式圆角矩形展开(clip-path 凝结到位) ----------
   排除 identity(用 ::before 做边框流光) 与 dock(不能裁剪放大的图标) */

.js .glass.reveal:not(.identity):not(.dock) {
	clip-path: inset(12% 8% round 32px);
}

.js .glass.reveal.in:not(.identity):not(.dock) {
	clip-path: inset(0 0 round var(--card-r, var(--r-lg)));
	transition: opacity .9s var(--ease-out) calc(var(--d, 0) * 90ms),
		transform .9s var(--ease-out) calc(var(--d, 0) * 90ms),
		translate .9s var(--ease-out) calc(var(--d, 0) * 90ms),
		clip-path .95s var(--ease-ios) calc(var(--d, 0) * 90ms + 40ms);
}

/* ---------- 账户卡: 用独立 translate 属性入场(不干扰倾斜 transform) ---------- */

.js .acct.reveal {
	opacity: 0;
	translate: 0 18px;
	animation: acctFade .9s var(--ease-out) calc(var(--d, 0) * 90ms) forwards;
}

@keyframes acctFade {
	to { opacity: 1; translate: 0 0; }
}

/* ---------- 毛玻璃基材 ---------- */

.glass {
	background: var(--glass-bg);
	-webkit-backdrop-filter: var(--studio-glass-filter);
	backdrop-filter: var(--studio-glass-filter);
	border: 1px solid var(--glass-border);
	border-radius: var(--r-lg);
	box-shadow: var(--glass-shadow), inset 0 1px 0 var(--glass-edge);
}

.glass-strong {
	background: var(--glass-bg-strong);
	-webkit-backdrop-filter: blur(32px) saturate(180%);
	backdrop-filter: blur(32px) saturate(180%);
	border: 1px solid var(--glass-border);
	border-radius: var(--r-lg);
	box-shadow: var(--glass-shadow), inset 0 1px 0 var(--glass-edge);
}

@supports not ((backdrop-filter: blur(1px)) or (-webkit-backdrop-filter: blur(1px))) {
	.glass { background: color-mix(in srgb, var(--glass-bg) 70%, #ffffff 30%); }
	html[data-theme="dark"] .glass { background: rgba(20, 23, 32, .88); }
	html[data-theme="light"] .glass { background: rgba(255, 255, 255, .9); }
}

/* ---------- 布局 ---------- */

.shell {
	max-width: 1180px;
	margin: 0 auto;
	/* 顶部要额外让出固定导航栏的高度，否则首屏内容会被压在导航下面 */
	padding: calc(var(--nav-h) + clamp(10px, 2vw, 28px)) clamp(18px, 3.5vw, 44px) clamp(18px, 3.5vw, 44px);
	display: grid;
	grid-template-columns: 330px minmax(0, 1fr);
	gap: 22px;
	align-items: start;
}

.column.side {
	position: sticky;
	top: calc(var(--nav-h) + 14px);
	display: flex;
	flex-direction: column;
	gap: 18px;
}

.column.main-col {
	display: flex;
	flex-direction: column;
	gap: 34px;
	padding-bottom: 20px;
}

/* ---------- 身份卡 ---------- */

.identity {
	padding: 28px 24px 24px;
	text-align: center;
}

/* ---------- 头像: 轻微圆角 + 居中半透明玻璃垫片 + 顶部玻璃高光 ----------
   以前那圈 conic 彩虹环是最典型的"AI 头像圈"画法, 现在改成:
   小圆角(不是方圆角) + 背后垫一层同心、四周略大的半透明玻璃(--glass-fill) + 1px 细边 + 顶部高光。
   玻璃要能透出卡片与壁纸, 所以填充用站内标准半透明档而不是 --glass-fill-strong */
.avatar {
	position: relative;
	width: 120px;
	height: 120px;
	margin: 0 auto 20px;
	border-radius: 12px;
	padding: 4px;
	/* 头像框 = 叠在垫片上的第二层玻璃: 自己也填充, 于是在垫片上又叠一层、比垫片亮一档,
	   才看得出是"堆叠的毛玻璃"; inset 那道顶光是玻璃边缘反光。
	   填充走 --avatar-frame-fill: 亮色=标准玻璃档, 暗色=偏白的霜(见主题块里的注释) */
	background: var(--avatar-frame-fill);
	border: 1px solid var(--avatar-edge);
	/* 投影要贴边(小 blur): 大范围的会像上一版那样把垫片四周压暗成一圈假边 */
	box-shadow: inset 0 1px 0 var(--glass-edge), 0 2px 6px rgba(8, 34, 78, .18);
}

/* 头像底下垫一层半透明玻璃: 与头像同心、四周各外扩 8px。
   注意绝对定位的 inset 是相对**内边距盒**解析的, 所以要多减掉 1px 边框: -9px 才是视觉上的外扩 8px */
.avatar::before {
	content: "";
	position: absolute;
	inset: -9px;
	z-index: -1;
	/* 同心圆角: 外圈半径 = 头像半径 + 外扩量 (12 + 8 = 20) */
	border-radius: 20px;
	background: var(--glass-bg);
	-webkit-backdrop-filter: blur(20px) saturate(160%);
	backdrop-filter: blur(20px) saturate(160%);
	border: 1px solid var(--glass-border);
	box-shadow: 0 8px 22px rgba(8, 34, 78, .22);
	pointer-events: none;
}

/* 顶部一道玻璃高光, 压在照片上 */
.avatar::after {
	content: "";
	position: absolute;
	inset: 4px;
	/* 内圈半径 = 外圈 12px − 1px 细边 − 4px 内边距 = 7px */
	border-radius: 7px;
	background: linear-gradient(180deg, rgba(255, 255, 255, .18), rgba(255, 255, 255, 0) 44%);
	pointer-events: none;
}

.avatar img {
	width: 100%;
	height: 100%;
	/* 与边框、垫片三层同心(见 ::after 的算式) */
	border-radius: 7px;
	object-fit: cover;
	display: block;
}

.who {
	font-size: 24px;
	font-weight: 800;
	letter-spacing: -.02em;
}

.alias {
	color: var(--text-2);
	font-size: 14px;
	margin-top: 2px;
}

.chips {
	display: flex;
	flex-wrap: wrap;
	justify-content: center;
	gap: 8px;
	margin-top: 14px;
}

.chip {
	display: inline-flex;
	align-items: center;
	gap: 6px;
	padding: 6px 12px;
	border-radius: 999px;
	background: var(--chip-bg);
	border: 1px solid var(--glass-border);
	font-size: 12.5px;
	color: var(--text-2);
	white-space: nowrap;
}

.chip svg {
	width: 13px;
	height: 13px;
	fill: currentColor;
	flex-shrink: 0;
}

.chip.live { color: var(--text); font-weight: 600; }

.chip.offline .pulse {
	background: #ff453a;
	border-color: #ff453a;
	box-shadow: 0 0 10px rgba(255, 69, 58, .5);
}

.chip.offline .pulse::after { display: none; }

.pulse {
	width: 8px;
	height: 8px;
	border-radius: 50%;
	background: var(--dot);
	position: relative;
	flex-shrink: 0;
}

.pulse::after {
	content: "";
	position: absolute;
	inset: -4px;
	border-radius: 50%;
	border: 1.5px solid var(--dot);
	animation: pulse 2.2s var(--ease-out) infinite;
}

@keyframes pulse {
	from { transform: scale(.6); opacity: 1; }
	to { transform: scale(1.6); opacity: 0; }
}

/* ---------- 技能 / 时间线卡 ---------- */

.skills-card, .timeline-card {
	padding: 22px 24px;
}

.card-title {
	font-size: 13px;
	font-weight: 700;
	text-transform: uppercase;
	letter-spacing: .12em;
	color: var(--text-2);
	margin-bottom: 14px;
}

.skills {
	display: flex;
	flex-wrap: wrap;
	gap: 8px;
	list-style: none;
}

.skills li {
	padding: 6px 13px;
	border-radius: 999px;
	background: var(--chip-bg);
	border: 1px solid var(--glass-border);
	font-size: 13px;
	font-weight: 500;
	transition: transform .35s var(--ease-spring), background .35s var(--ease-out);
	cursor: default;
}

@media (hover: hover) and (pointer: fine) {
	.skills li:hover {
		transform: translateY(-2px) scale(1.05);
		background: var(--glass-bg-strong);
	}
}

.timeline {
	list-style: none;
	position: relative;
	padding-left: 20px;
	display: flex;
	flex-direction: column;
	gap: 16px;
}

.timeline::before {
	content: "";
	position: absolute;
	left: 5px;
	top: 8px;
	bottom: 8px;
	width: 2px;
	border-radius: 2px;
	background: linear-gradient(to bottom, var(--glass-border), transparent);
}

.timeline li {
	position: relative;
	display: flex;
}

.tl-dot {
	position: absolute;
	left: -20px;
	top: 7px;
	width: 12px;
	height: 12px;
	border-radius: 50%;
	background: var(--glass-bg-strong);
	border: 2px solid var(--text-2);
}

.timeline li:first-child .tl-dot {
	background: var(--dot);
	border-color: var(--dot);
	box-shadow: 0 0 12px rgba(48, 209, 88, .5);
}

.timeline p {
	font-size: 14px;
	font-weight: 550;
	line-height: 1.45;
}

.timeline time {
	font-size: 12px;
	color: var(--text-2);
	font-variant-numeric: tabular-nums;
}

/* ---------- Hero ---------- */

.hero {
	padding: clamp(8px, 2vw, 20px) 6px 0;
}

/* 小标签: 等宽字 + 渐变短棒 + 细线延伸到右端(编排感) */
.hello {
	display: flex;
	align-items: center;
	gap: 10px;
	font-family: ui-monospace, SFMono-Regular, "SF Mono", Menlo, Consolas, monospace;
	font-size: 11.5px;
	font-weight: 600;
	letter-spacing: .18em;
	text-transform: uppercase;
	color: var(--text-2);
	margin-bottom: 8px;
}

.hello::before {
	content: "";
	flex: none;
	width: 20px;
	height: 2px;
	border-radius: 2px;
	background: var(--grad);
}

.hello::after {
	content: "";
	flex: 1;
	height: 1px;
	background: linear-gradient(90deg, var(--glass-border), transparent 72%);
}

.title {
	position: relative;
	/* 手机上也跟着屏宽走(原来 46px 一档到底, 大屏手机显得小) */
	font-size: clamp(40px, 11.5vw, 84px);
	font-weight: 850;
	letter-spacing: -.042em;
	line-height: 1.06;
	color: var(--text);
	padding-bottom: .08em;
	/* 这块字标**不画任何投影**（2026-09-26 站主要求去掉「黑边这个阴影」）。
	   历史：原来这里是一份 blur(20px) 的渐变副本当「柔光回声」—— 字标发糊的主因；
	   09-25 删掉它、换成一枚 text-shadow（`0 2px 14px var(--title-shadow)`）把字托起来；
	   09-26 站主看了这条投影说「我不需要黑边这个阴影」，于是投影也去掉，
	   和 Network 侧的大标题（global.css 里「大标题不再描投影」那一段）保持一致；
	   同一天站主还要求「文字背后那块暗底」也全透明，于是 Studio 的压暗层整个关掉了
	   （见本文件「合并适配」节）——**字标和首屏文字现在直接压在壁纸上**，
	   和原个人站（My-Blog）的长相一样。别再加任何托底的东西回来。
	   ⚠️ 真需要加回托底时，**只能用 text-shadow，不能用 filter: drop-shadow()**：
	   第一版写的就是 filter，结果整块字标渲染成一个纯色矩形、一个字都看不见 ——
	   filter 会建立新的渲染上下文，把后代 .ltr 上的 background-clip: text 整个打断，
	   渐变就不再裁进字形、而是铺满整个盒子。text-shadow 不碰裁剪，画的仍然是字形本身。 */
}

html:not(.js) .title {
	background: var(--grad);
	/* 无 JS 时没有逐字母错位可用, 直接让整条色阶铺满整个标题(此时流动动画无处可动, 静态) */
	background-size: 100% 100%;
	-webkit-background-clip: text;
	background-clip: text;
	-webkit-text-fill-color: transparent;
	color: transparent;
}

/* 逐字母渐变: 每个字母自持 background-clip(带 transform 的子层会打断父级裁剪,
   所以渐变必须落在字母上而不是父级)。
   取色机制: background-size 横向放到 400%(纵向 200% 留给流动动画), 每个字母只截取
   色阶的 1/4, 再由 --gx(studio.js 按序号给)把取色窗口依次往后挪,
   于是**实心段那半截词**的窗口首尾相接、铺满整条色阶 —— 这才是连续渐变的关键。
   原来写成 220% 且不错位时, 每个字母取的都是同一段色阶, 任何一刻整词几乎同色,
   那才是"渐变感不够"的根因, 光换配色救不回来。

   ⚠️ 2026-09-26: 两段式回来了（站主要「Baka 实心 + Asuka 线框」那一版，
   见下面 .t-line）。实心段因此只覆盖名字的前半截，--gx 相应地在**这半截内**铺满
   0→100%（studio.js 里那个 cut）；线框段恒取 100%，与原个人站 script.js:493 的写法
   一致 —— 那一截的值是给下面「灌墨」那层用的，取值理由见 .t-line::after 的注释。 */
.title .ltr {
	position: relative;
	display: inline-block;
	background: var(--grad);
	background-size: 400% 200%;
	background-position-x: var(--gx, 0%);
	-webkit-background-clip: text;
	background-clip: text;
	-webkit-text-fill-color: transparent;
	animation: gradFlowLetters 10s ease-in-out infinite alternate;
	transition: transform .45s var(--ease-spring);
	will-change: transform;
}

/* 线框段（名字后半段）: 只留一圈描边, 字心是透的 —— 天空自己当填充。
   这一版的粗细是按站主给的那张目标图量的: 描边 ≈ 0.045em（84px 下约 3.8px）。
   上一版把两段式取消，理由之一是「实心与描边在 84px 下硬切，像渲染坏了」——
   真正糊的不是这个切分，是同一条字标上还叠了「近白描边 + 淡青错位套印 +
   blur(20px) 渐变回声」三份软边（见交接文档）。现在错位套印和模糊回声都不在，
   描边自己够厚够硬，切分反而读成两种处理。 */
.title .ltr.t-line {
	background: none;
	color: transparent;
	-webkit-text-fill-color: transparent;
	-webkit-text-stroke: var(--t-stroke) var(--title-stroke);
}

/* 边缘光: 指针扫过时, 被扫到的字母亮起一圈冰色轮廓, 越近越亮
   (--fill 由 studio.js 按指针距离给值; 不写 JS 时恒为 0, 就是干净的渐变字标)。

   为什么这一层是**描边**而不是换色: 字母自己是一条「近白 → 宝蓝」的色阶,
   而它背后的天空是「暗 → 亮」, 两者正好反着走。所以任何整体提亮或压深的填充
   都必然在一头掉进背景里 —— 左端近白的字压深了没入暗背景, 右端宝蓝的字提亮了
   没入亮背景。唯一在整条色阶上都成立的反馈是**边缘**: 描边画的是字形轮廓,
   与它内部是什么颜色、背后是什么颜色都无关。

   于是描边在这里从「默认长相」降级成了一次**互动**。上一版被否掉正是因为
   它一直挂在那儿 —— 常驻的轮廓会让整个名字读起来像「没填色」。 */
.title .ltr::after {
	content: attr(data-ch);
	position: absolute;
	inset: 0;
	color: transparent;
	-webkit-text-fill-color: transparent;
	/* 比 --t-stroke 细一档：那一条是按「常驻轮廓」调的粗细，当作掠过的光泽用会显得笨重 */
	-webkit-text-stroke: clamp(1px, 0.16vw, 1.5px) var(--title-rim);
	/* text-shadow 是继承属性。`.title` 现在不画投影了（09-26 去掉，见上面 .title 的说明），
	   这条 `none` 留着是为了以后真加回托底时，这一层不会跟着多出一圈投影 ——
	   它画的是「掠过的一圈光泽」，带投影就脏了 */
	text-shadow: none;
	opacity: var(--fill, 0);
	transition: opacity .3s var(--ease-out);
}

/* 线框段的同一层不是边缘光, 而是「灌墨」: 指针扫到时把色阶灌进字心里
   （上一条规则是实心段的用法, 这里按段覆盖）。
   ⚠️ 这一段取的是色阶**最深**的那一头（--gx: 100%, studio.js 给的; 原个人站同此）。
   试过取浅端（0%）, 结果等于没灌：字标这条色阶的浅端是近白, 而线框本身也是近白描边,
   墨色压上去和描边同色（实测 #b6e3f5 对 #cbe3f2, 差 1~2 级）, 整块字纹丝不动。
   换到深端后字心从 #182338 变成 #3c7bef, 37.9% 的像素变化 >60 —— 这才看得出来。
   （这是坑 26「叠一层和底色相同的图层等于没叠」的同一族; 详细见交接文档坑 40。） */
.title .ltr.t-line::after {
	background: var(--grad);
	background-size: 400% 200%;
	background-position-x: var(--gx, 0%);
	-webkit-background-clip: text;
	background-clip: text;
	-webkit-text-fill-color: transparent;
	/* 把上一条的描边关掉: 这一层要画的是字心的墨, 不是轮廓 */
	-webkit-text-stroke: 0;
}

/* 载入时一圈亮边从词头扫到词尾、再退去: 提示这个字标能玩
   (纯 CSS 一次性动作, 动画结束后仍听 --fill) */
@media (prefers-reduced-motion: no-preference) {
	.js .title .ltr::after {
		animation: inkWash 1.6s var(--ease-out) calc(var(--i, 0) * 70ms + .55s) 1;
	}
}

/* 峰值压到 .6: 扫到 .8 时整词一度看起来就是「描边字标」, 又回到那个被否掉的样子;
   .6 只够读成一道掠过的光泽 */
@keyframes inkWash {
	0% { opacity: 0; }
	38% { opacity: .6; }
	100% { opacity: 0; }
}

/* 逐字母层: 只让纵向漂移。横向位置(--gx)是每个字母的色阶基准, 一旦让动画去动横向,
   字母之间刚拉开的色阶就会被重新抹平(这正是之前那套满幅横扫的问题);
   115° 的斜角让纵向漂移也能带出可见的颜色流动。
   纵向只在色阶低段(4%↔34%)里走: 低段是这条色阶最亮最饱和的一截(冰蓝→天蓝),
   漂到 40% 往上就整词沉进中段宝蓝, 会明显发闷、对比度掉下来(减少动态效果时
   没有动画、y 停在 0%, 那一帧反而是最清爽的, 这里就按那个取色区间来) */
@keyframes gradFlowLetters {
	from { background-position-y: 4%; }
	to { background-position-y: 34%; }
}

.tagline {
	font-size: clamp(14px, 1.8vw, 17px);
	color: var(--text-2);
	max-width: 560px;
	/* 这段文案折行时最后一行常常只剩一两个词（"you do." 单独占一行），
	   pretty 会让浏览器在断行时避开这种孤行；不支持的浏览器忽略这一条，排版退回原样 */
	text-wrap: pretty;
}

.tagline strong { color: var(--text); font-weight: 700; }
.tagline em { font-style: normal; opacity: .85; }

/* ---------- Dock (macOS 式) ---------- */

.dock {
	margin-top: 26px;
	display: inline-flex;
	align-items: flex-end;
	gap: 6px;
	padding: 10px 12px;
	border-radius: 24px;
	position: relative;
}

.dock-item {
	position: relative;
	width: 52px;
	height: 52px;
	border-radius: 16px;
	border: 1px solid var(--glass-border);
	background: var(--chip-bg);
	display: grid;
	place-items: center;
	cursor: pointer;
	color: var(--text);
	text-decoration: none;
	transform-origin: 50% 100%;
	transition: transform .38s var(--ease-spring), background .3s var(--ease-out), box-shadow .3s var(--ease-out);
}

.dock-item svg {
	width: 24px;
	height: 24px;
	fill: currentColor;
	transition: transform .38s var(--ease-spring);
}

.dock-item:focus-visible {
	background: var(--glass-bg-strong);
	box-shadow: 0 6px 20px rgba(10, 90, 220, .25), inset 0 1px 0 var(--glass-edge);
}

@media (hover: hover) and (pointer: fine) {
	.dock-item:hover {
		background: var(--glass-bg-strong);
		box-shadow: 0 6px 20px rgba(10, 90, 220, .25), inset 0 1px 0 var(--glass-edge);
	}
}

.dock-item:active { transform: scale(.92); }
/* 按下时把过渡压到 120ms：内联的放大 transform 也走这条过渡，
   松手去掉这个类就回到 .38s 弹簧 —— 按下即时、回弹有回弹（§1）。 */
.dock-item.is-pressed { transition-duration: 120ms; }

.dock-tip {
	position: absolute;
	bottom: calc(100% + 12px);
	left: 50%;
	transform: translateX(-50%) translateY(6px) scale(.9);
	padding: 5px 12px;
	border-radius: 10px;
	background: var(--glass-bg-strong);
	-webkit-backdrop-filter: blur(16px);
	backdrop-filter: blur(16px);
	border: 1px solid var(--glass-border);
	font-size: 12px;
	font-weight: 600;
	color: var(--text);
	white-space: nowrap;
	opacity: 0;
	pointer-events: none;
	transition: opacity .25s var(--ease-out), transform .3s var(--ease-spring);
	box-shadow: 0 4px 14px rgba(0, 0, 0, .18);
}

.dock-item:focus-visible .dock-tip {
	opacity: 1;
	transform: translateX(-50%) translateY(0) scale(1);
}

@media (hover: hover) and (pointer: fine) {
	.dock-item:hover .dock-tip {
		opacity: 1;
		transform: translateX(-50%) translateY(0) scale(1);
	}
}

/* 主题图标切换 (太阳/月亮 旋转交叉) */

#themeToggle .ico-moon { display: none; position: absolute; }
#themeToggle .ico-sun { position: absolute; }

html[data-theme="dark"] #themeToggle .ico-sun { display: none; }
html[data-theme="dark"] #themeToggle .ico-moon { display: block; }

#themeToggle svg {
	animation: icoSwap .5s var(--ease-spring);
}

@keyframes icoSwap {
	from { transform: rotate(-90deg) scale(.5); opacity: 0; }
	to { transform: rotate(0) scale(1); opacity: 1; }
}

/* ---------- 账户卡: 3D 倾斜 + 光泽跟随 ---------- */

.section-title {
	display: flex;
	align-items: center;
	gap: 9px;
	font-size: 15px;
	font-weight: 750;
	letter-spacing: .06em;
	color: var(--text-2);
	margin-bottom: 16px;
}

.section-title::before {
	content: "";
	width: 4px;
	height: 15px;
	border-radius: 3px;
	background: var(--grad);
}

.grid {
	display: grid;
	grid-template-columns: repeat(2, minmax(0, 1fr));
	gap: 14px;
}

.acct {
	--card-r: var(--r-md);
	position: relative;
	display: flex;
	align-items: center;
	justify-content: space-between;
	gap: 12px;
	padding: 22px 24px;
	color: var(--text);
	text-decoration: none;
	overflow: hidden;
	border-radius: var(--r-md);
	transform: perspective(800px) rotateX(var(--rx, 0deg)) rotateY(var(--ry, 0deg));
	transition: transform .55s var(--ease-ios), box-shadow .5s var(--ease-out), border-color .4s var(--ease-out), background .4s var(--ease-out);
}

/* 抬升 + 描边 + 底色这一组只在有指针的设备上生效；
   键盘焦点保留（:focus-visible 单独一条，不进门控）。 */
.acct:focus-visible {
	transform: perspective(800px) rotateX(var(--rx, 0deg)) rotateY(var(--ry, 0deg)) translateY(-3px);
	box-shadow: 0 22px 54px rgba(10, 60, 160, .16), 0 4px 14px rgba(10, 60, 160, .08), inset 0 1px 0 var(--glass-edge);
	border-color: var(--glass-edge);
	background: var(--glass-bg-strong);
}

@media (hover: hover) and (pointer: fine) {
	.acct:hover {
		transform: perspective(800px) rotateX(var(--rx, 0deg)) rotateY(var(--ry, 0deg)) translateY(-3px);
		box-shadow: 0 22px 54px rgba(10, 60, 160, .16), 0 4px 14px rgba(10, 60, 160, .08), inset 0 1px 0 var(--glass-edge);
		border-color: var(--glass-edge);
		background: var(--glass-bg-strong);
	}
}

.acct:active {
	transform: perspective(800px) rotateX(var(--rx, 0deg)) rotateY(var(--ry, 0deg)) translateY(-1px) scale(.985);
	transition-duration: .12s;
}

.acct-txt h3 {
	font-size: 17px;
	font-weight: 750;
	letter-spacing: -.01em;
}

.chev {
	font-size: 24px;
	font-weight: 500;
	line-height: 1;
	color: var(--text-2);
	transform: translateX(-4px);
	opacity: .7;
	transition: transform .45s var(--ease-spring), opacity .3s var(--ease-out), color .3s var(--ease-out);
}

.acct:focus-visible .chev { transform: translateX(2px); opacity: 1; color: var(--text); }

@media (hover: hover) and (pointer: fine) {
	.acct:hover .chev { transform: translateX(2px); opacity: 1; color: var(--text); }
}

/* 光泽: 一道柔和的径向高光跟随鼠标 */
.acct::after {
	content: "";
	position: absolute;
	inset: 0;
	border-radius: inherit;
	background: radial-gradient(420px circle at var(--st-mx, 50%) var(--st-my, 50%),
		rgba(255, 255, 255, .16), transparent 60%);
	opacity: 0;
	transition: opacity .45s var(--ease-out);
	pointer-events: none;
}

@media (hover: hover) and (pointer: fine) {
	.acct:hover::after { opacity: 1; }
}

/* ---------- 服务器状态 ---------- */

/* 大卡片的 3D 视差。
   ---------------------------------------------------------------------------
   2026-09-26 改版记录：这里原本只有「悬停提亮描边 + 按下 scale」，
   当时的注释写着「**不做 3D 倾斜** —— 在 740×340 的大面板上会让人觉得整个页面歪了」。
   用户明确要求「加那种 3D 视差的动效」，所以推翻：做，但要做得有层次。

   只让整块面板歪一点点是不够的 —— 那是「倾斜」，不是「视差」。
   视差的要点是**里面的东西以不同速率跟着动**，眼睛才会读出深度：
     .server-ico   ±7px  （最靠前，动得最多）
     .server-name  ±5px
     .server-pill  ±3px
     .stat         ∓4px  （在面板后面，反向动）
     .server-visit ∓3px
   --px / --py 是 [-1,1] 的无量纲数，由 studio.js 的阻尼循环写进来（每帧 lerp，
   不是直接跟手）—— 松手时它自己缓回 0，所以这几条规则**都不需要 CSS transition**，
   过渡由 JS 的 lerp 负责（和 initTilt 同一套做法）。
   translate 用的是独立属性，和 transform 上的倾斜互不干扰。

   性能：这块面板是毛玻璃，transform 每帧变会让背景模糊跟着重采样。
   所以角度压得比 .acct 小（3deg / 2.4deg，小卡是 7deg / 5.5deg），
   实测帧时间见交接文档。 */
.server-frame {
	padding: 22px 24px;
	display: flex;
	flex-direction: column;
	gap: 18px;
	transform: perspective(1100px)
		rotateX(calc(var(--py, 0) * -2.4deg))
		rotateY(calc(var(--px, 0) * 3deg));
	transition: border-color .35s var(--ease-out), scale .18s var(--ease-out);
}

/* 视差层。系数写在 CSS 里而不是 JS 里 —— 调深度不用碰脚本 */
.server-frame .server-ico { translate: calc(var(--px, 0) * 7px) calc(var(--py, 0) * 7px); }
.server-frame .server-name { translate: calc(var(--px, 0) * 5px) calc(var(--py, 0) * 5px); }
.server-frame .server-pill { translate: calc(var(--px, 0) * 3px) calc(var(--py, 0) * 3px); }
.server-frame .stat { translate: calc(var(--px, 0) * -4px) calc(var(--py, 0) * -4px); }
.server-frame .server-visit { translate: calc(var(--px, 0) * -3px) calc(var(--py, 0) * -3px); }

/* 面板整体歪的时候，里面那三格也跟着歪会显得「贴在面板上」；
   让它们反向飘一点，层次就出来了。 */

@media (hover: hover) and (pointer: fine) {
	.server-frame:hover {
		border-color: var(--glass-edge);
	}
}

.server-frame:active {
	scale: .995;
}

/* 大卡的入场位移改用独立属性 translate —— 它的 transform 被上面那套 3D 视差占着，
   而 .js .reveal.in（(0,3,0)）会把 transform 钉死（所以两条通用规则都排除了它）。
   位移量和通用规则原本的 translateY(26px) 一致，视觉上没变；
   过渡由 .js .glass.reveal.in:not(.identity):not(.dock) 那条统一提供，这里不用再写。 */
.js .server-frame.reveal { translate: 0 26px; }
.js .server-frame.reveal.in { translate: 0 0; }

.server-head {
	display: flex;
	align-items: center;
	gap: 13px;
}

.server-ico {
	width: 48px;
	height: 48px;
	flex-shrink: 0;
	filter: drop-shadow(0 6px 16px rgba(10, 100, 240, .35));
}

.server-ico img {
	width: 100%;
	height: 100%;
	display: block;
}

.server-name {
	font-size: 17px;
	font-weight: 750;
	letter-spacing: -.01em;
	margin-right: auto;
}

.server-pill {
	display: inline-flex;
	align-items: center;
	gap: 7px;
	padding: 6px 13px;
	border-radius: 999px;
	background: var(--chip-bg);
	border: 1px solid var(--glass-border);
	font-size: 11.5px;
	font-weight: 700;
	letter-spacing: .08em;
	text-transform: uppercase;
	color: var(--text-2);
	white-space: nowrap;
}

.server-pill .pulse { width: 7px; height: 7px; }

.server-pill.online { color: var(--text); }

.server-pill.offline .pulse {
	background: #ff453a;
	border-color: #ff453a;
	box-shadow: 0 0 10px rgba(255, 69, 58, .5);
}

.server-pill.offline .pulse::after { display: none; }

.server-stats {
	display: grid;
	grid-template-columns: repeat(3, minmax(0, 1fr));
	gap: 12px;
}

.stat {
	padding: 14px 16px;
	border-radius: var(--r-sm);
	background: var(--chip-bg);
	border: 1px solid var(--glass-border);
	display: flex;
	flex-direction: column;
	gap: 3px;
	min-width: 0;
}

.stat-label {
	font-size: 10.5px;
	font-weight: 700;
	letter-spacing: .12em;
	text-transform: uppercase;
	color: var(--text-2);
}

.stat-value {
	font-size: 16px;
	font-weight: 750;
	font-variant-numeric: tabular-nums;
	overflow: hidden;
	text-overflow: ellipsis;
	white-space: nowrap;
}

@media (max-width: 620px) {
	.server-stats { grid-template-columns: 1fr 1fr; }
	.stat-label { font-size: 11.5px; }
	.copy-btn { min-height: 44px; }
}

/* ---------- 手机版: 滚动靠近放大、远离缩小(Apple 式, 滚动驱动零 JS) ---------- */

@keyframes proxScale {
	0% { scale: 0.95; }
	40% { scale: 1; }
	60% { scale: 1; }
	100% { scale: 0.95; }
}

@media (max-width: 720px) {
	/* 包一层 no-preference：`animation-duration: .01ms !important` 那种兜底
	   管不住滚动时间轴驱动的动画，减少动态效果的用户本来还会看到缩放 */
	@media (prefers-reduced-motion: no-preference) {
	@supports (animation-timeline: view()) {
		.js .glass.reveal:not(.dock) {
			animation: proxScale linear both;
			animation-timeline: view();
			animation-range: entry 0% exit 100%;
		}

		/* 账户卡保留入场动画, 两个动画各挂各的时间轴 */
		.js .glass.acct.reveal {
			animation: acctFade .9s var(--ease-out) calc(var(--d, 0) * 90ms) forwards,
				proxScale linear both;
			animation-timeline: auto, view();
			animation-range: normal, entry 0% exit 100%;
		}
	}
	}
}

/* ---------- 页脚 ----------
   这里原本有三条**无作用域**的 .footer / .footer a 规则（个人站自带的页脚样式）。
   合并之后页脚是共享组件（src/components/Footer.astro），于是这三条在 /studio 上
   额外盖住了组件的样式，造成同一个页脚在两个世界长得不一样：
     - .footer 多出 padding 8px 16px（Network 那边内缩 24px，Studio 变成 40px）
     - .footer a 给**每个**页脚链接加了虚线底边 —— 包括「Go to Studio」那枚胶囊，
       而胶囊自己已经有品牌色描边和渐变下划线了，两条线叠在一起
     - text-align: center 和组件的左对齐打架
   全部删除。页脚现在只有一份定义（Footer.astro），两个世界一致；
   组件那边已经改成整体居中，所以连 text-align 都不需要在这里补。 */

/* ---------- 弹窗 (玻璃 modal) ---------- */

.modal[hidden] { display: none; }

.modal-backdrop {
	position: fixed;
	inset: 0;
	z-index: 90;
	background: rgba(12, 14, 24, .35);
	-webkit-backdrop-filter: blur(10px);
	backdrop-filter: blur(10px);
	opacity: 0;
	touch-action: none;
	overscroll-behavior: contain;
	transition: opacity .4s var(--ease-out);
}

.modal.open .modal-backdrop { opacity: 1; }

.modal-card {
	position: fixed;
	z-index: 91;
	left: 50%;
	top: 50%;
	width: min(340px, calc(100vw - 40px));
	/* 横屏/矮视口: 卡片不高于视口, 内容可滚, 关闭按钮与按钮不至于跑到屏幕外 */
	max-height: calc(100vh - 28px);
	max-height: calc(100dvh - 28px);
	overflow-y: auto;
	overscroll-behavior: contain;
	transform: translate(-50%, -50%) scale(.86) translateY(14px);
	opacity: 0;
	padding: 26px 24px 22px;
	text-align: center;
	transition: transform .5s var(--ease-spring), opacity .3s var(--ease-out);
}

.modal.open .modal-card {
	transform: translate(-50%, -50%) scale(1) translateY(0);
	opacity: 1;
}

.modal-title {
	font-size: 17px;
	font-weight: 750;
	margin-bottom: 14px;
}

.modal-img {
	width: auto;
	max-width: 100%;
	/* 矮视口(手机横屏)缩小二维码而不是把卡片撑出屏幕; 176px = 卡片内边距+标题+按钮 的实测高度再加余量 */
	max-height: min(440px, calc(100vh - 176px));
	max-height: min(440px, calc(100dvh - 176px));
	margin: 0 auto;
	border-radius: var(--r-sm);
	display: block;
}

.modal-x {
	position: absolute;
	top: 10px;
	right: 12px;
	width: 30px;
	height: 30px;
	border-radius: 50%;
	border: none;
	background: var(--chip-bg);
	color: var(--text-2);
	font-size: 17px;
	line-height: 1;
	cursor: pointer;
	transition: transform .3s var(--ease-spring), background .3s var(--ease-out);
}

/* 视觉上仍是 30px 的小圆叉, 但把触摸热区撑到 48px(手机手指点得到) */
.modal-x::after {
	content: "";
	position: absolute;
	inset: -9px;
}

@media (hover: hover) and (pointer: fine) {
	.modal-x:hover { transform: rotate(90deg) scale(1.08); background: var(--glass-bg-strong); }
}

.copy-btn {
	margin-top: 14px;
	padding: 10px 22px;
	border: none;
	border-radius: 13px;
	/* 白字压在渐变上, 所以走 --grad-btn(深段)而不是 --grad, 见 token 处的说明 */
	background: var(--grad-btn);
	color: #fff;
	font-size: 14px;
	font-weight: 650;
	font-family: inherit;
	cursor: pointer;
	transition: transform .3s var(--ease-spring), box-shadow .3s var(--ease-out);
	box-shadow: 0 6px 18px rgba(10, 100, 240, .35);
}

@media (hover: hover) and (pointer: fine) {
	.copy-btn:hover { transform: translateY(-2px); box-shadow: 0 10px 26px rgba(10, 100, 240, .45); }
}
.copy-btn:active { transform: scale(.95); }

/* ---------- Toast ---------- */

.toast {
	position: fixed;
	left: 50%;
	bottom: calc(28px + env(safe-area-inset-bottom));
	transform: translateX(-50%) translateY(24px) scale(.9);
	z-index: 99;
	padding: 11px 20px;
	border-radius: 999px;
	background: var(--glass-bg-strong);
	-webkit-backdrop-filter: blur(20px);
	backdrop-filter: blur(20px);
	border: 1px solid var(--glass-border);
	box-shadow: var(--glass-shadow);
	color: var(--text);
	font-size: 13.5px;
	font-weight: 600;
	opacity: 0;
	pointer-events: none;
	transition: transform .45s var(--ease-spring), opacity .3s var(--ease-out);
}

.toast.show {
	opacity: 1;
	transform: translateX(-50%) translateY(0) scale(1);
}

/* ---------- 入场编排 / 滚动显现 ---------- */

/* ⚠️ 这两条**必须排除**账户卡与服务器大卡（都是自己要用 transform 做交互的）。
   用 :where() 包住 :not() 是为了让特异性一个字都不变
   （:where() 计 0 特异性，所以还是原来的 (0,2,0) / (0,3,0)）—— .dock 和 .identity
   自己没有 transform，入场完全靠这两条，不能受牵连。
   为什么要排除：账户卡的入场走的是独立属性 translate + opacity（见上面 .js .acct.reveal
   与 acctFade，那条注释明写了「不干扰倾斜 transform」）。可这两条写的是 transform、
   特异性 (0,3,0)、又排在 .acct:hover (0,2,0) 之后 —— 于是卡片一挂上 .in，
   transform 就被永久钉死在 translateY(0) scale(1) 上。
   后果：studio.js 一直在算 --rx/--ry，但 3D 倾斜、悬停抬升 translateY(-3px)、
   按压 scale(.985) 一次都没渲染出来。实测悬停时 getComputedStyle(card).transform
   是恒等矩阵、而 box-shadow 已经是悬停值 —— 这就是「效果明明写了却毫无反应」的根因。
   2026-09-26 补：服务器大卡（.server-frame）后来也加了 3D 视差，同样被这两条钉死，
   所以一并排除；它自己的入场改用独立属性 translate（见下面 .server-frame.reveal 那一段）。 */
.js .reveal:where(:not(.acct):not(.server-frame)) {
	opacity: 0;
	transform: translateY(26px) scale(.985);
}

.js .reveal.in:where(:not(.acct):not(.server-frame)) {
	opacity: 1;
	transform: translateY(0) scale(1);
	transition: opacity .9s var(--ease-out), transform .9s var(--ease-out);
	transition-delay: calc(var(--d, 0) * 110ms);
}

@keyframes acctFade {
	to { opacity: 1; translate: 0 0; }
}

/* ---------- 焦点与滚动条 ---------- */

:focus-visible {
	/* 用品牌 token，别硬编码 iOS 蓝：浅色主题下那个蓝和 --brand 不是一回事 */
	outline: 2px solid var(--brand);
	outline-offset: 3px;
	border-radius: 6px;
}

::-webkit-scrollbar { width: 10px; }
::-webkit-scrollbar-thumb {
	background: var(--glass-border);
	border-radius: 8px;
	border: 2px solid transparent;
	background-clip: content-box;
}
::-webkit-scrollbar-track { background: transparent; }

/* ---------- 响应式 ---------- */

@media (max-width: 960px) {
	.shell {
		grid-template-columns: minmax(0, 1fr);
		padding-bottom: 120px;
	}
	.column.side {
		position: static;
		flex-direction: row;
		flex-wrap: wrap;
	}
	.column.side > * { flex: 1 1 260px; }
	.identity { flex: 1 1 100% !important; }
	.grid { grid-template-columns: repeat(2, minmax(0, 1fr)); }
}

@media (max-width: 620px) {
	/* ⚠️ 两行都要写：[data-theme="dark"] 的特异性比 :root 高，
	   只改 :root 的话深色主题在手机上仍是桌面档的模糊半径 */
	:root, [data-theme="dark"] {
		--glass-blur: 13px;
	}
	.grid { grid-template-columns: minmax(0, 1fr); }
	.dock {
		position: fixed;
		left: 0;
		right: 0;
		bottom: calc(14px + env(safe-area-inset-bottom));
		margin: 0 auto;
		width: fit-content;
		z-index: 60;
		padding: 8px 10px;
		border-radius: 22px;
	}
	.dock-item { width: 48px; height: 48px; }
	.server-frame iframe { height: 280px; }
}

/* ---------- 弹窗打开时，把会动的背景停住 ----------
   全屏 backdrop-filter 对着会动的背景 = 每帧重新做一遍模糊（§12：材质要相对背景静止）。
   弹窗开着的时候没人看壁纸，停掉之后模糊结果能被缓存，弹窗期间几乎零开销。
   类由 studio.js 的 openModal / finish 加摘；视频是 JS 直接 pause 的。 */
html.is-modal-open .ambient-image--studio { animation-play-state: paused; }
html.is-modal-open .cursor-glow { opacity: 0; }

/* 切到后台时同样别烧电：kenburns 与字标的色阶流动都是常驻循环
   （.tab-hidden 挂在 <html> 上，见 interactions.js 的 initVisibilityPause） */
html.tab-hidden .ambient-image--studio,
html.tab-hidden .title .ltr { animation-play-state: paused; }

/* ---------- 无障碍: 减少动态效果 ---------- */

@media (prefers-reduced-motion: reduce) {
	.ambient-image--studio { animation: none; }
	#motes, .cursor-glow { display: none; }
	.identity::before, .sheen::after { display: none; }
	.js .reveal { opacity: 1; transform: none; transition: none; }
	.js .acct.reveal { animation: none; }
	.title { animation: none; }
	.js .title .ltr { opacity: 1; translate: none; animation: none; transition: none; }
	.js .title .ltr::after { animation: none; }
	.js .glass.reveal { clip-path: none; }
	.mini-pill { transition: none; }
	.acct { transition: none; transform: none !important; }
	.acct::after { display: none; }
	.dock-item, .skills li, .modal-card, .toast, .pulse::after { transition: none; animation: none; }
}

@media (prefers-reduced-transparency: reduce) {
	.glass, .glass-strong {
		background: var(--glass-bg-strong);
		backdrop-filter: none;
		-webkit-backdrop-filter: none;
	}
}

/* ============================================================================
   合并适配 —— 这一节里的东西在个人站原本的样式表里不存在，
   是「挂进共用骨架」这件事本身带出来的：顶部多了一条固定导航栏、
   身份卡徽章变成了可点的入口、服务器卡多了一个跨世界按钮。
   ============================================================================ */

/* ---------- 首屏压暗层：Studio 这一侧**整个关掉**（2026-09-26 站主要求「全透明」）----------
   global.css 里那条 `.hero::before`（Network 首页的「方向性压暗」）是给首页写的，
   但 Studio 的首屏也叫 .hero，于是被一起套上了。两个世界的首屏处境正相反：
   首页那层左边贴着屏幕边、背后是暗色的壁纸视频，它的边界看不出来；
   而 Studio 的首屏在页面中间、背后是被滤镜提亮过的亮天空 ——
   一块 alpha .84 的矩形杵在那儿，就是「文字背后一块带边的暗底」。

   来回记一下，免得下次又白折腾：
     · 第一轮站主说「这块大黑边和其他不契合」→ 当时按壁纸滤镜查，怎么调都没反应，
       后来才发现是这层借来的压暗层（不是壁纸的暗云），于是**把四条边全化开**；
     · 第二轮（09-26）站主看到化开之后**仍然是一块看得出来的暗底**（右缘/下缘是硬的，
       而且它整体把文字区压暗了），要求「就是全透明的」，对照原个人站那张图 ——
       那边 `.hero::before` 根本没有背景，字是直接压在壁纸上的。
   所以现在 Studio 这侧不画任何压暗层。它原来负责的「小字对比度」交回给文字自身
   （见 global.css 里 --text-dim 的取值）—— 别再为这个把压暗层加回来。

   ⚠️ 必须留这条 `background: none`，**不能把本节删掉**：`.hero` 这个类名两个世界共用，
   删了首页那层压暗会原样套到 Studio 上（这正是它当初跑过来的路径）。
   `.hero { overflow: visible }` 与压暗层无关、继续留着：global 的 `clip` 是给首页
   水印大字准备的，Studio 没有要裁的东西（实测 320/390/620/960 都没有横向溢出）。 */
.hero {
	overflow: visible;
}

.hero::before {
	background: none;
}

/* ---------- 迷你药丸要让开固定导航栏 ----------
   个人站原本没有顶部导航（它是底部 Dock），药丸就贴在 top:16px。
   现在顶上有一条 fixed 的导航栏，同一个位置会重叠 ——
   药丸是「大标题已经滚走了」的提示，被导航栏压住就失去意义。
   放到导航栏下方：导航在向下滚动时会自己隐藏，两者通常不会同时出现，
   但向上回滚、导航重新落下时，这个偏移保证它们不打架。 */
.mini-pill {
	top: calc(var(--nav-h) + 14px);
}

/* ---------- 身份卡上的服务器徽章：从静态文字变成跨世界入口 ----------
   原本是个 <span>，只显示在线状态；合并后它还是一个链接（点了回 Network 世界），
   所以要清掉链接的默认样式，并给出「可以点」的反馈。 */
a.chip {
	text-decoration: none;
	transition: border-color .22s var(--ease-out), background-color .22s var(--ease-out),
		transform .3s var(--ease-spring);
}

@media (hover: hover) and (pointer: fine) {
	a.chip:hover {
		border-color: color-mix(in srgb, var(--brand) 40%, transparent);
		background: color-mix(in srgb, var(--brand) 12%, var(--chip-bg));
		transform: translateY(-1px);
	}
}

a.chip:active { transform: scale(.96); }

/* 触摸目标：药丸本身只有 30px 高，手机上撑到 44px 的可点高度 */
@media (max-width: 768px) {
	a.chip {
		position: relative;
	}

	a.chip::after {
		content: "";
		position: absolute;
		left: -2px;
		right: -2px;
		top: -7px;
		bottom: -7px;
	}
}

/* ---------- 服务器卡的跨世界入口 ----------
   这张卡是 Studio 世界里最靠近「服务器」的地方，所以从个人站回服务器站
   最自然的落点就是这里，而不是让用户再回顶部点导航栏。 */
.server-visit {
	display: flex;
	align-items: center;
	justify-content: space-between;
	gap: 10px;
	margin-top: 14px;
	padding: 11px 14px;
	border-radius: var(--r-sm);
	border: 1px solid color-mix(in srgb, var(--brand) 26%, transparent);
	background: color-mix(in srgb, var(--brand) 9%, transparent);
	color: var(--text);
	font-size: 14px;
	font-weight: 600;
	text-decoration: none;
	transition: background-color .22s var(--ease-out), border-color .22s var(--ease-out),
		transform .3s var(--ease-spring);
}

.server-visit .mdi {
	font-size: 17px;
	color: var(--brand);
	transition: transform .3s var(--ease-out);
}

@media (hover: hover) and (pointer: fine) {
	.server-visit:hover {
		background: color-mix(in srgb, var(--brand) 15%, transparent);
		border-color: color-mix(in srgb, var(--brand) 44%, transparent);
		transform: translateY(-1px);
	}

	.server-visit:hover .mdi {
		transform: translateX(3px);
	}
}

.server-visit:active { transform: scale(.985); }

/* 手机上触摸目标补到 48px */
@media (max-width: 768px) {
	.server-visit { padding: 14px 15px; }
}
