初始化提交

This commit is contained in:
chenxin committed 2026-08-21 16:00:13 +08:00
commit 887c1f449e
963 files changed
+166869

No files matched your search

+981
View File
@@ -0,0 +1,981 @@
<!doctype html>
<html lang="zh-CN" data-theme="dark">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<title>魂镜 · 登入灵台</title>
<link rel="stylesheet" href="tokens.css" />
<link rel="stylesheet" href="_base.css" />
<link rel="stylesheet" href="_chrome.css" />
<style>
/* ============================================================
* 铜镜鉴心 v2.1 · 登入灵台
* 与 frontend/design/copper-mirror-redesign-v2.html 同源语言。
*
* 此页只做一件事:让用户以「照镜」的仪式感认证身份。
* 登录卡 = 抛光铜镜(唯一的镜面),其余为镜背暗纹。
* 星盘、铭文环、铜面噪点组成镜背的铸纹体量。
* 扫描亮带 = 铜镜在烛光下微倾时的漫反射(唯一动态事件)。
*
* 亮色模式绝非「暗色反相」——而是暖纸上陈列的铜镜,
* 保留鎏金发丝线、铜绿锈点缀、暖铜高光。
* ============================================================ */
/* ── 亮色:素绢暖纸上的铜镜 ── */
* {
box-sizing: border-box;
}
html,
body {
height: 100%;
}
body {
margin: 0;
background: var(--ink);
color: var(--fg);
font-family:
"PingFang SC",
"Microsoft YaHei",
-apple-system,
BlinkMacSystemFont,
system-ui,
sans-serif;
font-size: 15px;
line-height: 1.6;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
overflow: hidden;
display: flex;
flex-direction: column;
}
/* ═══════════════════════════════════════════════════
* 镜背基底
* ═══════════════════════════════════════════════════ */
/* 铜面噪点(feTurbulence — 金属颗粒感) */
.grain-overlay {
position: fixed;
inset: 0;
z-index: -1;
pointer-events: none;
width: 100%;
height: 100%;
}
[data-theme="dark"] .grain-overlay {
opacity: 0.035;
mix-blend-mode: soft-light;
}
[data-theme="light"] .grain-overlay {
opacity: 0.015;
mix-blend-mode: multiply;
}
/* 同心铭文轨 */
.inscription-rings {
position: fixed;
inset: 0;
z-index: -1;
pointer-events: none;
display: flex;
align-items: center;
justify-content: center;
}
.inscription-rings svg {
width: min(92vmin, 820px);
height: min(92vmin, 820px);
}
[data-theme="dark"] .inscription-rings svg {
opacity: 0.18;
}
[data-theme="light"] .inscription-rings svg {
opacity: 0.1;
}
.inscription-rings .ring-ink {
fill: none;
stroke: var(--gilt-faint);
stroke-width: 0.5;
}
.inscription-rings .ring-faint {
fill: none;
stroke: var(--gilt-faint);
stroke-width: 0.3;
stroke-dasharray: 2 6;
}
/* ═══════════════════════════════════════════════════
* 顶栏
* ═══════════════════════════════════════════════════ */
.topbar {
position: fixed;
top: 0;
left: 0;
right: 0;
z-index: 40;
display: flex;
align-items: center;
gap: 16px;
padding: 14px clamp(20px, 4vw, 48px);
background: color-mix(in srgb, var(--ink) 76%, transparent);
backdrop-filter: blur(16px) saturate(140%);
-webkit-backdrop-filter: blur(16px) saturate(140%);
}
.topbar .spacer {
flex: 1;
}
.pill {
display: inline-flex;
align-items: center;
gap: 7px;
padding: 6px 14px;
border-radius: 999px;
border: 1px solid var(--rule);
background: var(--surface);
color: var(--fg-dim);
font-size: 11.5px;
cursor: pointer;
transition:
color 0.18s,
border-color 0.18s,
background 0.18s;
font-family: inherit;
}
.pill:hover {
color: var(--fg);
border-color: var(--gilt-dim);
background: var(--surface-hi);
}
.pill:focus-visible {
outline: 2px solid var(--patina);
outline-offset: 2px;
}
/* ═══════════════════════════════════════════════════
* 右上角星盘(活的仪器 — 140s 慢旋转)
* ═══════════════════════════════════════════════════ */
.mini-astrolabe {
position: absolute;
right: clamp(24px, 5vw, 72px);
top: clamp(80px, 12vh, 120px);
width: 120px;
height: 120px;
pointer-events: none;
}
[data-theme="dark"] .mini-astrolabe {
opacity: 0.25;
}
[data-theme="light"] .mini-astrolabe {
opacity: 0.18;
}
.mini-astrolabe svg {
width: 100%;
height: 100%;
}
.astrolabe-rotating {
transform-origin: center;
animation: slow-rot 140s linear infinite;
}
@keyframes slow-rot {
to {
transform: rotate(360deg);
}
}
@media (max-width: 700px) {
.mini-astrolabe {
display: none;
}
}
/* ═══════════════════════════════════════════════════
* LOGO · 铜镜徽(品牌标志,替代朱漆红点)
* ═══════════════════════════════════════════════════ */
.logo-mark {
display: block;
width: 68px;
height: 68px;
margin: 0 auto 16px;
}
.logo-mark .mirror-ring {
fill: none;
stroke: var(--gilt);
stroke-width: 1.4;
opacity: 0.65;
}
.logo-mark .mirror-inner {
fill: none;
stroke: var(--patina-dim);
stroke-width: 0.8;
opacity: 0.45;
}
.logo-mark .char-ring {
fill: none;
stroke: var(--gilt-dim);
stroke-width: 0.5;
opacity: 0.35;
}
.logo-mark .char {
font-family: "Songti SC", serif;
font-size: 30px;
fill: var(--gilt);
font-weight: 700;
opacity: 0.8;
}
.logo-mark .dot {
fill: var(--gilt);
opacity: 0.35;
}
.logo-mark .fill-surface {
fill: var(--patina-wash);
opacity: 0.3;
}
/* ═══════════════════════════════════════════════════
* 主舞台
* ═══════════════════════════════════════════════════ */
.stage {
flex: 1;
display: flex;
align-items: center;
justify-content: center;
padding: 80px clamp(20px, 4vw, 48px) 48px;
position: relative;
z-index: 1;
}
/* ═══════════════════════════════════════════════════
* 镜面卡 · 抛光铜镜
* ═══════════════════════════════════════════════════ */
.mirror-card {
position: relative;
width: 100%;
max-width: 400px;
background: var(--face-sheen), var(--surface);
border: 1px solid var(--gilt-dim);
border-radius: 6px;
box-shadow:
inset 0 1px 0 var(--face-in-top),
inset 0 -1px 0 var(--face-in-bot),
var(--shadow);
overflow: hidden;
}
/* 扫描光泽 —— 铜镜在光源前微倾时的漫反射滑移 */
.mirror-card::before {
content: "";
position: absolute;
inset: 0;
z-index: 0;
pointer-events: none;
background-size: 200% 200%;
background-position: 200% 200%;
animation: mirror-sweep 9s ease-in-out infinite;
}
[data-theme="dark"] .mirror-card::before {
background: linear-gradient(
130deg,
transparent 0%,
transparent 28%,
rgba(201, 162, 74, 0.025) 34%,
rgba(255, 255, 255, 0.06) 40%,
rgba(201, 162, 74, 0.025) 48%,
transparent 55%,
transparent 100%
);
}
[data-theme="light"] .mirror-card::before {
background: linear-gradient(
130deg,
transparent 0%,
transparent 24%,
rgba(200, 170, 130, 0.06) 30%,
rgba(255, 248, 235, 0.18) 36%,
rgba(200, 170, 130, 0.1) 44%,
transparent 52%,
transparent 100%
);
}
@keyframes mirror-sweep {
0% {
background-position: 200% 200%;
}
40% {
background-position: 50% 50%;
}
60% {
background-position: 50% 50%;
}
100% {
background-position: -100% -100%;
}
}
.card-inner {
position: relative;
z-index: 1;
padding: 44px 40px 36px;
}
/* —— 品牌区 —— */
.brand {
text-align: center;
margin-bottom: 32px;
}
.brand .wordmark-lg {
display: block;
font-family: "Songti SC", serif;
font-size: clamp(26px, 4vw, 30px);
font-weight: 700;
letter-spacing: 0.14em;
color: var(--fg);
margin-top: 2px;
}
.brand .eyebrow {
display: block;
font-family: "JetBrains Mono", monospace;
font-size: 9px;
letter-spacing: 0.26em;
text-transform: uppercase;
color: var(--gilt);
font-weight: 500;
margin-top: 8px;
}
.brand .divider {
display: block;
width: 24px;
height: 1px;
background: var(--gilt-dim);
margin: 14px auto 0;
}
/* —— 表单 —— */
.form {
display: flex;
flex-direction: column;
gap: 16px;
}
.field-group {
display: flex;
flex-direction: column;
gap: 6px;
}
.field-group label {
font-size: 11.5px;
font-weight: 500;
letter-spacing: 0.04em;
color: var(--fg-dim);
}
.field-group .input-wrap {
position: relative;
display: flex;
align-items: center;
border: 1px solid var(--rule);
border-radius: 4px;
transition:
border-color 0.2s,
box-shadow 0.2s;
}
[data-theme="dark"] .field-group .input-wrap {
background: rgba(0, 0, 0, 0.3);
}
[data-theme="light"] .field-group .input-wrap {
background: rgba(255, 248, 235, 0.5);
}
.field-group .input-wrap:focus-within {
border-color: var(--gilt-dim);
box-shadow:
0 0 0 1px var(--gilt-faint),
inset 0 0 0 1px var(--gilt-faint);
}
.field-group .input-wrap.error {
border-color: var(--cinnabar-dim);
box-shadow: 0 0 0 1px var(--cinnabar-dim);
}
.field-group .input-wrap.error:focus-within {
border-color: var(--cinnabar);
box-shadow: 0 0 0 1px
color-mix(in srgb, var(--cinnabar) 55%, transparent);
}
.field-group input {
flex: 1;
font-family: inherit;
font-size: 14px;
color: var(--fg);
background: transparent;
border: none;
outline: none;
padding: 11px 13px;
min-width: 0;
}
.field-group input::placeholder {
color: var(--fg-faint);
font-size: 13px;
}
.field-group .input-icon {
flex: none;
width: 36px;
display: flex;
align-items: center;
justify-content: center;
color: var(--fg-faint);
opacity: 0.5;
transition: opacity 0.2s;
}
.field-group .input-wrap:focus-within .input-icon {
opacity: 0.85;
color: var(--gilt);
}
/* —— 错误消息 —— */
.form-error {
display: flex;
align-items: center;
gap: 8px;
padding: 9px 13px;
border: 1px solid var(--cinnabar-dim);
border-radius: 4px;
font-size: 12.5px;
color: var(--cinnabar);
}
[data-theme="dark"] .form-error {
background: rgba(184, 57, 46, 0.07);
}
[data-theme="light"] .form-error {
background: rgba(168, 52, 42, 0.06);
}
.form-error svg {
flex: none;
}
/* —— 提交按钮 —— */
.submit-btn {
display: flex;
align-items: center;
justify-content: center;
gap: 8px;
width: 100%;
padding: 12px 20px;
margin-top: 4px;
font-family: "Songti SC", serif;
font-size: 15px;
font-weight: 600;
letter-spacing: 0.1em;
color: #ece6d8;
background: var(--cinnabar);
border: 1px solid var(--cinnabar-dim);
border-radius: 4px;
cursor: pointer;
transition:
background 0.2s,
border-color 0.2s,
transform 0.15s cubic-bezier(0.16, 1, 0.3, 1),
box-shadow 0.2s;
}
.submit-btn:hover {
background: color-mix(in srgb, var(--cinnabar) 88%, var(--patina));
border-color: var(--cinnabar);
box-shadow: 0 0 20px var(--cinnabar-dim);
}
.submit-btn:active {
transform: scale(0.98);
}
.submit-btn:focus-visible {
outline: 2px solid var(--patina);
outline-offset: 2px;
}
.submit-btn:disabled {
opacity: 0.55;
cursor: not-allowed;
box-shadow: none;
transform: none !important;
}
.submit-btn .loading-dot {
display: inline-block;
width: 7px;
height: 7px;
border-radius: 50%;
background: currentColor;
animation: load-pulse 1.4s ease-in-out infinite;
}
@keyframes load-pulse {
0%,
100% {
opacity: 0.35;
transform: scale(0.85);
}
50% {
opacity: 1;
transform: scale(1);
}
}
/* —— 脚注 —— */
.card-footer {
margin-top: 32px;
text-align: center;
padding-top: 16px;
border-top: 1px solid var(--rule);
}
.card-footer p {
font-size: 9.5px;
letter-spacing: 0.22em;
color: var(--fg-faint);
margin: 0;
}
/* ═══════════════════════════════════════════════════
* 入场动画序列
* ═══════════════════════════════════════════════════ */
.mirror-card {
opacity: 0;
transform: translateY(18px);
animation: card-enter 0.8s cubic-bezier(0.16, 1, 0.3, 1) forwards;
animation-delay: 0.12s;
}
@keyframes card-enter {
to {
opacity: 1;
transform: translateY(0);
}
}
.brand > * {
opacity: 0;
transform: translateY(6px);
animation: stagger-in 0.5s cubic-bezier(0.16, 1, 0.3, 1) forwards;
}
.brand .logo-mark {
animation-delay: 0.22s;
}
.brand .wordmark-lg {
animation-delay: 0.3s;
}
.brand .eyebrow {
animation-delay: 0.38s;
}
.brand .divider {
animation-delay: 0.46s;
}
.form > * {
opacity: 0;
animation: stagger-in 0.4s cubic-bezier(0.16, 1, 0.3, 1) forwards;
}
.form .field-group:nth-child(1) {
animation-delay: 0.5s;
}
.form .field-group:nth-child(2) {
animation-delay: 0.58s;
}
.form .submit-btn {
animation-delay: 0.66s;
}
.card-footer {
animation-delay: 0.74s;
opacity: 0;
animation: stagger-in 0.4s cubic-bezier(0.16, 1, 0.3, 1) forwards;
}
@keyframes stagger-in {
to {
opacity: 1;
transform: translateY(0);
}
}
.mirror-card.departing {
animation: card-depart 0.5s cubic-bezier(0.55, 0, 0.7, 0.3) forwards;
}
@keyframes card-depart {
to {
opacity: 0;
transform: translateY(-10px) scale(0.97);
filter: blur(2px);
}
}
/* ═══════════════════════════════════════════════════
* Reduced Motion
* ═══════════════════════════════════════════════════ */
.mirror-card {
opacity: 1 !important;
transform: none !important;
animation: none !important;
}
.mirror-card::before {
display: none !important;
}
.brand > *,
.form > *,
.card-footer {
opacity: 1 !important;
transform: none !important;
animation: none !important;
}
.astrolabe-rotating {
animation: none !important;
}
}
/* ═══ 统一 sidebar(6 组导航)═══ */
.siming { color: var(--cinnabar); }
@media (max-width: 900px) { .sidebar-wrap { display: none; } }
</style>
</head>
<body>
<div class="app-shell">
<div class="sidebar-mount" data-active=""></div>
<div class="main-area">
<!-- 铜面噪点 -->
<svg
class="grain-overlay"
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 200 200"
preserveAspectRatio="none"
>
<filter id="bronze-grain" x="0" y="0" width="100%" height="100%">
<feTurbulence
type="fractalNoise"
baseFrequency="0.75"
numOctaves="3"
seed="42"
result="noise"
/>
<feColorMatrix
type="matrix"
values="1 0 0 0 0 0 1 0 0 0 0 0 1 0 0 0 0 0 0.25 0"
in="noise"
result="colored"
/>
</filter>
<rect width="100%" height="100%" filter="url(#bronze-grain)" />
</svg>
<!-- 同心铭文轨 -->
<div class="inscription-rings">
<svg viewBox="0 0 400 400" role="img" aria-hidden="true">
<circle cx="200" cy="200" r="195" class="ring-ink" />
<circle cx="200" cy="200" r="185" class="ring-faint" />
<circle cx="200" cy="200" r="170" class="ring-ink" />
<circle cx="200" cy="200" r="155" class="ring-faint" />
<circle cx="200" cy="200" r="135" class="ring-ink" opacity="0.3" />
<circle cx="200" cy="200" r="110" class="ring-faint" opacity="0.2" />
<circle cx="200" cy="200" r="80" class="ring-ink" opacity="0.15" />
</svg>
</div>
<!-- 星盘装饰(140s 慢旋转) -->
<div class="mini-astrolabe" role="img" aria-label="九枢星盘">
<svg viewBox="0 0 100 100">
<!-- 静止外环 -->
<circle
cx="50"
cy="50"
r="48"
fill="none"
stroke="var(--gilt-dim)"
stroke-width="0.6"
/>
<circle
cx="50"
cy="50"
r="42"
fill="none"
stroke="var(--gilt-faint)"
stroke-width="0.3"
stroke-dasharray="1.5 3"
/>
<!-- 旋转层 -->
<g class="astrolabe-rotating">
<circle
cx="50"
cy="50"
r="33"
fill="none"
stroke="var(--patina-dim)"
stroke-width="0.5"
/>
<circle
cx="50"
cy="50"
r="24"
fill="none"
stroke="var(--gilt-faint)"
stroke-width="0.4"
stroke-dasharray="1 4"
/>
<!-- 辐线 -->
<g stroke="var(--gilt-faint)" stroke-width="0.3" opacity="0.5">
<line x1="50" y1="17" x2="50" y2="4" />
<line x1="83" y1="30" x2="93" y2="22" />
<line x1="93" y1="62" x2="97" y2="68" />
<line x1="74" y1="81" x2="80" y2="89" />
<line x1="32" y1="82" x2="26" y2="90" />
<line x1="14" y1="56" x2="6" y2="60" />
<line x1="18" y1="26" x2="10" y2="20" />
</g>
<!-- 节点 -->
<g fill="var(--gilt)" opacity="0.4">
<circle cx="50" cy="12" r="1.8" />
<circle cx="82" cy="28" r="1.5" />
<circle cx="88" cy="56" r="1.6" />
<circle cx="68" cy="80" r="1.4" />
<circle cx="34" cy="78" r="1.5" />
<circle cx="14" cy="52" r="1.7" />
<circle cx="22" cy="24" r="1.4" />
</g>
</g>
<!-- 中心轴 -->
<circle
cx="50"
cy="50"
r="3.5"
fill="var(--patina-bright)"
opacity="0.5"
/>
<circle cx="50" cy="50" r="1.5" fill="var(--patina)" opacity="0.7" />
</svg>
</div>
<!-- 顶栏 -->
<header class="topbar">
<span class="spacer"></span>
<button class="pill" id="themeToggle" type="button" aria-label="切换主题">
<svg
width="12"
height="12"
viewBox="0 0 16 16"
fill="none"
aria-hidden="true"
>
<path
d="M8 1v2M8 13v2M2 8h2M12 8h2M3.5 3.5l1.4 1.4M11.1 11.1l1.4 1.4M3.5 12.5l1.4-1.4M11.1 4.9l1.4-1.4"
stroke="currentColor"
stroke-width="1.2"
stroke-linecap="round"
/>
<circle
cx="8"
cy="8"
r="3"
stroke="currentColor"
stroke-width="1.2"
/>
</svg>
<span id="themeLabel">夜铜</span>
</button>
</header>
<!-- 主舞台 -->
<main class="stage" role="main">
<div class="mirror-card" id="mirrorCard">
<div class="card-inner">
<!-- 品牌区 · 铜镜徽 + 魂镜 + 铭文 -->
<div class="brand">
<svg
class="logo-mark"
viewBox="0 0 68 68"
role="img"
aria-label="魂镜铜镜徽"
>
<circle cx="34" cy="34" r="32" class="mirror-ring" />
<circle cx="34" cy="34" r="27" class="mirror-inner" />
<circle cx="34" cy="34" r="22" class="char-ring" />
<circle cx="34" cy="34" r="15" class="char-ring" />
<circle
cx="34"
cy="34"
r="8"
fill="var(--patina-wash)"
opacity="0.3"
/>
<text x="34" y="40" text-anchor="middle" class="char">魂</text>
<circle cx="34" cy="6" r="1.5" class="dot" />
<circle cx="34" cy="62" r="1.5" class="dot" />
<circle cx="6" cy="34" r="1.5" class="dot" />
<circle cx="62" cy="34" r="1.5" class="dot" />
</svg>
<span class="wordmark-lg">魂鏡</span>
<span class="eyebrow">MIRROR OF THE HEART</span>
<span class="divider" aria-hidden="true"></span>
</div>
<!-- 表单 -->
<form class="form" id="loginForm" novalidate autocomplete="off">
<div class="field-group">
<label for="username">用户名</label>
<div class="input-wrap">
<span class="input-icon mono" aria-hidden="true">◎</span>
<input
id="username"
type="text"
placeholder="铜镜所照之名"
autocomplete="username"
required
/>
</div>
</div>
<div class="field-group">
<label for="password">密令</label>
<div class="input-wrap">
<span class="input-icon mono" aria-hidden="true">◉</span>
<input
id="password"
type="password"
placeholder="铭文锁钥"
autocomplete="current-password"
required
/>
</div>
</div>
<!-- 错误消息 -->
<div
id="formError"
class="form-error"
role="alert"
style="display: none"
>
<svg
width="13"
height="13"
viewBox="0 0 16 16"
fill="none"
aria-hidden="true"
>
<circle
cx="8"
cy="8"
r="6"
stroke="currentColor"
stroke-width="1.2"
/>
<path
d="M8 5v3M8 10.5v.5"
stroke="currentColor"
stroke-width="1.3"
stroke-linecap="round"
/>
</svg>
<span id="errorText"></span>
</div>
<button type="submit" class="submit-btn" id="submitBtn">
<span id="submitText">登入</span>
</button>
</form>
<!-- 脚注 -->
<div class="card-footer">
<p class="mono">琉璃镜 · 照见数字生命</p>
</div>
</div>
</div>
</main>
<script>
/* ── 主题切换 ── */
const root = document.documentElement;
const label = document.getElementById("themeLabel");
document.getElementById("themeToggle").addEventListener("click", () => {
const n = root.getAttribute("data-theme") === "dark" ? "light" : "dark";
root.setAttribute("data-theme", n);
label.textContent = n === "dark" ? "夜铜" : "素绢";
});
/* ── 表单交互 ── */
const form = document.getElementById("loginForm");
const user = document.getElementById("username");
const pass = document.getElementById("password");
const btn = document.getElementById("submitBtn");
const txt = document.getElementById("submitText");
const err = document.getElementById("formError");
const et = document.getElementById("errorText");
function showError(m) {
et.textContent = m;
err.style.display = "flex";
document
.querySelectorAll(".input-wrap")
.forEach((w) => w.classList.add("error"));
err.style.animation = "none";
err.offsetHeight;
err.style.animation = "shake .35s cubic-bezier(.16,1,.3,1)";
}
function hideError() {
err.style.display = "none";
document
.querySelectorAll(".input-wrap")
.forEach((w) => w.classList.remove("error"));
}
user.addEventListener("input", hideError);
pass.addEventListener("input", hideError);
const ss = document.createElement("style");
ss.textContent = `@keyframes shake{0%,100%{transform:translateX(0)}25%{transform:translateX(-4px)}50%{transform:translateX(4px)}75%{transform:translateX(-2px)}}`;
document.head.appendChild(ss);
form.addEventListener("submit", async (e) => {
e.preventDefault();
hideError();
if (!user.value.trim() || !pass.value) {
showError("请填写用户名和密令。");
return;
}
btn.disabled = true;
txt.innerHTML = '登入灵台<span class="loading-dot"></span>';
await new Promise((r) => setTimeout(r, 1500));
const t = parseInt(sessionStorage.getItem("t") || "0");
sessionStorage.setItem("t", String(t + 1));
if (t === 0) {
showError("用户名或密令错误,铜镜不识此名。");
btn.disabled = false;
txt.textContent = "登入";
return;
}
const c = document.getElementById("mirrorCard");
c.classList.add("departing");
txt.textContent = "镜开灵现";
await new Promise((r) => setTimeout(r, 600));
txt.textContent = "登入";
btn.disabled = false;
c.classList.remove("departing");
sessionStorage.removeItem("t");
showError("演示:登录成功");
err.style.border = "1px solid var(--patina-dim)";
err.style.background = "var(--patina-wash)";
err.style.color = "var(--patina)";
const ic = err.querySelector("svg");
if (ic) ic.style.display = "none";
});
</script>
</div>
</div>
<script src="_theme.js" defer></script>
</body>
</html>