统一 Airshelf 界面组件与图标

This commit is contained in:
iye
2026-05-27 12:29:41 +08:00
parent ea335587d2
commit 086d92991e
47 changed files with 750 additions and 450 deletions
+89
View File
@@ -0,0 +1,89 @@
(function () {
const PATHS = {
home: '<path d="M3 10.5 12 3l9 7.5"/><path d="M5 10v10h14V10"/><path d="M9 20v-6h6v6"/>',
package: '<path d="M11 21.7a2 2 0 0 0 2 0l7-4A2 2 0 0 0 21 16V8a2 2 0 0 0-1-1.7l-7-4a2 2 0 0 0-2 0l-7 4A2 2 0 0 0 3 8v8a2 2 0 0 0 1 1.7Z"/><path d="m3.3 7 8.7 5 8.7-5"/><path d="M12 22V12"/><path d="m7.5 4.3 9 5.1"/>',
boxes: '<path d="M11 21.7a2 2 0 0 0 2 0l7-4A2 2 0 0 0 21 16V8a2 2 0 0 0-1-1.7l-7-4a2 2 0 0 0-2 0l-7 4A2 2 0 0 0 3 8v8a2 2 0 0 0 1 1.7Z"/><path d="m3.3 7 8.7 5 8.7-5"/><path d="M12 22V12"/><path d="m7.5 4.3 9 5.1"/>',
clapperboard: '<path d="m12.3 3.5 3 4"/><path d="M20.2 6 3 11l-.9-2.4a2 2 0 0 1 1.3-2.5l13.5-4a2 2 0 0 1 2.5 1.3Z"/><path d="m6.2 5.3 3.1 3.9"/><path d="M3 11h18v8a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2Z"/>',
film: '<path d="m12.3 3.5 3 4"/><path d="M20.2 6 3 11l-.9-2.4a2 2 0 0 1 1.3-2.5l13.5-4a2 2 0 0 1 2.5 1.3Z"/><path d="m6.2 5.3 3.1 3.9"/><path d="M3 11h18v8a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2Z"/>',
video: '<path d="m16 13 5.2 3.1a.5.5 0 0 0 .8-.4V8.3a.5.5 0 0 0-.8-.4L16 11"/><rect x="2" y="6" width="14" height="12" rx="2"/>',
sparkles: '<path d="M12 3l1.7 4.6L18 9l-4.3 1.4L12 15l-1.7-4.6L6 9l4.3-1.4L12 3Z"/><path d="M19 15l.9 2.1L22 18l-2.1.9L19 21l-.9-2.1L16 18l2.1-.9L19 15Z"/>',
images: '<rect x="3" y="3" width="18" height="18" rx="2"/><circle cx="9" cy="9" r="2"/><path d="m21 15-3.1-3.1a2 2 0 0 0-2.8 0L6 21"/>',
library: '<rect x="3" y="3" width="18" height="18" rx="2"/><circle cx="9" cy="9" r="2"/><path d="m21 15-3.1-3.1a2 2 0 0 0-2.8 0L6 21"/>',
users: '<path d="M16 21v-2a4 4 0 0 0-4-4H7a4 4 0 0 0-4 4v2"/><circle cx="9.5" cy="7" r="4"/><path d="M22 21v-2a4 4 0 0 0-3-3.87"/><path d="M16 3.13a4 4 0 0 1 0 7.75"/>',
wallet: '<rect x="3" y="6" width="18" height="13" rx="2"/><path d="M3 10h18"/><path d="M16 14h2"/>',
creditCard: '<rect x="2" y="5" width="20" height="14" rx="2"/><path d="M2 10h20"/>',
settings: '<path d="M12 15.5a3.5 3.5 0 1 0 0-7 3.5 3.5 0 0 0 0 7Z"/><path d="M19.4 15a1.7 1.7 0 0 0 .3 1.8l.1.1a2 2 0 1 1-2.8 2.8l-.1-.1a1.7 1.7 0 0 0-1.8-.3 1.7 1.7 0 0 0-1 1.5V21a2 2 0 1 1-4 0v-.2a1.7 1.7 0 0 0-1.1-1.5 1.7 1.7 0 0 0-1.8.3l-.1.1a2 2 0 1 1-2.8-2.8l.1-.1a1.7 1.7 0 0 0 .3-1.8 1.7 1.7 0 0 0-1.5-1H3a2 2 0 1 1 0-4h.2A1.7 1.7 0 0 0 4.7 9a1.7 1.7 0 0 0-.3-1.8l-.1-.1a2 2 0 1 1 2.8-2.8l.1.1a1.7 1.7 0 0 0 1.8.3 1.7 1.7 0 0 0 1.1-1.5V3a2 2 0 1 1 4 0v.2a1.7 1.7 0 0 0 1 1.5 1.7 1.7 0 0 0 1.8-.3l.1-.1a2 2 0 1 1 2.8 2.8l-.1.1a1.7 1.7 0 0 0-.3 1.8 1.7 1.7 0 0 0 1.5 1h.1a2 2 0 1 1 0 4h-.1a1.7 1.7 0 0 0-1.5 1Z"/>',
airshelf: '<path d="M4.5 19 12 4.5 19.5 19"/><path d="M8 14h8"/><path d="M7 17h10"/><path d="M9.5 10.3c1.5-1.2 3.5-1.2 5 0"/>',
flame: '<path d="M8.5 14.5c0-2 1.5-3.4 3.5-5.5.4 2.2 2.8 3.1 2.8 5.6a3.3 3.3 0 0 1-6.3-.1Z"/><path d="M12 22c4 0 7-2.8 7-7 0-4.7-3.3-7.3-5-12-2 3.3-7 6-7 12a5 5 0 0 0 5 7Z"/>',
search: '<circle cx="11" cy="11" r="7"/><path d="m21 21-4.3-4.3"/>',
bell: '<path d="M6 8a6 6 0 0 1 12 0c0 7 3 9 3 9H3s3-2 3-9"/><path d="M10 21a2 2 0 0 0 4 0"/>',
list: '<path d="M8 6h13"/><path d="M8 12h13"/><path d="M8 18h13"/><path d="M3 6h.01"/><path d="M3 12h.01"/><path d="M3 18h.01"/>',
check: '<path d="M20 6 9 17l-5-5"/>',
x: '<path d="M18 6 6 18"/><path d="m6 6 12 12"/>',
plus: '<path d="M12 5v14"/><path d="M5 12h14"/>',
productPlus: '<path d="M12 22V12"/><path d="M16 17h6"/><path d="M19 14v6"/><path d="M21 10.5V8a2 2 0 0 0-1-1.7l-7-4a2 2 0 0 0-2 0l-7 4A2 2 0 0 0 3 8v8a2 2 0 0 0 1 1.7l7 4a2 2 0 0 0 2 0l1.7-1"/><path d="m3.3 7 8.7 5 8.7-5"/><path d="m7.5 4.3 9 5.1"/>',
arrowRight: '<path d="M5 12h14"/><path d="m12 5 7 7-7 7"/>',
arrowLeft: '<path d="M19 12H5"/><path d="m12 19-7-7 7-7"/>',
arrowUp: '<path d="M12 19V5"/><path d="m5 12 7-7 7 7"/>',
chevronDown: '<path d="m6 9 6 6 6-6"/>',
rotateCcw: '<path d="M3 12a9 9 0 1 0 3-6.7"/><path d="M3 4v5h5"/>',
download: '<path d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4"/><path d="M7 10l5 5 5-5"/><path d="M12 15V3"/>',
upload: '<path d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4"/><path d="m17 8-5-5-5 5"/><path d="M12 3v12"/>',
moreHorizontal: '<circle cx="5" cy="12" r="1.6" fill="currentColor" stroke="none"/><circle cx="12" cy="12" r="1.6" fill="currentColor" stroke="none"/><circle cx="19" cy="12" r="1.6" fill="currentColor" stroke="none"/>',
trash: '<path d="M3 6h18"/><path d="M8 6V4a2 2 0 0 1 2-2h4a2 2 0 0 1 2 2v2"/><path d="m19 6-1 14a2 2 0 0 1-2 2H8a2 2 0 0 1-2-2L5 6"/>',
edit: '<path d="M12 20h9"/><path d="M16.5 3.5a2.1 2.1 0 0 1 3 3L7 19l-4 1 1-4 12.5-12.5Z"/>',
play: '<path d="m6 4 14 8-14 8Z"/>',
clock: '<circle cx="12" cy="12" r="9"/><path d="M12 7v5l3 2"/>',
alertCircle: '<circle cx="12" cy="12" r="9"/><path d="M12 8v4"/><path d="M12 16h.01"/>',
info: '<circle cx="12" cy="12" r="9"/><path d="M12 11v5"/><path d="M12 8h.01"/>',
shieldCheck: '<path d="M12 22s8-4 8-10V5l-8-3-8 3v7c0 6 8 10 8 10Z"/><path d="m9 12 2 2 4-4"/>',
messageCircle: '<path d="M21 11.5a8.5 8.5 0 0 1-12.5 7.5L3 20l1-5.5A8.5 8.5 0 1 1 21 11.5Z"/>',
mail: '<rect x="3" y="5" width="18" height="14" rx="2"/><path d="m3 7 9 6 9-6"/>',
lock: '<rect x="3" y="11" width="18" height="10" rx="2"/><path d="M7 11V7a5 5 0 0 1 10 0v4"/>',
eye: '<path d="M2 12s4-7 10-7 10 7 10 7-4 7-10 7-10-7-10-7Z"/><circle cx="12" cy="12" r="3"/>',
image: '<rect x="3" y="3" width="18" height="18" rx="2"/><circle cx="9" cy="9" r="2"/><path d="m21 15-3.1-3.1a2 2 0 0 0-2.8 0L6 21"/>',
grid: '<rect x="3" y="3" width="7" height="7"/><rect x="14" y="3" width="7" height="7"/><rect x="3" y="14" width="7" height="7"/><rect x="14" y="14" width="7" height="7"/>',
copy: '<rect x="9" y="9" width="13" height="13" rx="2"/><path d="M5 15H4a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2h9a2 2 0 0 1 2 2v1"/>',
save: '<path d="M19 21H5a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h11l5 5v11a2 2 0 0 1-2 2Z"/><path d="M17 21v-8H7v8"/><path d="M7 3v5h8"/>',
userPlus: '<path d="M16 21v-2a4 4 0 0 0-4-4H6a4 4 0 0 0-4 4v2"/><circle cx="9" cy="7" r="4"/><path d="M19 8v6"/><path d="M22 11h-6"/>',
helpCircle: '<circle cx="12" cy="12" r="9"/><path d="M9.5 9a2.5 2.5 0 0 1 5 0c0 1.5-2.5 2-2.5 4"/><path d="M12 17h.01"/>'
};
const ALIASES = {
dashboard: 'home',
products: 'package',
projects: 'clapperboard',
assetFactory: 'sparkles',
account: 'creditCard',
billing: 'creditCard',
team: 'users',
queue: 'list',
arrow: 'arrowRight',
back: 'arrowLeft',
close: 'x',
rerun: 'rotateCcw',
more: 'moreHorizontal',
danger: 'alertCircle'
};
function svg(name, opts) {
opts = opts || {};
const key = ALIASES[name] || name;
const body = PATHS[key] || PATHS.helpCircle;
const size = opts.size || 16;
const strokeWidth = opts.strokeWidth || 1.5;
const className = ['ui-icon', opts.className || ''].join(' ').trim();
const label = opts.label ? ` role="img" aria-label="${escapeAttr(opts.label)}"` : ' aria-hidden="true"';
return `<svg class="${className}" data-icon="${key}" width="${size}" height="${size}" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="${strokeWidth}" stroke-linecap="round" stroke-linejoin="round" focusable="false"${label}>${body}</svg>`;
}
function escapeAttr(value) {
return String(value).replace(/[&<>"']/g, c => ({ '&': '&amp;', '<': '&lt;', '>': '&gt;', '"': '&quot;', "'": '&#39;' }[c]));
}
window.IconKit = {
svg,
names: () => Object.keys(PATHS),
has: name => !!PATHS[ALIASES[name] || name]
};
})();
Binary file not shown.

After

Width:  |  Height:  |  Size: 63 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 66 KiB

+64 -14
View File
@@ -1,5 +1,5 @@
/* ============================================================
流·Studio · Restraint V2.1 · Firecrawl-aligned · 纯净版
Airshelf · Restraint V2.1 · Firecrawl-aligned · 纯净版
============================================================
严格遵循 DESIGN_SPEC_V2.md V2.1 · 不含任何 V1/V2 legacy alias
============================================================ */
@@ -115,11 +115,11 @@
--r-pill: 999px;
/* ===== Font · 关键:--font-mono 必须含 PuHuiTi 中文 fallback ===== */
/* Inter / JetBrains Mono 都不含 CJK 字形。
/* Inter 不含 CJK 字形。
字体链按字符级 fallthrough,中文字符会找下一个候选 —— 必须包含 PuHuiTi。 */
--font-sans: 'Inter', 'Alibaba PuHuiTi', 'PingFang SC', 'Microsoft YaHei', system-ui, sans-serif;
--font-inter: 'Inter', system-ui, sans-serif;
--font-mono: 'JetBrains Mono', 'Geist Mono', ui-monospace, 'Alibaba PuHuiTi', 'PingFang SC', 'Microsoft YaHei', monospace;
--font-mono: 'Inter', 'Alibaba PuHuiTi', 'PingFang SC', 'Microsoft YaHei', system-ui, sans-serif;
/* ===== Transition ===== */
--t-fast: 100ms ease;
@@ -161,6 +161,52 @@ button { font: inherit; cursor: pointer; border: 0; background: none; color: inh
input, textarea, select { font: inherit; color: inherit; outline: none; }
img, svg, video { display: block; max-width: 100%; }
/* ─── Icon system · Lucide-line only ─── */
:root {
--icon-stroke: 1.5;
--icon-xs: 12px;
--icon-s: 14px;
--icon-m: 16px;
--icon-l: 20px;
--icon-xl: 24px;
}
.ui-icon {
width: var(--icon-m);
height: var(--icon-m);
flex: 0 0 auto;
display: block;
fill: none;
stroke: currentColor;
stroke-width: var(--icon-stroke);
stroke-linecap: round;
stroke-linejoin: round;
}
.ui-icon * { vector-effect: non-scaling-stroke; }
.ui-icon [stroke="none"] {
fill: currentColor;
stroke: none;
}
:where(nav a, .search-box, .balance-chip, .queue-chip, .icon-btn, .btn, .chip, .toolbar, .page-head, .toast, .modal, .drawer, .card-del-btn, .row-more, .gen-img-btn, .shortcut, .empty-state) svg:not(.corner):not(.quote-icon) {
flex: 0 0 auto;
fill: none;
stroke: currentColor;
stroke-width: var(--icon-stroke);
stroke-linecap: round;
stroke-linejoin: round;
}
:where(nav a, .search-box, .balance-chip, .queue-chip, .icon-btn, .btn, .chip, .toolbar, .page-head, .toast, .modal, .drawer, .card-del-btn, .row-more, .gen-img-btn, .shortcut, .empty-state) svg:not(.corner):not(.quote-icon) [fill="currentColor"] {
fill: none;
stroke: currentColor;
}
:where(nav a, .search-box, .balance-chip, .queue-chip, .icon-btn, .btn, .chip, .toolbar, .page-head, .toast, .modal, .drawer, .card-del-btn, .row-more, .gen-img-btn, .shortcut, .empty-state) svg:not(.corner):not(.quote-icon) [stroke="none"] {
fill: currentColor;
stroke: none;
}
.row-more svg circle {
fill: currentColor !important;
stroke: none !important;
}
.num, .tnum { font-variant-numeric: tabular-nums; }
.mono { font-family: var(--font-mono); }
.muted { color: var(--black-alpha-56); }
@@ -185,8 +231,8 @@ aside.sidebar {
overflow-y: auto;
}
.brand { display: flex; align-items: center; gap: 10px; padding: 6px 8px 16px; }
.flame { width: 22px; height: 22px; color: var(--heat); }
.flame svg { width: 100%; height: 100%; }
.brand-mark, .flame { width: 22px; height: 22px; color: var(--heat); }
.brand-mark svg, .flame svg { width: 100%; height: 100%; }
.brand .name { font-weight: 600; font-size: 18px; letter-spacing: -.012em; color: var(--accent-black); }
/* sidebar search · Ctrl K Inter Bold 平铺 */
@@ -203,7 +249,7 @@ aside.sidebar {
}
.search-box:hover { border-color: var(--black-alpha-24); }
.search-box:focus-within { border-color: var(--heat-40); box-shadow: inset 0 0 0 1px var(--heat-40); }
.search-box svg { width: 14px; height: 14px; flex-shrink: 0; color: var(--black-alpha-56); }
.search-box svg { width: var(--icon-m); height: var(--icon-m); flex-shrink: 0; color: var(--black-alpha-56); }
.search-box input {
flex: 1; min-width: 0;
border: 0; background: transparent;
@@ -242,7 +288,7 @@ nav a {
}
nav a:hover { background: var(--black-alpha-4); color: var(--accent-black); }
nav a.active { background: var(--heat-12); color: var(--heat); }
nav a svg { width: 14px; height: 14px; opacity: .85; }
nav a svg { width: var(--icon-m); height: var(--icon-m); opacity: .85; }
nav a.active svg { opacity: 1; }
nav a .pill-mini {
margin-left: auto;
@@ -330,20 +376,20 @@ main { position: relative; background: var(--background-base); min-width: 0; }
}
.balance-chip:hover { background: var(--black-alpha-4); border-color: var(--black-alpha-24); }
.balance-chip strong { color: var(--accent-black); font-weight: 600; font-variant-numeric: tabular-nums; }
.balance-chip svg { width: 13px; height: 13px; color: var(--heat); }
.balance-chip svg { width: var(--icon-m); height: var(--icon-m); color: var(--heat); }
.icon-btn {
width: 36px; height: 36px;
display: flex; align-items: center; justify-content: center;
background: var(--surface);
border: 1px solid var(--border-faint);
border-radius: var(--r-md);
border-radius: var(--r-pill);
color: var(--black-alpha-56);
cursor: pointer;
position: relative;
transition: background var(--t-base), border-color var(--t-base), color var(--t-base);
}
.icon-btn:hover { background: var(--black-alpha-4); color: var(--accent-black); border-color: var(--black-alpha-24); }
.icon-btn svg { width: 15px; height: 15px; }
.icon-btn svg { width: var(--icon-m); height: var(--icon-m); }
.icon-btn .dot-noti {
position: absolute; top: 8px; right: 9px;
width: 7px; height: 7px; border-radius: 50%;
@@ -376,7 +422,7 @@ main { position: relative; background: var(--background-base); min-width: 0; }
}
.queue-chip:hover { background: var(--black-alpha-4); border-color: var(--black-alpha-24); }
.queue-chip[hidden] { display: none; }
.queue-chip svg { width: 14px; height: 14px; color: var(--black-alpha-56); }
.queue-chip svg { width: var(--icon-m); height: var(--icon-m); color: var(--black-alpha-56); }
.queue-chip .count {
display: inline-flex; align-items: center; justify-content: center;
height: 20px; min-width: 20px; padding: 0 6px;
@@ -441,7 +487,8 @@ main { position: relative; background: var(--background-base); min-width: 0; }
padding: 0 20px;
font-size: 13.5px;
}
.page-head .actions > .btn svg { width: 14px; height: 14px; }
.page-head .actions > .btn svg { width: var(--icon-s); height: var(--icon-s); }
.page-head .actions > .btn.btn-create svg { width: var(--icon-m); height: var(--icon-m); }
.section-h { display: flex; align-items: baseline; justify-content: space-between; margin-bottom: 14px; }
.section-h h2 { font-size: 16px; font-weight: 600; letter-spacing: -.01em; color: var(--accent-black); }
@@ -482,7 +529,7 @@ main { position: relative; background: var(--background-base); min-width: 0; }
background: var(--black-alpha-5);
border-color: var(--black-alpha-12);
}
.btn svg { width: 13px; height: 13px; }
.btn svg { width: var(--icon-s); height: var(--icon-s); }
.btn[hidden] { display: none; }
.btn-primary {
@@ -1588,7 +1635,10 @@ table.t tbody tr:hover { background: var(--black-alpha-4); }
.product-card:has(.tri-missing-badge:hover),
.product-card:has(.tri-missing-badge:focus-within),
.asset-card:has(.tri-missing-badge:hover),
.asset-card:has(.tri-missing-badge:focus-within) {
.asset-card:has(.tri-missing-badge:focus-within),
.asset-card-2:has(.tri-missing-badge:hover),
.asset-card-2:has(.tri-missing-badge:focus-within) {
overflow: visible;
z-index: 5;
position: relative;
}
+19 -17
View File
@@ -1,5 +1,5 @@
/* ============================================================
流·Studio · Shell renderer V2.1
Airshelf · Shell renderer V2.1
渲染 sidebar / topbar / 网格背景装饰 / Toast / Modal helpers
每个页面调用 Shell.render({ active, crumbs, balance, topActions })
@@ -7,38 +7,40 @@
- sidebar 搜索 ⌘K → "Ctrl K" Inter Bold 平铺(无 kbd 边框)
============================================================ */
const ShellIcon = (name, opts) => window.IconKit ? window.IconKit.svg(name, opts) : '';
const NAV = [
{
id: 'dashboard', label: '工作台', href: 'index.html',
icon: '<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5"><path d="M3 12 12 3l9 9"/><path d="M5 10v10h14V10"/></svg>'
icon: ShellIcon('home')
},
{
id: 'products', label: '商品库', href: 'products.html', badge: '7',
icon: '<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5"><path d="m3 7 9-4 9 4-9 4-9-4z"/><path d="m3 12 9 4 9-4M3 17l9 4 9-4"/></svg>'
icon: ShellIcon('package')
},
{
id: 'projects', label: '视频项目', href: 'projects.html', badge: '8',
icon: '<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5"><rect x="3" y="4" width="18" height="16"/><path d="M7 4v16M16 4v16M3 9h18M3 15h18"/></svg>'
icon: ShellIcon('clapperboard')
},
{
id: 'asset-factory', label: '图片生成', href: 'asset-factory.html',
icon: '<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"><path d="M12 3l1.7 4.6L18 9l-4.3 1.4L12 15l-1.7-4.6L6 9l4.3-1.4L12 3z"/><path d="M19 16l.85 2.3L22 19l-2.15.7L19 22l-.85-2.3L16 19l2.15-.7L19 16z"/></svg>'
icon: ShellIcon('sparkles')
},
{
id: 'library', label: '资产库', href: 'library.html',
icon: '<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5"><rect x="3" y="4" width="6" height="16"/><rect x="11" y="4" width="4" height="16"/><rect x="17" y="6" width="4" height="14"/></svg>'
icon: ShellIcon('images')
},
{
id: 'team', label: '团队', href: 'team.html',
icon: '<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5"><circle cx="9" cy="8" r="3"/><circle cx="17" cy="9" r="2.5"/><path d="M3 19c0-3 2.7-5 6-5s6 2 6 5M14 19c.5-2.4 2.4-4 5-4 .8 0 1.5.2 2 .5"/></svg>'
icon: ShellIcon('users')
},
{
id: 'account', label: '消费', href: 'account.html',
icon: '<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5"><rect x="3" y="6" width="18" height="13" rx="2"/><path d="M3 10h18M16 14h2"/></svg>'
icon: ShellIcon('creditCard')
},
{
id: 'settings', label: '设置', href: 'settings.html',
icon: '<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5"><circle cx="12" cy="12" r="3"/><path d="M19.4 15a1.7 1.7 0 0 0 .3 1.8 2 2 0 0 1-2.8 2.8 1.7 1.7 0 0 0-1.8-.3 1.7 1.7 0 0 0-1 1.5 2 2 0 0 1-4 0 1.7 1.7 0 0 0-1.1-1.5 1.7 1.7 0 0 0-1.8.3 2 2 0 0 1-2.8-2.8 1.7 1.7 0 0 0 .3-1.8 1.7 1.7 0 0 0-1.5-1 2 2 0 0 1 0-4 1.7 1.7 0 0 0 1.5-1.1 1.7 1.7 0 0 0-.3-1.8 2 2 0 0 1 2.8-2.8 1.7 1.7 0 0 0 1.8.3 1.7 1.7 0 0 0 1-1.5 2 2 0 0 1 4 0 1.7 1.7 0 0 0 1 1.5 1.7 1.7 0 0 0 1.8-.3 2 2 0 0 1 2.8 2.8 1.7 1.7 0 0 0-.3 1.8 1.7 1.7 0 0 0 1.5 1 2 2 0 0 1 0 4 1.7 1.7 0 0 0-1.5 1.1Z"/></svg>'
icon: ShellIcon('settings')
}
];
@@ -55,11 +57,11 @@ window.Shell = {
const sidebar = `
<aside class="sidebar">
<div class="brand">
<div class="flame"><svg viewBox="0 0 24 24" fill="currentColor"><path d="M12 2c1 3 4 5 4 9a4 4 0 0 1-4 4 4 4 0 0 1-4-4 5 5 0 0 1 1.5-3.5C10.5 6 11.5 4 12 2zm-1 13c0 2 1 3 1 5 1-1 3-2 3-5 0-1.5-1-2-2-3-1 1-2 2-2 3z"/></svg></div>
<div><div class="name">流·Studio</div></div>
<div class="brand-mark">${ShellIcon('airshelf', { size: 22 })}</div>
<div><div class="name">Airshelf</div></div>
</div>
<div class="search-box" onclick="document.getElementById('global-search').focus()">
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5"><circle cx="11" cy="11" r="7"/><path d="m21 21-4.3-4.3"/></svg>
${ShellIcon('search')}
<input id="global-search" placeholder="搜索"/>
<span class="kbd">Ctrl K</span>
</div>
@@ -90,16 +92,16 @@ window.Shell = {
${crumbHtml}
<div class="right">
<span class="balance-chip" onclick="location.href='account.html'">
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5"><circle cx="12" cy="12" r="9"/><path d="M12 7v10M9 10h4a1.5 1.5 0 0 1 0 3h-3a1.5 1.5 0 0 0 0 3h4"/></svg>
${ShellIcon('creditCard')}
余额 <strong>${balance}</strong>
</span>
<button class="queue-chip" onclick="Shell.toast('任务队列', '3 个进行中')" hidden>
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"><path d="M3 6h18M3 12h18M3 18h18"/></svg>
${ShellIcon('list')}
任务队列
<span class="count">3</span>
</button>
<button class="icon-btn" onclick="location.href='messages.html'" title="消息中心">
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"><path d="M6 8a6 6 0 0 1 12 0c0 7 3 9 3 9H3s3-2 3-9M10 21a2 2 0 0 0 4 0"/></svg>
${ShellIcon('bell')}
<span class="count-noti">12</span>
</button>
<div class="topbar-avatar" onclick="Shell.toast('账户菜单', '李 · li@shop.com')" title="账户">
@@ -120,7 +122,7 @@ window.Shell = {
const toastHtml = `
<div class="toast" id="__toast">
<div class="ic-t"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="3"><polyline points="20 6 9 17 4 12"/></svg></div>
<div class="ic-t">${ShellIcon('check')}</div>
<div class="txt" id="__toast-txt">操作成功<span class="mono">[ 200 OK ]</span></div>
</div>
`;
@@ -129,7 +131,7 @@ window.Shell = {
const lightboxHtml = `
<div class="np-lightbox" id="np-lightbox" onclick="Shell._closeLightbox()">
<button class="lb-x" type="button" onclick="event.stopPropagation();Shell._closeLightbox()">
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round"><path d="M18 6L6 18M6 6l12 12"/></svg>
${ShellIcon('x')}
</button>
<img id="np-lightbox-img" alt="">
<span class="lb-name" id="np-lightbox-name"></span>