PersonalWebApplication/Client/src/index.css
cc 8d89cee451 @
feat(photo-wall): 修复4个问题 + 特殊文件夹(共享公共相册)

**照片墙修复:**
- 右键菜单 z-index 40→9999,不再被元素遮挡
- 图片放大后强制重建 DOM(img key 绑定 w/h)
- 右键菜单添加滑条调节旋转步长(1°-90°)和缩放步长(照片 px / 其他 scale)
- Delete 键可删除右键菜单选中的元素(便签也能删)

**特殊文件夹功能(按设计文档实现):**
- 后端:FolderRegistry 解析 special_folders.txt 注册表 + FolderStore 按文件夹存图
- 后端:5 个接口(存在性检查/列表/上传/获取/删除),白名单 + 管理员鉴权
- 前端:App.tsx 按 hash 路由到特殊文件夹,复用 PhotoWallPage
- 前端:文件夹模式无张数上限,localStorage 按文件夹隔离
- 前端:管理员配额警告条(超 1GiB 时显示)
- 前端:403 无权访问时退回起始页

Co-Authored-By: Claude <noreply@anthropic.com>
@
2026-06-24 22:19:29 +08:00

592 lines
18 KiB
CSS
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

/* ── Fonts (本地打包,离线/WSL 可用) ───────────────────────── */
@import '@fontsource-variable/fraunces/opsz.css';
@import '@fontsource-variable/fraunces/opsz-italic.css';
@import '@fontsource/hanken-grotesk/300.css';
@import '@fontsource/hanken-grotesk/400.css';
@import '@fontsource/hanken-grotesk/500.css';
@import '@fontsource/hanken-grotesk/600.css';
@import 'tailwindcss';
/* ── Design tokens暖调暗色编辑风──────────────────────────── */
@theme {
--color-bg: #17130f;
--color-surface: #221c16;
--color-text: #eae2d4;
--color-muted: #9b8f7e;
--color-accent: #c99a5b;
--color-accent-soft: #e0bd8a;
--color-line: #3a3027;
--font-display: 'Fraunces Variable', ui-serif, Georgia, serif;
--font-sans: 'Hanken Grotesk', system-ui, -apple-system, sans-serif;
}
/* ── Base ────────────────────────────────────────────────── */
html {
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
text-rendering: optimizeLegibility;
}
body {
margin: 0;
min-height: 100svh;
background-color: var(--color-bg);
color: var(--color-text);
font-family: var(--font-sans);
font-weight: 400;
/* 隐藏系统光标,由 CursorFollower 接管(仅精确指针设备)*/
}
@media (pointer: fine) {
body.has-custom-cursor,
body.has-custom-cursor * {
cursor: none;
}
}
/* 全站文字默认不可选;输入框/文本域/可编辑区例外,加 data-selectable 可单独放开 */
body {
-webkit-user-select: none;
user-select: none;
}
input,
textarea,
[contenteditable='true'],
[data-selectable] {
-webkit-user-select: text;
user-select: text;
}
/* 选中文本配色 */
::selection {
background: var(--color-accent);
color: var(--color-bg);
}
/* 可见键盘焦点环 */
:focus-visible {
outline: 2px solid var(--color-accent);
outline-offset: 2px;
}
/* ── 颗粒噪点(叠加在氛围面板上)─────────────────────────────── */
.grain::before {
content: '';
position: absolute;
inset: 0;
pointer-events: none;
opacity: 0.06;
background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='160' height='160'%3E%3Cfilter id='n'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='0.9' numOctaves='3' stitchTiles='stitch'/%3E%3C/filter%3E%3Crect width='100%25' height='100%25' filter='url(%23n)'/%3E%3C/svg%3E");
mix-blend-mode: overlay;
}
/* ── 动态渐变 mesh缓慢漂移───────────────────────────────── */
@keyframes drift {
0% { transform: translate3d(-6%, -4%, 0) scale(1.15) rotate(0deg); }
50% { transform: translate3d(6%, 4%, 0) scale(1.25) rotate(8deg); }
100% { transform: translate3d(-6%, -4%, 0) scale(1.15) rotate(0deg); }
}
.mesh {
position: absolute;
inset: -25%;
background:
radial-gradient(40% 45% at 25% 30%, rgba(201, 154, 91, 0.45), transparent 70%),
radial-gradient(35% 40% at 75% 65%, rgba(140, 96, 64, 0.40), transparent 70%),
radial-gradient(45% 50% at 60% 20%, rgba(90, 70, 52, 0.40), transparent 70%);
filter: blur(40px);
animation: drift 26s ease-in-out infinite;
will-change: transform;
}
/* ── 标题下划线闪烁游标 ──────────────────────────────────────── */
@keyframes blink {
0%, 45% { opacity: 1; }
50%, 95% { opacity: 0; }
100% { opacity: 1; }
}
.cursor-bar {
animation: blink 1.15s steps(1, end) infinite;
}
/* ── 彩虹(仿 joshwcomeau─────────────────────────────────────
一条横向流动的多色渐变。首尾同色,配合 200% 宽度做无缝平移。
既可裁切到文字,也可铺在装饰条上。 */
@keyframes rainbow-pan {
to {
background-position: 200% center;
}
}
.rainbow-gradient {
background-image: linear-gradient(
90deg,
#ff5f6d,
#ffb13d,
#ffe14d,
#4ade80,
#38bdf8,
#a78bfa,
#f472b6,
#ff5f6d
);
background-size: 200% auto;
animation: rainbow-pan 6s linear infinite;
}
/* 把流动彩虹裁切进文字(始终显示彩虹色) */
.rainbow-text {
background-image: linear-gradient(
90deg,
#ff5f6d,
#ffb13d,
#ffe14d,
#4ade80,
#38bdf8,
#a78bfa,
#f472b6,
#ff5f6d
);
background-size: 200% auto;
-webkit-background-clip: text;
background-clip: text;
color: transparent;
animation: rainbow-pan 6s linear infinite;
}
/* 把彩虹裁进文字但不强制颜色:默认用不透明文字色盖住,
hover 时把文字色设为 transparentgroup-hover:text-transparent即露出彩虹。 */
.rainbow-clip {
background-image: linear-gradient(
90deg,
#ff5f6d,
#ffb13d,
#ffe14d,
#4ade80,
#38bdf8,
#a78bfa,
#f472b6,
#ff5f6d
);
background-size: 200% auto;
-webkit-background-clip: text;
background-clip: text;
animation: rainbow-pan 6s linear infinite;
}
/* ── 整页渐变极光背景(玻璃栏透出的就是它)──────────────────────
固定铺满视口、置于所有内容之下。底色为 Lunada 渐变
uigradients「Lunada」#A7BFE8 → #6190E8上面叠几团同色系
蓝光斑缓慢漂移并大幅模糊,让平面渐变产生液态流动感。 */
.aurora {
position: fixed;
inset: 0;
z-index: -10;
overflow: hidden;
/* Lunada 渐变打底 */
background: linear-gradient(135deg, #6190e8 0%, #89a8e6 48%, #a7bfe8 100%);
}
.aurora::before {
content: '';
position: absolute;
inset: -25%;
background:
radial-gradient(30% 34% at 18% 28%, rgba(97, 144, 232, 0.65), transparent 70%),
radial-gradient(28% 32% at 82% 24%, rgba(167, 191, 232, 0.70), transparent 70%),
radial-gradient(34% 38% at 72% 78%, rgba(120, 162, 236, 0.60), transparent 70%),
radial-gradient(26% 30% at 26% 82%, rgba(192, 211, 245, 0.55), transparent 70%),
radial-gradient(24% 28% at 50% 50%, rgba(140, 175, 240, 0.45), transparent 70%);
filter: blur(60px);
/* 静止:持续漂移会让上层多个 backdrop-filter 每帧重算模糊,是卡顿主因 */
}
/* 中间内容纸张:压暗的暖色玻璃(模糊背后极光保证可读),
两侧各一道高光描边,与左右玻璃竖栏拼出连续的玻璃接缝。 */
.glass-sheet {
/* 底色 84% 不透明,背后极光本就几乎透不过来;省掉昂贵的 backdrop-filter
观感基本无差,却避免这条又高又大的区域每帧重算模糊。 */
background: rgba(23, 19, 15, 0.84);
box-shadow:
inset 1px 0 0 rgba(255, 255, 255, 0.12),
inset -1px 0 0 rgba(255, 255, 255, 0.12),
0 30px 80px rgba(20, 30, 60, 0.30);
}
/* ── 起始页玻璃面板(极简浅色玻璃,叠在背景图上)──────────────
与暗色 .glass-sheet 不同:这里要真模糊,让背后照片透出液态质感。
半透明白 + 细高光描边 + 适度饱和,放在任何背景图上都成立。 */
.glass-panel {
background: rgba(255, 255, 255, 0.10);
border: 1px solid rgba(255, 255, 255, 0.16);
box-shadow:
inset 0 1px 0 rgba(255, 255, 255, 0.22),
0 10px 30px rgba(0, 0, 0, 0.22);
/* blur 半径直接决定每帧重算模糊的成本(∝ 半径×面积),高分屏下尤甚;
20px→10px 观感几乎无差,开销减半。见 .glass-sheet 处的同类取舍。 */
backdrop-filter: blur(10px) saturate(140%);
-webkit-backdrop-filter: blur(10px) saturate(140%);
}
/* ── reduced-motion 降级 ────────────────────────────────────── */
@media (prefers-reduced-motion: reduce) {
.mesh,
.cursor-bar,
.rainbow-gradient,
.rainbow-text,
.rainbow-clip,
.aurora::before {
animation: none;
}
}
/* ══════════════════════════════════════════════════════════════
照片墙 Photo Wall地址栏 #photo → 登录后进入的互动照片墙)
纯美术与画布交互,命名空间 .pw-,避免与全站令牌冲突。
══════════════════════════════════════════════════════════════ */
.pw-root {
position: fixed;
inset: 0;
overflow: hidden;
--pw-tb: 56px;
font-family: var(--font-sans);
color: #333;
}
/* ── 顶部工具栏:半透明磨砂,与画布视觉区分 ──────────────────── */
.pw-toolbar {
position: absolute;
top: 0;
left: 0;
right: 0;
height: var(--pw-tb);
display: flex;
align-items: center;
gap: 4px;
padding: 0 14px;
background: rgba(255, 255, 255, 0.85);
-webkit-backdrop-filter: blur(12px);
backdrop-filter: blur(12px);
border-bottom: 1px solid rgba(0, 0, 0, 0.06);
z-index: 30;
}
.pw-logo {
font-weight: 700;
font-size: 16px;
margin-right: 12px;
white-space: nowrap;
}
.pw-tbtn {
border: none;
background: transparent;
font: inherit;
font-size: 13.5px;
color: #333;
padding: 7px 11px;
border-radius: 8px;
cursor: pointer;
white-space: nowrap;
transition: background 0.1s ease;
}
.pw-tbtn:hover { background: #f5f5f5; }
.pw-tbtn:active { background: #eee; }
.pw-tbtn.pw-danger { color: #ef5350; }
.pw-tbtn.pw-danger:hover { background: #ffebee; }
.pw-save {
font-size: 12.5px;
color: #888;
padding: 0 8px;
min-width: 56px;
text-align: right;
white-space: nowrap;
}
/* 下拉 / 右键菜单共用纸片样式 */
.pw-pop {
background: #fff;
border: 1px solid #e0e0e0;
border-radius: 8px;
box-shadow: 0 8px 28px rgba(0, 0, 0, 0.16);
padding: 6px;
}
.pw-item {
padding: 8px 12px;
border-radius: 6px;
cursor: pointer;
font-size: 13.5px;
display: flex;
justify-content: space-between;
gap: 18px;
white-space: nowrap;
}
.pw-item:hover { background: #f5f5f5; }
.pw-item.pw-danger { color: #ef5350; }
.pw-item.pw-danger:hover { background: #ffebee; }
.pw-item .pw-kbd { color: #aaa; font-size: 11.5px; }
.pw-sep { height: 1px; background: #eee; margin: 5px 4px; }
.pw-grid {
display: grid;
grid-template-columns: repeat(2, 1fr);
gap: 4px;
padding: 2px;
}
.pw-grid .pw-item { text-align: center; padding: 7px 6px; justify-content: center; }
.pw-rowin { display: flex; gap: 4px; padding: 6px 2px; }
.pw-rowin input {
flex: 1;
min-width: 0;
border: 1px solid #ddd;
border-radius: 6px;
padding: 6px 8px;
font: inherit;
font-size: 13px;
color: #333;
}
.pw-rowin button {
border: none;
background: #444;
color: #fff;
border-radius: 6px;
padding: 0 12px;
cursor: pointer;
font-size: 13px;
}
.pw-colorrow { display: flex; gap: 6px; padding: 6px 8px; }
.pw-preset { display: flex; align-items: center; gap: 5px; padding: 5px 8px; flex-wrap: wrap; }
.pw-preset-label { font-size: 12px; color: #888; width: 28px; }
.pw-chip {
border: 1px solid #e0e0e0; background: #fff; border-radius: 12px;
padding: 3px 9px; font-size: 12px; cursor: pointer; color: #444;
}
.pw-chip:hover { background: #f5f5f5; }
.pw-sw {
width: 20px;
height: 20px;
border-radius: 50%;
cursor: pointer;
border: 1px solid rgba(0, 0, 0, 0.1);
}
.pw-menu { position: fixed; z-index: 9999; transform-origin: top left; animation: pwMenuIn 0.12s ease; }
@keyframes pwMenuIn {
from { opacity: 0; transform: scale(0.95); }
to { opacity: 1; transform: scale(1); }
}
/* 右键菜单内的滑条行 */
.pw-slider-row { display: flex; align-items: center; gap: 8px; padding: 6px 12px; }
.pw-slider-row .pw-preset-label { min-width: 56px; font-size: 13px; color: #555; }
.pw-slider-row input[type="range"] { flex: 1; height: 4px; accent-color: #3498DB; }
.pw-slider-row .pw-preset-val { min-width: 42px; text-align: right; font-size: 12px; color: #888; font-variant-numeric: tabular-nums; }
/* 管理员配额警告条 */
.pw-quota-bar { position: sticky; top: 44px; z-index: 30; background: #FDEBD0; color: #7D6608; text-align: center; padding: 6px 16px; font-size: 13px; border-bottom: 1px solid #F5B041; }
/* ── 画布与四种背景 ─────────────────────────────────────────── */
.pw-wall {
position: absolute;
left: 0;
right: 0;
top: var(--pw-tb);
bottom: 0;
overflow: hidden;
}
.pw-bg-cork {
background-color: #c19a6b;
background-image:
repeating-linear-gradient(115deg, rgba(160, 120, 90, 0.35) 0 2px, transparent 2px 5px),
repeating-linear-gradient(35deg, rgba(120, 85, 55, 0.2) 0 1px, transparent 1px 4px),
radial-gradient(circle at 30% 20%, rgba(255, 255, 255, 0.1), transparent 60%),
linear-gradient(135deg, #c19a6b, #a0785a);
}
.pw-bg-blackboard {
background-color: #2d3b2d;
box-shadow: inset 0 0 220px rgba(0, 0, 0, 0.55);
background-image:
repeating-radial-gradient(circle at 20% 30%, rgba(255, 255, 255, 0.018) 0 1px, transparent 1px 3px),
repeating-radial-gradient(circle at 70% 60%, rgba(255, 255, 255, 0.015) 0 1px, transparent 1px 4px);
}
.pw-bg-wall {
background-color: #fafafa;
background-image:
linear-gradient(rgba(0, 0, 0, 0.05) 1px, transparent 1px),
linear-gradient(90deg, rgba(0, 0, 0, 0.05) 1px, transparent 1px);
background-size: 26px 26px;
}
.pw-bg-kraft {
background-color: #d4a56a;
background-image:
radial-gradient(circle at 20% 30%, rgba(255, 235, 200, 0.3), transparent 45%),
radial-gradient(circle at 80% 70%, rgba(150, 100, 55, 0.3), transparent 45%),
radial-gradient(circle at 50% 90%, rgba(120, 80, 45, 0.22), transparent 50%),
linear-gradient(120deg, #dbb078, #c7935a);
}
.pw-drophint {
position: absolute;
inset: 0;
display: flex;
align-items: center;
justify-content: center;
font-size: 20px;
color: rgba(255, 255, 255, 0.92);
background: rgba(0, 0, 0, 0.28);
border: 4px dashed rgba(255, 255, 255, 0.7);
z-index: 20;
pointer-events: none;
}
/* ── 画布元素通用 ───────────────────────────────────────────── */
.pw-el {
position: absolute;
top: 0;
left: 0;
transform-origin: center center;
cursor: grab;
touch-action: none;
will-change: transform;
transition: box-shadow 0.12s ease;
}
.pw-el.pw-dragging { cursor: grabbing; transition: none; }
/* 宝丽来照片 */
.pw-photo {
background: #fff;
padding: 10px 10px 36px;
border-radius: 2px;
box-shadow: 3px 5px 14px rgba(0, 0, 0, 0.28);
}
.pw-photo.pw-dragging { box-shadow: 10px 18px 36px rgba(0, 0, 0, 0.35); }
.pw-imgwrap { overflow: hidden; }
.pw-photo img {
display: block;
width: 100%;
height: 100%;
object-fit: cover;
background: #f0f0f0;
pointer-events: none;
-webkit-user-drag: none;
}
.pw-caption {
font-family: 'Comic Sans MS', 'Comic Sans', cursive;
font-size: 12px;
color: #444;
text-align: center;
margin-top: 8px;
min-height: 14px;
line-height: 14px;
word-break: break-word;
}
.pw-caption-input {
font-family: 'Comic Sans MS', 'Comic Sans', cursive;
font-size: 12px;
color: #444;
text-align: center;
border: none;
border-bottom: 1px dashed #bbb;
outline: none;
width: 100%;
margin-top: 6px;
background: transparent;
}
/* 固定方式:胶带 / 图钉 */
.pw-fix-tape {
position: absolute;
top: -10px;
left: 50%;
width: 64px;
height: 22px;
background: repeating-linear-gradient(
90deg,
rgba(255, 235, 140, 0.55) 0 3px,
rgba(255, 225, 110, 0.4) 3px 6px
);
box-shadow: 0 1px 3px rgba(0, 0, 0, 0.15);
pointer-events: none;
}
.pw-fix-tape.pw-left { left: 8px; top: -8px; transform: rotate(-42deg); }
.pw-fix-tape.pw-right { left: auto; right: 8px; top: -8px; transform: rotate(42deg); }
.pw-fix-pin {
position: absolute;
top: -9px;
left: 50%;
width: 18px;
height: 18px;
margin-left: -9px;
border-radius: 50%;
box-shadow: inset -2px -2px 3px rgba(0, 0, 0, 0.35), 1px 2px 4px rgba(0, 0, 0, 0.4);
pointer-events: none;
}
.pw-fix-pin::after {
content: '';
position: absolute;
top: 4px;
left: 5px;
width: 5px;
height: 5px;
border-radius: 50%;
background: rgba(255, 255, 255, 0.7);
}
/* 便签 */
.pw-note {
width: 160px;
min-height: 140px;
padding: 14px;
box-shadow: 2px 4px 12px rgba(0, 0, 0, 0.25);
font-family: 'Comic Sans MS', 'Comic Sans', cursive;
font-size: 14px;
color: #333;
word-break: break-word;
white-space: pre-wrap;
overflow: hidden;
}
.pw-note-fold { position: absolute; bottom: 0; left: 0; width: 0; height: 0; border-style: solid; }
.pw-note-input {
width: 100%;
height: 100%;
min-height: 110px;
border: none;
outline: none;
resize: none;
background: transparent;
font: inherit;
color: inherit;
}
/* 胶带条 / 贴纸 / 印章 / 手绘 */
.pw-tape {
height: 18px;
box-shadow: 0 1px 3px rgba(0, 0, 0, 0.18);
border-radius: 1px;
}
.pw-sticker {
padding: 7px 11px;
border-radius: 6px;
font-family: 'Comic Sans MS', 'Comic Sans', cursive;
font-size: 13px;
color: #444;
background: #fff;
box-shadow: 1px 2px 6px rgba(0, 0, 0, 0.18);
white-space: nowrap;
}
.pw-stamp svg,
.pw-doodle svg { display: block; overflow: visible; }
/* 轻提示 */
.pw-toast {
position: fixed;
bottom: 22px;
left: 50%;
transform: translateX(-50%);
background: rgba(40, 40, 40, 0.92);
color: #fff;
padding: 9px 16px;
border-radius: 20px;
font-size: 13px;
z-index: 50;
pointer-events: none;
}