You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 

1117 lines
44 KiB

<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>万物收藏</title>
<link rel="preconnect" href="https://fonts.googleapis.com">
<link href="https://fonts.googleapis.com/css2?family=Noto+Serif+SC:wght@400;600&family=DM+Mono:wght@400;500&family=Outfit:wght@300;400;500;600&display=swap" rel="stylesheet">
<style>
:root {
--bg: #0e0f11;
--bg2: #141518;
--bg3: #1c1e22;
--bg4: #242629;
--border: rgba(255,255,255,0.07);
--border2: rgba(255,255,255,0.12);
--text: #e8e6e1;
--text2: #8a8680;
--text3: #525050;
--accent: #c8a97e;
--accent2: #a07d52;
--accent-bg: rgba(200,169,126,0.1);
--teal: #5db8a0;
--teal-bg: rgba(93,184,160,0.1);
--blue: #6da4d4;
--blue-bg: rgba(109,164,212,0.1);
--red: #d46d6d;
--red-bg: rgba(212,109,109,0.1);
--purple: #9d88d4;
--purple-bg: rgba(157,136,212,0.1);
--font-display: 'Noto Serif SC', serif;
--font-body: 'Outfit', sans-serif;
--font-mono: 'DM Mono', monospace;
--radius: 10px;
--radius-lg: 16px;
--sidebar-w: 220px;
}
* { box-sizing: border-box; margin: 0; padding: 0; }
html, body { height: 100%; overflow: hidden; }
body {
background: var(--bg);
color: var(--text);
font-family: var(--font-body);
font-size: 14px;
display: flex;
flex-direction: column;
}
/* ─── Top Bar ─── */
.topbar {
height: 48px;
background: var(--bg2);
border-bottom: 1px solid var(--border);
display: flex;
align-items: center;
padding: 0 16px;
gap: 12px;
flex-shrink: 0;
z-index: 100;
}
.logo {
font-family: var(--font-display);
font-size: 16px;
color: var(--accent);
letter-spacing: 0.04em;
white-space: nowrap;
margin-right: 8px;
}
.search-wrap {
flex: 1;
max-width: 480px;
position: relative;
}
.search-wrap svg { position: absolute; left: 10px; top: 50%; transform: translateY(-50%); opacity: 0.4; }
.search-wrap input {
width: 100%;
background: var(--bg3);
border: 1px solid var(--border);
border-radius: 8px;
padding: 7px 12px 7px 34px;
color: var(--text);
font-family: var(--font-body);
font-size: 13px;
outline: none;
transition: border-color 0.2s;
}
.search-wrap input::placeholder { color: var(--text3); }
.search-wrap input:focus { border-color: var(--border2); }
.topbar-right { margin-left: auto; display: flex; align-items: center; gap: 8px; }
.btn-add {
background: var(--accent);
color: #1a1208;
border: none;
border-radius: 8px;
padding: 7px 14px;
font-family: var(--font-body);
font-size: 13px;
font-weight: 500;
cursor: pointer;
display: flex;
align-items: center;
gap: 6px;
transition: background 0.2s, transform 0.1s;
}
.btn-add:hover { background: #d4b88a; }
.btn-add:active { transform: scale(0.97); }
.avatar {
width: 30px; height: 30px;
border-radius: 50%;
background: linear-gradient(135deg, var(--teal), var(--blue));
display: flex; align-items: center; justify-content: center;
font-size: 12px; font-weight: 600; color: white; cursor: pointer;
}
/* ─── Layout ─── */
.app {
flex: 1;
display: flex;
overflow: hidden;
}
/* ─── Sidebar ─── */
.sidebar {
width: var(--sidebar-w);
background: var(--bg2);
border-right: 1px solid var(--border);
display: flex;
flex-direction: column;
overflow-y: auto;
flex-shrink: 0;
padding: 12px 0;
}
.sidebar::-webkit-scrollbar { width: 0; }
.sidebar-section { padding: 0 8px; margin-bottom: 20px; }
.sidebar-label {
font-size: 10px;
font-weight: 500;
letter-spacing: 0.1em;
text-transform: uppercase;
color: var(--text3);
padding: 0 8px;
margin-bottom: 4px;
}
.nav-item {
display: flex;
align-items: center;
gap: 8px;
padding: 7px 8px;
border-radius: 7px;
cursor: pointer;
color: var(--text2);
font-size: 13px;
transition: background 0.15s, color 0.15s;
position: relative;
}
.nav-item:hover { background: var(--bg3); color: var(--text); }
.nav-item.active { background: var(--accent-bg); color: var(--accent); }
.nav-item svg { flex-shrink: 0; }
.nav-badge {
margin-left: auto;
background: var(--bg4);
color: var(--text3);
font-size: 10px;
padding: 1px 6px;
border-radius: 10px;
font-family: var(--font-mono);
}
.nav-item.active .nav-badge { background: rgba(200,169,126,0.2); color: var(--accent); }
.tag-dot { width: 7px; height: 7px; border-radius: 50%; flex-shrink: 0; }
.cat-indent { padding-left: 20px; }
/* ─── Main ─── */
.main {
flex: 1;
display: flex;
flex-direction: column;
overflow: hidden;
background: var(--bg);
}
.main-header {
padding: 20px 24px 0;
display: flex;
align-items: flex-end;
justify-content: space-between;
flex-shrink: 0;
}
.main-title {
font-family: var(--font-display);
font-size: 20px;
color: var(--text);
font-weight: 400;
}
.main-sub { font-size: 12px; color: var(--text3); margin-top: 2px; }
.view-tabs {
display: flex;
gap: 2px;
background: var(--bg3);
padding: 3px;
border-radius: 8px;
}
.view-tab {
padding: 5px 10px;
border-radius: 6px;
font-size: 12px;
cursor: pointer;
color: var(--text2);
transition: all 0.15s;
display: flex; align-items: center; gap: 5px;
}
.view-tab.active { background: var(--bg4); color: var(--text); }
.view-tab:hover:not(.active) { color: var(--text); }
.filter-bar {
padding: 12px 24px;
display: flex;
align-items: center;
gap: 8px;
flex-shrink: 0;
border-bottom: 1px solid var(--border);
}
.filter-chip {
padding: 5px 12px;
border-radius: 20px;
font-size: 12px;
cursor: pointer;
border: 1px solid var(--border);
color: var(--text2);
background: transparent;
transition: all 0.15s;
display: flex; align-items: center; gap: 5px;
white-space: nowrap;
}
.filter-chip:hover { border-color: var(--border2); color: var(--text); }
.filter-chip.active { background: var(--accent-bg); border-color: rgba(200,169,126,0.3); color: var(--accent); }
.filter-sep { width: 1px; height: 16px; background: var(--border); margin: 0 4px; }
/* ─── Cards Grid ─── */
.cards-area {
flex: 1;
overflow-y: auto;
padding: 16px 24px 24px;
}
.cards-area::-webkit-scrollbar { width: 4px; }
.cards-area::-webkit-scrollbar-track { background: transparent; }
.cards-area::-webkit-scrollbar-thumb { background: var(--bg4); border-radius: 2px; }
.cards-grid {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(260px, 1fr));
gap: 12px;
}
.card {
background: var(--bg2);
border: 1px solid var(--border);
border-radius: var(--radius-lg);
overflow: hidden;
cursor: pointer;
transition: border-color 0.2s, transform 0.15s, box-shadow 0.2s;
animation: fadeUp 0.3s ease both;
}
.card:hover {
border-color: var(--border2);
transform: translateY(-2px);
box-shadow: 0 8px 32px rgba(0,0,0,0.3);
}
@keyframes fadeUp {
from { opacity: 0; transform: translateY(12px); }
to { opacity: 1; transform: translateY(0); }
}
.card:nth-child(1) { animation-delay: 0.03s; }
.card:nth-child(2) { animation-delay: 0.06s; }
.card:nth-child(3) { animation-delay: 0.09s; }
.card:nth-child(4) { animation-delay: 0.12s; }
.card:nth-child(5) { animation-delay: 0.15s; }
.card:nth-child(6) { animation-delay: 0.18s; }
.card-thumb {
height: 120px;
position: relative;
overflow: hidden;
}
.card-thumb-inner {
width: 100%; height: 100%;
display: flex; align-items: center; justify-content: center;
font-size: 32px;
}
.card-thumb .type-badge {
position: absolute; top: 8px; left: 8px;
font-size: 10px; font-weight: 500; padding: 2px 7px;
border-radius: 4px; backdrop-filter: blur(8px);
}
.card-thumb .fav-btn {
position: absolute; top: 8px; right: 8px;
width: 26px; height: 26px; border-radius: 6px;
background: rgba(0,0,0,0.4); backdrop-filter: blur(8px);
display: flex; align-items: center; justify-content: center;
opacity: 0; transition: opacity 0.15s;
}
.card:hover .fav-btn { opacity: 1; }
.card-body { padding: 12px 14px 14px; }
.card-title-text {
font-size: 14px;
font-weight: 500;
color: var(--text);
line-height: 1.4;
margin-bottom: 6px;
display: -webkit-box;
-webkit-line-clamp: 2;
-webkit-box-orient: vertical;
overflow: hidden;
}
.card-desc {
font-size: 12px;
color: var(--text2);
line-height: 1.5;
display: -webkit-box;
-webkit-line-clamp: 2;
-webkit-box-orient: vertical;
overflow: hidden;
margin-bottom: 10px;
}
.card-meta {
display: flex;
align-items: center;
justify-content: space-between;
gap: 8px;
}
.card-tags { display: flex; gap: 4px; flex-wrap: wrap; }
.ctag {
font-size: 10px;
padding: 2px 7px;
border-radius: 4px;
font-weight: 400;
}
.card-date { font-size: 11px; color: var(--text3); font-family: var(--font-mono); white-space: nowrap; }
.card-source {
display: flex; align-items: center; gap: 4px;
font-size: 11px; color: var(--text3); margin-bottom: 8px;
}
.card-source-dot { width: 12px; height: 12px; border-radius: 3px; background: var(--bg4); display:flex;align-items:center;justify-content:center; font-size:8px; }
.star-rating { display: flex; gap: 2px; }
.star { font-size: 11px; }
/* ─── Right Panel ─── */
.detail-panel {
width: 380px;
background: var(--bg2);
border-left: 1px solid var(--border);
display: flex;
flex-direction: column;
flex-shrink: 0;
overflow: hidden;
transform: translateX(100%);
transition: transform 0.25s ease;
}
.detail-panel.open { transform: translateX(0); }
.detail-header {
padding: 16px 20px;
border-bottom: 1px solid var(--border);
display: flex;
align-items: center;
justify-content: space-between;
}
.detail-header-title { font-size: 13px; font-weight: 500; color: var(--text2); }
.detail-close {
width: 26px; height: 26px; border-radius: 6px;
background: var(--bg3); border: none; cursor: pointer;
color: var(--text2); display: flex; align-items: center; justify-content: center;
transition: background 0.15s;
}
.detail-close:hover { background: var(--bg4); color: var(--text); }
.detail-body { flex: 1; overflow-y: auto; padding: 20px; }
.detail-body::-webkit-scrollbar { width: 0; }
.detail-thumb {
height: 160px;
border-radius: var(--radius);
overflow: hidden;
margin-bottom: 16px;
display: flex; align-items: center; justify-content: center;
font-size: 48px;
}
.detail-title {
font-family: var(--font-display);
font-size: 18px;
line-height: 1.4;
color: var(--text);
margin-bottom: 10px;
}
.detail-source {
display: flex; align-items: center; gap: 6px;
font-size: 12px; color: var(--text3); margin-bottom: 14px;
}
.detail-divider { height: 1px; background: var(--border); margin: 16px 0; }
.detail-section-label {
font-size: 10px; font-weight: 500; letter-spacing: 0.08em;
text-transform: uppercase; color: var(--text3); margin-bottom: 8px;
}
.detail-summary {
font-size: 13px; color: var(--text2); line-height: 1.7;
background: var(--bg3); border-radius: var(--radius);
padding: 12px 14px;
border-left: 2px solid var(--accent);
margin-bottom: 14px;
}
.detail-tags { display: flex; flex-wrap: wrap; gap: 6px; margin-bottom: 6px; }
.detail-tag {
font-size: 12px; padding: 4px 10px;
border-radius: 6px; cursor: pointer;
transition: opacity 0.15s;
}
.detail-tag:hover { opacity: 0.8; }
.note-area {
width: 100%;
background: var(--bg3);
border: 1px solid var(--border);
border-radius: var(--radius);
padding: 10px 12px;
color: var(--text);
font-family: var(--font-body);
font-size: 13px;
line-height: 1.6;
resize: none;
outline: none;
min-height: 80px;
transition: border-color 0.2s;
}
.note-area:focus { border-color: var(--border2); }
.note-area::placeholder { color: var(--text3); }
.detail-actions {
padding: 12px 20px;
border-top: 1px solid var(--border);
display: flex; gap: 8px;
}
.btn-action {
flex: 1;
padding: 8px 12px;
border-radius: 8px;
font-family: var(--font-body);
font-size: 12px;
font-weight: 500;
cursor: pointer;
border: 1px solid var(--border);
background: var(--bg3);
color: var(--text2);
transition: all 0.15s;
display: flex; align-items: center; justify-content: center; gap: 5px;
}
.btn-action:hover { border-color: var(--border2); color: var(--text); }
.btn-action.primary { background: var(--accent-bg); border-color: rgba(200,169,126,0.3); color: var(--accent); }
.btn-action.primary:hover { background: rgba(200,169,126,0.2); }
/* ─── Modal: Add ─── */
.modal-overlay {
position: fixed; inset: 0;
background: rgba(0,0,0,0.6);
backdrop-filter: blur(4px);
z-index: 1000;
display: flex;
align-items: center;
justify-content: center;
opacity: 0; pointer-events: none;
transition: opacity 0.2s;
}
.modal-overlay.open { opacity: 1; pointer-events: all; }
.modal {
background: var(--bg2);
border: 1px solid var(--border2);
border-radius: var(--radius-lg);
width: 480px;
max-width: 90vw;
padding: 24px;
transform: translateY(10px) scale(0.98);
transition: transform 0.2s;
}
.modal-overlay.open .modal { transform: translateY(0) scale(1); }
.modal-title {
font-family: var(--font-display);
font-size: 18px;
color: var(--text);
margin-bottom: 20px;
}
.form-group { margin-bottom: 16px; }
.form-label { font-size: 12px; color: var(--text2); margin-bottom: 6px; display: block; }
.form-input {
width: 100%;
background: var(--bg3);
border: 1px solid var(--border);
border-radius: 8px;
padding: 9px 12px;
color: var(--text);
font-family: var(--font-body);
font-size: 13px;
outline: none;
transition: border-color 0.2s;
}
.form-input:focus { border-color: rgba(200,169,126,0.4); }
.form-input::placeholder { color: var(--text3); }
textarea.form-input { resize: vertical; min-height: 80px; line-height: 1.6; }
.type-selector { display: flex; gap: 6px; flex-wrap: wrap; }
.type-opt {
padding: 6px 12px; border-radius: 8px; font-size: 12px;
border: 1px solid var(--border); cursor: pointer;
color: var(--text2); transition: all 0.15s;
}
.type-opt.selected { border-color: rgba(200,169,126,0.4); background: var(--accent-bg); color: var(--accent); }
.modal-actions { display: flex; gap: 8px; justify-content: flex-end; margin-top: 20px; }
.btn-cancel {
padding: 8px 16px; border-radius: 8px; font-family: var(--font-body);
font-size: 13px; cursor: pointer; border: 1px solid var(--border);
background: transparent; color: var(--text2); transition: all 0.15s;
}
.btn-cancel:hover { border-color: var(--border2); color: var(--text); }
.btn-save {
padding: 8px 20px; border-radius: 8px; font-family: var(--font-body);
font-size: 13px; font-weight: 500; cursor: pointer; border: none;
background: var(--accent); color: #1a1208; transition: all 0.15s;
}
.btn-save:hover { background: #d4b88a; }
/* type colors */
.bg-web { background: linear-gradient(135deg, #1a2535, #203040); }
.bg-text { background: linear-gradient(135deg, #1e1a2e, #251e38); }
.bg-img { background: linear-gradient(135deg, #1a2820, #1e3228); }
.bg-vid { background: linear-gradient(135deg, #2a1a1a, #381e1e); }
.bg-doc { background: linear-gradient(135deg, #2a261a, #38321e); }
.c-web { color: var(--blue); }
.c-text { color: var(--purple); }
.c-img { color: var(--teal); }
.c-vid { color: var(--red); }
.c-doc { color: var(--accent); }
.badge-web { background: var(--blue-bg); color: var(--blue); }
.badge-text { background: var(--purple-bg); color: var(--purple); }
.badge-img { background: var(--teal-bg); color: var(--teal); }
.badge-vid { background: var(--red-bg); color: var(--red); }
.badge-doc { background: var(--accent-bg); color: var(--accent); }
/* highlight strip */
.highlight-strip {
height: 2px;
border-radius: 0 0 2px 2px;
margin-bottom: 0;
}
/* empty state */
.empty-state {
display: flex; flex-direction: column; align-items: center; justify-content: center;
height: 100%; gap: 12px; color: var(--text3); text-align: center;
}
.empty-state-icon { font-size: 40px; opacity: 0.5; }
/* AI chat strip */
.ai-strip {
padding: 10px 24px;
border-top: 1px solid var(--border);
display: flex; align-items: center; gap: 10px;
background: var(--bg2);
flex-shrink: 0;
}
.ai-input {
flex: 1;
background: var(--bg3);
border: 1px solid var(--border);
border-radius: 8px;
padding: 8px 12px;
color: var(--text);
font-family: var(--font-body);
font-size: 13px;
outline: none;
transition: border-color 0.2s;
}
.ai-input::placeholder { color: var(--text3); }
.ai-input:focus { border-color: rgba(200,169,126,0.4); }
.ai-label { font-size: 11px; color: var(--accent); font-weight: 500; white-space: nowrap; display: flex; align-items: center; gap: 4px; }
.ai-pulse { width: 6px; height: 6px; border-radius: 50%; background: var(--accent); animation: pulse 2s infinite; }
@keyframes pulse { 0%,100%{opacity:1;transform:scale(1)} 50%{opacity:0.5;transform:scale(0.8)} }
.ai-send {
padding: 8px 14px; border-radius: 8px; border: 1px solid rgba(200,169,126,0.3);
background: var(--accent-bg); color: var(--accent); font-size: 12px; font-weight: 500;
cursor: pointer; font-family: var(--font-body); transition: all 0.15s; white-space: nowrap;
}
.ai-send:hover { background: rgba(200,169,126,0.2); }
/* toast */
.toast {
position: fixed; bottom: 24px; left: 50%; transform: translateX(-50%) translateY(60px);
background: var(--bg4); border: 1px solid var(--border2);
border-radius: 10px; padding: 10px 18px;
font-size: 13px; color: var(--text); z-index: 9999;
transition: transform 0.3s ease;
display: flex; align-items: center; gap: 8px;
}
.toast.show { transform: translateX(-50%) translateY(0); }
.toast-dot { width: 6px; height: 6px; border-radius: 50%; background: var(--teal); }
</style>
</head>
<body>
<!-- Top Bar -->
<div class="topbar">
<div class="logo">万物收藏</div>
<div class="search-wrap">
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><circle cx="11" cy="11" r="8"/><path d="m21 21-4.35-4.35"/></svg>
<input type="text" placeholder="搜索收藏、标签、内容..." id="searchInput" oninput="handleSearch(this.value)">
</div>
<div class="topbar-right">
<button class="btn-add" onclick="openModal()">
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5"><path d="M12 5v14M5 12h14"/></svg>
新增收藏
</button>
<div class="avatar"></div>
</div>
</div>
<!-- App Body -->
<div class="app">
<!-- Sidebar -->
<div class="sidebar">
<div class="sidebar-section">
<div class="sidebar-label">视图</div>
<div class="nav-item active" onclick="setNav(this,'inbox')">
<svg width="15" height="15" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8"><path d="M22 12h-6l-2 3h-4l-2-3H2"/><path d="M5.45 5.11 2 12v6a2 2 0 0 0 2 2h16a2 2 0 0 0 2-2v-6l-3.45-6.89A2 2 0 0 0 16.76 4H7.24a2 2 0 0 0-1.79 1.11z"/></svg>
收件箱
<span class="nav-badge">3</span>
</div>
<div class="nav-item" onclick="setNav(this,'all')">
<svg width="15" height="15" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8"><rect x="3" y="3" width="7" height="7"/><rect x="14" y="3" width="7" height="7"/><rect x="14" y="14" width="7" height="7"/><rect x="3" y="14" width="7" height="7"/></svg>
全部收藏
<span class="nav-badge">24</span>
</div>
<div class="nav-item" onclick="setNav(this,'recent')">
<svg width="15" height="15" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8"><circle cx="12" cy="12" r="10"/><polyline points="12 6 12 12 16 14"/></svg>
最近收藏
</div>
<div class="nav-item" onclick="setNav(this,'starred')">
<svg width="15" height="15" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8"><polygon points="12 2 15.09 8.26 22 9.27 17 14.14 18.18 21.02 12 17.77 5.82 21.02 7 14.14 2 9.27 8.91 8.26 12 2"/></svg>
已加星标
</div>
<div class="nav-item" onclick="setNav(this,'articles')">
<svg width="15" height="15" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8"><path d="M14 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8z"/><polyline points="14 2 14 8 20 8"/><line x1="16" y1="13" x2="8" y2="13"/><line x1="16" y1="17" x2="8" y2="17"/></svg>
我的文章
<span class="nav-badge">2</span>
</div>
</div>
<div class="sidebar-section">
<div class="sidebar-label">分类</div>
<div class="nav-item" onclick="setNav(this,'cat-design')">
<svg width="15" height="15" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8"><circle cx="12" cy="12" r="10"/><path d="M8 14s1.5 2 4 2 4-2 4-2"/><line x1="9" y1="9" x2="9.01" y2="9"/><line x1="15" y1="9" x2="15.01" y2="9"/></svg>
设计
<span class="nav-badge">8</span>
</div>
<div class="nav-item cat-indent" onclick="setNav(this,'cat-ui')">
<svg width="13" height="13" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8"><rect x="2" y="3" width="20" height="14" rx="2"/><path d="M8 21h8M12 17v4"/></svg>
UI / 交互
</div>
<div class="nav-item cat-indent" onclick="setNav(this,'cat-font')">
<svg width="13" height="13" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8"><polyline points="4 7 4 4 20 4 20 7"/><line x1="9" y1="20" x2="15" y2="20"/><line x1="12" y1="4" x2="12" y2="20"/></svg>
字体排版
</div>
<div class="nav-item" onclick="setNav(this,'cat-ai')">
<svg width="15" height="15" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8"><path d="M12 2a4 4 0 0 1 4 4v2H8V6a4 4 0 0 1 4-4z"/><rect x="8" y="8" width="8" height="10" rx="2"/><path d="M9 22v-4M15 22v-4"/></svg>
AI / 技术
<span class="nav-badge">11</span>
</div>
<div class="nav-item" onclick="setNav(this,'cat-read')">
<svg width="15" height="15" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8"><path d="M2 3h6a4 4 0 0 1 4 4v14a3 3 0 0 0-3-3H2z"/><path d="M22 3h-6a4 4 0 0 0-4 4v14a3 3 0 0 1 3-3h7z"/></svg>
阅读 / 思考
<span class="nav-badge">5</span>
</div>
</div>
<div class="sidebar-section">
<div class="sidebar-label">标签</div>
<div class="nav-item" onclick="filterByTag('值得深读')">
<span class="tag-dot" style="background:var(--accent)"></span>
值得深读
<span class="nav-badge">6</span>
</div>
<div class="nav-item" onclick="filterByTag('灵感')">
<span class="tag-dot" style="background:var(--purple)"></span>
灵感
<span class="nav-badge">9</span>
</div>
<div class="nav-item" onclick="filterByTag('参考资料')">
<span class="tag-dot" style="background:var(--teal)"></span>
参考资料
<span class="nav-badge">4</span>
</div>
<div class="nav-item" onclick="filterByTag('AI工具')">
<span class="tag-dot" style="background:var(--blue)"></span>
AI工具
<span class="nav-badge">7</span>
</div>
</div>
</div>
<!-- Main Content -->
<div class="main">
<div class="main-header">
<div>
<div class="main-title" id="mainTitle">收件箱</div>
<div class="main-sub" id="mainSub">3 条待整理</div>
</div>
<div class="view-tabs">
<div class="view-tab active" onclick="setView('grid',this)">
<svg width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><rect x="3" y="3" width="7" height="7"/><rect x="14" y="3" width="7" height="7"/><rect x="14" y="14" width="7" height="7"/><rect x="3" y="14" width="7" height="7"/></svg>卡片
</div>
<div class="view-tab" onclick="setView('list',this)">
<svg width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><line x1="8" y1="6" x2="21" y2="6"/><line x1="8" y1="12" x2="21" y2="12"/><line x1="8" y1="18" x2="21" y2="18"/><line x1="3" y1="6" x2="3.01" y2="6"/><line x1="3" y1="12" x2="3.01" y2="12"/><line x1="3" y1="18" x2="3.01" y2="18"/></svg>列表
</div>
</div>
</div>
<div class="filter-bar">
<div class="filter-chip active" onclick="setFilter('all',this)">全部</div>
<div class="filter-chip" onclick="setFilter('web',this)">
<svg width="11" height="11" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><circle cx="12" cy="12" r="10"/><path d="M2 12h20M12 2a15.3 15.3 0 0 1 4 10 15.3 15.3 0 0 1-4 10 15.3 15.3 0 0 1-4-10 15.3 15.3 0 0 1 4-10z"/></svg>
网页
</div>
<div class="filter-chip" onclick="setFilter('text',this)">文本</div>
<div class="filter-chip" onclick="setFilter('img',this)">图片</div>
<div class="filter-chip" onclick="setFilter('vid',this)">视频</div>
<div class="filter-chip" onclick="setFilter('doc',this)">文档</div>
<div class="filter-sep"></div>
<div class="filter-chip" onclick="setFilter('starred',this)">⭐ 星标</div>
</div>
<div class="cards-area" id="cardsArea">
<div class="cards-grid" id="cardsGrid"></div>
</div>
<!-- AI Strip -->
<div class="ai-strip">
<div class="ai-label"><div class="ai-pulse"></div> AI 问答</div>
<input class="ai-input" placeholder="问我的收藏:最近有哪些关于设计的内容?" id="aiInput">
<button class="ai-send" onclick="sendAI()">发送 →</button>
</div>
</div>
<!-- Detail Panel -->
<div class="detail-panel" id="detailPanel">
<div class="detail-header">
<div class="detail-header-title">收藏详情</div>
<button class="detail-close" onclick="closeDetail()">
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M18 6 6 18M6 6l12 12"/></svg>
</button>
</div>
<div class="detail-body" id="detailBody">
</div>
<div class="detail-actions">
<button class="btn-action primary" onclick="writeArticle()">
<svg width="13" height="13" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M11 4H4a2 2 0 0 0-2 2v14a2 2 0 0 0 2 2h14a2 2 0 0 0 2-2v-7"/><path d="M18.5 2.5a2.121 2.121 0 0 1 3 3L12 15l-4 1 1-4 9.5-9.5z"/></svg>
写文章
</button>
<button class="btn-action" onclick="copyLink()">
<svg width="13" height="13" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M10 13a5 5 0 0 0 7.54.54l3-3a5 5 0 0 0-7.07-7.07l-1.72 1.71"/><path d="M14 11a5 5 0 0 0-7.54-.54l-3 3a5 5 0 0 0 7.07 7.07l1.71-1.71"/></svg>
复制链接
</button>
<button class="btn-action" onclick="deleteItem()">
<svg width="13" height="13" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><polyline points="3 6 5 6 21 6"/><path d="M19 6v14a2 2 0 0 1-2 2H7a2 2 0 0 1-2-2V6m3 0V4a1 1 0 0 1 1-1h4a1 1 0 0 1 1 1v2"/></svg>
删除
</button>
</div>
</div>
</div>
<!-- Add Modal -->
<div class="modal-overlay" id="modalOverlay" onclick="handleOverlayClick(event)">
<div class="modal">
<div class="modal-title">新增收藏</div>
<div class="form-group">
<label class="form-label">内容类型</label>
<div class="type-selector">
<div class="type-opt selected" onclick="selectType(this,'web')">🌐 网页</div>
<div class="type-opt" onclick="selectType(this,'text')">📝 文本</div>
<div class="type-opt" onclick="selectType(this,'img')">🖼️ 图片</div>
<div class="type-opt" onclick="selectType(this,'vid')">🎬 视频</div>
<div class="type-opt" onclick="selectType(this,'doc')">📄 文档</div>
</div>
</div>
<div class="form-group">
<label class="form-label">链接或内容</label>
<input class="form-input" type="text" placeholder="https://... 或粘贴文字" id="formUrl">
</div>
<div class="form-group">
<label class="form-label">标题(可选,留空自动识别)</label>
<input class="form-input" type="text" placeholder="给这条收藏起个名字" id="formTitle">
</div>
<div class="form-group">
<label class="form-label">标签(用空格分隔)</label>
<input class="form-input" type="text" placeholder="灵感 设计 AI工具" id="formTags">
</div>
<div class="form-group">
<label class="form-label">备注</label>
<textarea class="form-input" placeholder="写下你的想法..." id="formNote"></textarea>
</div>
<div class="modal-actions">
<button class="btn-cancel" onclick="closeModal()">取消</button>
<button class="btn-save" onclick="saveItem()">保存收藏</button>
</div>
</div>
</div>
<!-- Toast -->
<div class="toast" id="toast">
<div class="toast-dot"></div>
<span id="toastMsg">已保存</span>
</div>
<script>
const ITEMS = [
{
id:1, type:'web', title:'Vercel 设计系统 2024 更新:新组件库与动效规范',
desc:'Vercel 团队分享了最新的设计系统更新,包括全新的 Radix 组件封装、Motion 动效库集成和无障碍规范改进。',
url:'https://vercel.com/design', source:'vercel.com', tags:['设计','参考资料'],
date:'05-16', starred:true, rating:4,
note:'重点关注他们的 Motion 实现方案,和我们的项目需求很贴近。',
summary:'文章详细介绍了 Vercel 如何重构其设计系统,核心思路是「组件即文档」——每个组件自带使用示例和无障碍说明。动效方面引入了基于物理的弹簧动画替代传统缓动曲线。',
emoji:'🌐', bg:'bg-web', badgeClass:'badge-web', accentColor:'var(--blue)'
},
{
id:2, type:'text', title:'关于「慢阅读」的思考碎片',
desc:'在信息爆炸时代,我们需要主动降速。真正的理解需要摩擦感,需要反复咀嚼,需要停下来想一想。',
url:'', source:'个人笔记', tags:['灵感','值得深读'],
date:'05-15', starred:false, rating:5,
note:'这段话在地铁上想到的,和最近读《深度工作》有关。',
summary:'这段文字探讨了现代人与信息的关系——我们消费的速度远超消化的速度。提出「慢阅读」作为一种对抗信息焦虑的实践方法。',
emoji:'📝', bg:'bg-text', badgeClass:'badge-text', accentColor:'var(--purple)'
},
{
id:3, type:'web', title:'2024 年最值得关注的 AI 设计工具合集',
desc:'涵盖 Galileo AI、Uizard、Framer AI 等 14 款工具的深度对比,从生成质量、可控性、工作流融合三个维度评分。',
url:'https://example.com/ai-tools', source:'uxdesign.cc', tags:['AI工具','设计'],
date:'05-14', starred:true, rating:4,
note:'',
summary:'文章系统比较了当前主流 AI 设计工具,结论是 Framer AI 在工作流融合上领先,但 Galileo AI 的生成质量更高。建议根据团队使用场景分开选择。',
emoji:'🤖', bg:'bg-web', badgeClass:'badge-web', accentColor:'var(--blue)'
},
{
id:4, type:'img', title:'Teenage Engineering OP-1 产品摄影参考',
desc:'极简主义硬件产品的摄影风格:高对比、单色背景、精准的光影层次,彰显工业美学。',
url:'', source:'Dribbble', tags:['灵感','设计'],
date:'05-13', starred:false, rating:3,
note:'下次产品拍摄可以参考这种风格。',
summary:'这组产品摄影展示了如何用极简布景突出产品本身的设计语言——没有任何多余道具,只有主体、光线和阴影的对话。',
emoji:'📷', bg:'bg-img', badgeClass:'badge-img', accentColor:'var(--teal)'
},
{
id:5, type:'vid', title:'3Blue1Brown: 神经网络的可视化解释',
desc:'通过动态几何可视化,直觉上理解反向传播、梯度下降和神经元激活的工作原理,无需数学公式。',
url:'https://youtube.com', source:'YouTube', tags:['AI工具','参考资料','值得深读'],
date:'05-12', starred:true, rating:5,
note:'最好的 ML 入门视频,没有之一。准备在团队分享会上播放。',
summary:'Grant 用其标志性的数学可视化风格,将反向传播从一个抽象算法变成可以「看到」的几何变换过程,极大降低了理解门槛。',
emoji:'▶️', bg:'bg-vid', badgeClass:'badge-vid', accentColor:'var(--red)'
},
{
id:6, type:'doc', title:'《设计系统:企业级 UI 的构建法则》PDF',
desc:'Nathan Curtis 的经典著作,详述如何从零搭建可维护的设计系统,包含 Figma 组织规范和开发交接流程。',
url:'', source:'PDF 文档', tags:['设计','参考资料'],
date:'05-10', starred:false, rating:4,
note:'第三章关于 Token 体系的部分要精读,跟我们的需求高度相关。',
summary:'本书是目前最系统的设计系统建设指南,核心观点:设计系统不是组件库,而是一套治理语言和协作流程。',
emoji:'📄', bg:'bg-doc', badgeClass:'badge-doc', accentColor:'var(--accent)'
},
{
id:7, type:'web', title:'Raycast 如何在产品增长上做了哪些正确的事',
desc:'从 0 到 100 万用户的增长路径复盘:产品主导增长、开发者社区、插件生态三大飞轮如何协同运转。',
url:'https://raycast.com', source:'raycast.com', tags:['灵感','值得深读'],
date:'05-09', starred:false, rating:3,
note:'',
summary:'Raycast 的增长故事印证了「产品即渠道」的理念——当工具足够好用,用户自然成为传播者。插件生态是其最聪明的护城河策略。',
emoji:'🚀', bg:'bg-web', badgeClass:'badge-web', accentColor:'var(--blue)'
},
{
id:8, type:'text', title:'设计师应该学代码吗?我的答案变了三次',
desc:'从「完全没必要」到「会一点很好」再到「这个问题本身就是错的」——真正的边界在于理解,而不在于实现。',
url:'', source:'个人笔记', tags:['灵感','设计'],
date:'05-08', starred:true, rating:4,
note:'这是我要写的下一篇文章的核心论点。',
summary:'作者认为「设计师要不要学代码」这个问题预设了一个错误的二元对立。更好的问题是:如何建立跨越学科的理解力?',
emoji:'💭', bg:'bg-text', badgeClass:'badge-text', accentColor:'var(--purple)'
}
];
let allItems = [...ITEMS];
let currentFilter = 'all';
let currentView = 'grid';
let selectedItem = null;
let selectedType = 'web';
function renderCards(items) {
const grid = document.getElementById('cardsGrid');
if (items.length === 0) {
grid.innerHTML = '<div class="empty-state" style="grid-column:1/-1;padding:60px 0"><div class="empty-state-icon">🔍</div><div>没有找到匹配的收藏</div></div>';
return;
}
grid.innerHTML = items.map((item,i) => `
<div class="card" onclick="openDetail(${item.id})" style="animation-delay:${i*0.04}s">
<div class="card-thumb ${item.bg}">
<div class="card-thumb-inner">${item.emoji}</div>
<div class="type-badge ${item.badgeClass}">${typeLabel(item.type)}</div>
<div class="fav-btn" onclick="toggleStar(event,${item.id})">
<svg width="13" height="13" viewBox="0 0 24 24" fill="${item.starred?'var(--accent)':'none'}" stroke="${item.starred?'var(--accent)':'rgba(255,255,255,0.6)'}" stroke-width="2"><polygon points="12 2 15.09 8.26 22 9.27 17 14.14 18.18 21.02 12 17.77 5.82 21.02 7 14.14 2 9.27 8.91 8.26 12 2"/></svg>
</div>
</div>
<div class="card-body">
<div class="card-source">
<div class="card-source-dot" style="background:${item.accentColor}22;color:${item.accentColor}">●</div>
${item.source}
</div>
<div class="card-title-text">${item.title}</div>
<div class="card-desc">${item.desc}</div>
<div class="card-meta">
<div class="card-tags">
${item.tags.slice(0,2).map(t=>`<span class="ctag" style="background:${tagBg(t)};color:${tagColor(t)}">#${t}</span>`).join('')}
</div>
<div class="card-date">${item.date}</div>
</div>
</div>
</div>
`).join('');
}
function typeLabel(t) {
return {web:'网页',text:'文本',img:'图片',vid:'视频',doc:'文档'}[t] || t;
}
function tagBg(t) {
const m = {'灵感':'var(--purple-bg)','设计':'var(--teal-bg)','AI工具':'var(--blue-bg)','值得深读':'var(--accent-bg)','参考资料':'rgba(93,184,160,0.12)'};
return m[t]||'var(--bg4)';
}
function tagColor(t) {
const m = {'灵感':'var(--purple)','设计':'var(--teal)','AI工具':'var(--blue)','值得深读':'var(--accent)','参考资料':'var(--teal)'};
return m[t]||'var(--text2)';
}
function setFilter(type, el) {
document.querySelectorAll('.filter-chip').forEach(e=>e.classList.remove('active'));
el.classList.add('active');
currentFilter = type;
applyFilter();
}
function applyFilter() {
let items = allItems;
if (currentFilter !== 'all') {
if (currentFilter === 'starred') items = items.filter(i=>i.starred);
else items = items.filter(i=>i.type === currentFilter);
}
const q = document.getElementById('searchInput').value.toLowerCase();
if (q) items = items.filter(i=>i.title.toLowerCase().includes(q)||i.desc.toLowerCase().includes(q)||i.tags.some(t=>t.includes(q)));
renderCards(items);
document.getElementById('mainSub').textContent = `${items.length} 条收藏`;
}
function handleSearch(v) { applyFilter(); }
function setNav(el, key) {
document.querySelectorAll('.nav-item').forEach(e=>e.classList.remove('active'));
el.classList.add('active');
const titles = {inbox:'收件箱',all:'全部收藏',recent:'最近收藏',starred:'已加星标',articles:'我的文章','cat-design':'设计','cat-ui':'UI / 交互','cat-font':'字体排版','cat-ai':'AI / 技术','cat-read':'阅读 / 思考'};
document.getElementById('mainTitle').textContent = titles[key] || key;
if (key === 'starred') { currentFilter='starred'; } else { currentFilter='all'; }
applyFilter();
}
function filterByTag(tag) {
document.querySelectorAll('.nav-item').forEach(e=>e.classList.remove('active'));
let items = allItems.filter(i=>i.tags.includes(tag));
document.getElementById('mainTitle').textContent = '#' + tag;
document.getElementById('mainSub').textContent = items.length + ' 条收藏';
renderCards(items);
}
function setView(v, el) {
document.querySelectorAll('.view-tab').forEach(e=>e.classList.remove('active'));
el.classList.add('active');
currentView = v;
const grid = document.getElementById('cardsGrid');
if (v === 'list') grid.style.gridTemplateColumns = '1fr';
else grid.style.gridTemplateColumns = '';
}
function openDetail(id) {
selectedItem = allItems.find(i=>i.id===id);
if (!selectedItem) return;
const p = selectedItem;
document.getElementById('detailBody').innerHTML = `
<div class="detail-thumb ${p.bg}">${p.emoji}</div>
<div class="detail-title">${p.title}</div>
<div class="detail-source">
<div style="width:14px;height:14px;border-radius:4px;background:${p.accentColor}22;display:flex;align-items:center;justify-content:center;font-size:9px;color:${p.accentColor}">●</div>
${p.source} · ${p.date}
<span style="margin-left:auto">${'★'.repeat(p.rating)}${'☆'.repeat(5-p.rating)}</span>
</div>
<div class="detail-divider"></div>
<div class="detail-section-label">AI 摘要</div>
<div class="detail-summary">${p.summary}</div>
<div class="detail-section-label">标签</div>
<div class="detail-tags">
${p.tags.map(t=>`<span class="detail-tag" style="background:${tagBg(t)};color:${tagColor(t)}">#${t}</span>`).join('')}
<span class="detail-tag" style="background:var(--bg3);color:var(--text3);cursor:pointer" onclick="showToast('标签编辑功能开发中')">+ 添加</span>
</div>
<div class="detail-divider"></div>
<div class="detail-section-label">我的备注</div>
<textarea class="note-area" placeholder="写下你的想法...">${p.note||''}</textarea>
<div class="detail-divider"></div>
<div class="detail-section-label">相关收藏推荐</div>
${allItems.filter(i=>i.id!==p.id&&i.tags.some(t=>p.tags.includes(t))).slice(0,2).map(r=>`
<div onclick="openDetail(${r.id})" style="display:flex;align-items:center;gap:10px;padding:8px 10px;border-radius:8px;cursor:pointer;transition:background 0.15s;margin-bottom:6px" onmouseover="this.style.background='var(--bg3)'" onmouseout="this.style.background=''">
<div style="width:32px;height:32px;border-radius:6px;display:flex;align-items:center;justify-content:center;font-size:16px;flex-shrink:0" class="${r.bg}">${r.emoji}</div>
<div style="flex:1;min-width:0">
<div style="font-size:12px;color:var(--text);white-space:nowrap;overflow:hidden;text-overflow:ellipsis">${r.title}</div>
<div style="font-size:11px;color:var(--text3)">${r.tags[0]}</div>
</div>
</div>
`).join('')}
`;
document.getElementById('detailPanel').classList.add('open');
}
function closeDetail() {
document.getElementById('detailPanel').classList.remove('open');
selectedItem = null;
}
function toggleStar(e, id) {
e.stopPropagation();
const item = allItems.find(i=>i.id===id);
if (item) { item.starred = !item.starred; applyFilter(); showToast(item.starred?'已加入星标':'已移除星标'); }
}
function writeArticle() {
showToast('✍️ 正在打开文章编辑器...');
setTimeout(closeDetail, 800);
}
function copyLink() { showToast('🔗 链接已复制到剪贴板'); }
function deleteItem() {
if (!selectedItem) return;
allItems = allItems.filter(i=>i.id!==selectedItem.id);
closeDetail(); applyFilter(); showToast('已删除');
}
function openModal() {
document.getElementById('modalOverlay').classList.add('open');
}
function closeModal() {
document.getElementById('modalOverlay').classList.remove('open');
document.getElementById('formUrl').value='';
document.getElementById('formTitle').value='';
document.getElementById('formTags').value='';
document.getElementById('formNote').value='';
}
function handleOverlayClick(e) { if (e.target===e.currentTarget) closeModal(); }
function selectType(el, t) {
document.querySelectorAll('.type-opt').forEach(e=>e.classList.remove('selected'));
el.classList.add('selected');
selectedType = t;
}
const typeEmoji = {web:'🌐',text:'📝',img:'📷',vid:'▶️',doc:'📄'};
const typeBg = {web:'bg-web',text:'bg-text',img:'bg-img',vid:'bg-vid',doc:'bg-doc'};
const typeBadge = {web:'badge-web',text:'badge-text',img:'badge-img',vid:'badge-vid',doc:'badge-doc'};
const typeAccent = {web:'var(--blue)',text:'var(--purple)',img:'var(--teal)',vid:'var(--red)',doc:'var(--accent)'};
function saveItem() {
const url = document.getElementById('formUrl').value.trim();
const title = document.getElementById('formTitle').value.trim() || (url ? url.replace(/^https?:\/\//,'').split('/')[0] : '新收藏 · ' + new Date().toLocaleDateString());
const tagsRaw = document.getElementById('formTags').value.trim();
const tags = tagsRaw ? tagsRaw.split(/\s+/).filter(Boolean) : ['未分类'];
const note = document.getElementById('formNote').value.trim();
const now = new Date();
const dateStr = `${String(now.getMonth()+1).padStart(2,'0')}-${String(now.getDate()).padStart(2,'0')}`;
const newItem = {
id: Date.now(), type: selectedType, title, desc: note || '暂无描述。',
url, source: url ? url.replace(/^https?:\/\//,'').split('/')[0] : '手动输入',
tags, date: dateStr, starred: false, rating: 3, note,
summary: 'AI 正在分析内容,摘要即将生成...',
emoji: typeEmoji[selectedType], bg: typeBg[selectedType],
badgeClass: typeBadge[selectedType], accentColor: typeAccent[selectedType]
};
allItems.unshift(newItem);
closeModal(); applyFilter(); showToast('✅ 收藏已保存');
}
function sendAI() {
const val = document.getElementById('aiInput').value.trim();
if (!val) return;
showToast('🤖 AI 正在检索你的收藏...');
document.getElementById('aiInput').value = '';
}
function showToast(msg) {
const t = document.getElementById('toast');
document.getElementById('toastMsg').textContent = msg;
t.classList.add('show');
setTimeout(()=>t.classList.remove('show'), 2200);
}
// Init
renderCards(allItems);
</script>
</body>
</html>