AirShelf/电商AI平台/projects.html
iye cae935588b init: 电商AI平台 v2.1 静态设计稿
- 10 个页面 (工作台/项目/商品/流水线/资产/账户/创建向导)
- V2.1 Restraint 设计规范 (冷灰底 + #FA5D19 + 8px 圆角)
- 完整 design-system.html 组件库参考
- SVG line icon · stroke 1.5 全合规

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-15 17:55:11 +08:00

524 lines
25 KiB
HTML

<!doctype html>
<html lang="zh-CN">
<head>
<meta charset="utf-8">
<title>视频项目 · 流·Studio</title>
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&family=JetBrains+Mono:wght@400;500&display=swap" rel="stylesheet">
<link rel="stylesheet" href="assets/restraint.css">
<style>
/* ─── List view ─── */
.proj-name-cell { display: flex; align-items: center; gap: 12px; }
.proj-thumb { width: 40px; height: 52px; flex-shrink: 0; border-radius: var(--r-md); }
.proj-name { font-weight: 600; color: var(--accent-black); font-size: 13.5px; }
.proj-sub { font-size: 11.5px; color: var(--black-alpha-48); margin-top: 3px; font-family: var(--font-mono); letter-spacing: .02em; }
.row-action { display: flex; gap: 4px; visibility: hidden; }
table.t tbody tr:hover .row-action { visibility: visible; }
.row-action a { width: 28px; height: 28px; display: grid; place-items: center; color: var(--black-alpha-56); border-radius: var(--r-md); }
.row-action a:hover { background: var(--surface); color: var(--heat); border: 1px solid var(--border-faint); }
/* ─── View toggle ─── */
.view-toggle { display: inline-flex; border: 1px solid var(--border-faint); border-radius: var(--r-md); overflow: hidden; }
.view-toggle button { padding: 0 14px; background: var(--surface); color: var(--black-alpha-56); font-size: 13px; border-right: 1px solid var(--border-faint); border-radius: 0; height: 36px; cursor: pointer; font-family: inherit; display: flex; align-items: center; gap: 6px; transition: background var(--t-base), color var(--t-base); }
.view-toggle button:last-child { border-right: 0; }
.view-toggle button:hover { background: var(--background-lighter); color: var(--accent-black); }
.view-toggle button.active { background: var(--heat-12); color: var(--heat); font-weight: 600; }
.view-toggle button svg { width: 13px; height: 13px; }
/* ─── Grid view ─── */
.proj-grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(240px, 1fr)); gap: 16px; }
.proj-card { background: var(--surface); border: 1px solid var(--border-faint); border-radius: var(--r-md); cursor: pointer; transition: background .15s; display: flex; flex-direction: column; }
.proj-card:hover { background: var(--background-lighter); border-color: var(--black-alpha-48); }
.proj-card .card-thumb { aspect-ratio: 9/16; max-height: 280px; border-radius: var(--r-md) var(--r-md) 0 0; }
.proj-card .card-body { padding: 14px; display: flex; flex-direction: column; gap: 10px; flex: 1; }
.proj-card .card-name { font-size: 13.5px; font-weight: 600; color: var(--accent-black); line-height: 1.4; }
.proj-card .card-sub { font-size: 11.5px; color: var(--black-alpha-48); font-family: var(--font-mono); letter-spacing: .02em; }
.proj-card .card-foot { display: flex; align-items: center; justify-content: space-between; padding-top: 10px; border-top: 1px solid var(--border-faint); margin-top: auto; }
.proj-card .card-time { font-family: var(--font-mono); font-size: 10.5px; color: var(--black-alpha-48); letter-spacing: .02em; }
/* ─── Empty state ─── */
.empty-state {
background: var(--surface);
border: 1px dashed var(--border-faint);
border-radius: var(--r-md);
padding: 80px 40px;
text-align: center;
color: var(--black-alpha-56);
display: none;
}
.empty-state.show { display: block; }
.empty-state .ic-empty {
width: 48px; height: 48px;
margin: 0 auto 14px;
background: var(--background-lighter);
border: 1px solid var(--border-faint);
border-radius: var(--r-md);
display: grid; place-items: center;
color: var(--black-alpha-48);
}
.empty-state h3 { font-size: 14px; font-weight: 600; color: var(--accent-black); margin-bottom: 6px; }
.empty-state p { font-size: 12.5px; color: var(--black-alpha-48); font-family: var(--font-mono); letter-spacing: .02em; }
/* ─── Result count ─── */
.result-meta {
font-family: var(--font-mono);
font-size: 11px;
color: var(--black-alpha-48);
margin-bottom: 14px;
letter-spacing: .04em;
}
.result-meta .count { color: var(--heat); font-weight: 600; }
</style>
</head>
<body>
<div id="page">
<div class="page-head">
<div>
<h1>视频项目</h1>
<div class="sub"><span class="mono">// 12 个 · 3 进行中 · 8 完成 · 1 失败</span></div>
</div>
<div class="actions">
<a class="btn btn-primary btn-lg" href="projects-new.html">
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5"><path d="M12 5v14M5 12h14"/></svg>
新建项目
</a>
</div>
</div>
<div class="tabs" id="status-tabs">
<div class="tab active" data-filter="all">全部 <span class="count">12</span></div>
<div class="tab" data-filter="wip">进行中 <span class="count">3</span></div>
<div class="tab" data-filter="done">已完成 <span class="count">8</span></div>
<div class="tab" data-filter="fail">失败 <span class="count">1</span></div>
</div>
<div class="toolbar">
<div class="search-inline">
<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>
<input class="input" id="search-input" placeholder="搜索项目名称、商品">
</div>
<button class="chip" onclick="Shell.toast('商品筛选', '/filter/product')">商品 <svg viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="1.5"><path d="M4 6l4 4 4-4"/></svg></button>
<button class="chip" onclick="Shell.toast('脚本来源筛选', '/filter/source')">脚本来源 <svg viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="1.5"><path d="M4 6l4 4 4-4"/></svg></button>
<button class="chip" onclick="Shell.toast('时间筛选', '/filter/date')">创建时间 <svg viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="1.5"><path d="M4 6l4 4 4-4"/></svg></button>
<span class="spacer"></span>
<div class="view-toggle">
<button id="view-grid" data-view="grid">
<svg viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="1.5"><rect x="2" y="2" width="5" height="5"/><rect x="9" y="2" width="5" height="5"/><rect x="2" y="9" width="5" height="5"/><rect x="9" y="9" width="5" height="5"/></svg>
网格
</button>
<button id="view-list" class="active" data-view="list">
<svg viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="1.5"><path d="M2 4h12M2 8h12M2 12h12"/></svg>
列表
</button>
</div>
</div>
<div class="result-meta" id="result-meta">// 显示 <span class="count">12</span> / 12 个项目</div>
<!-- ============= LIST VIEW ============= -->
<div id="list-view">
<table class="t">
<thead>
<tr>
<th style="width:32%">项目</th>
<th>商品</th>
<th>脚本来源</th>
<th style="width:200px">进度</th>
<th>状态</th>
<th style="width:120px">更新于</th>
<th style="width:60px"></th>
</tr>
</thead>
<tbody id="list-tbody">
<tr data-status="wip" data-name="补水面膜 痛点种草" onclick="location.href='pipeline.html#stage-3'">
<td>
<div class="proj-name-cell">
<div class="placeholder proj-thumb"><span class="ph-frame">9:16</span></div>
<div><div class="proj-name">补水面膜 · 痛点种草 · v3</div><div class="proj-sub">6 镜 · 0-15s</div></div>
</div>
</td>
<td>透真补水面膜</td>
<td><span class="muted">AI 全生</span></td>
<td>
<div class="hstack">
<div class="prog"><span class="done"></span><span class="done"></span><span class="cur"></span><span></span><span></span></div>
<span class="muted-2 mono" style="font-size:11px;">3/5</span>
</div>
</td>
<td><span class="pill info"><span class="dot"></span>故事板 待确认</span></td>
<td class="muted-2">12 分钟前</td>
<td>
<div class="row-action">
<a href="pipeline.html#stage-3" onclick="event.stopPropagation()" title="继续"><svg width="14" height="14" viewBox="0 0 16 16"><path d="M5 4l6 4-6 4z" fill="currentColor"/></svg></a>
<a onclick="event.stopPropagation();Shell.toast('更多操作')"><svg width="14" height="14" viewBox="0 0 16 16"><circle cx="3" cy="8" r="1.2" fill="currentColor"/><circle cx="8" cy="8" r="1.2" fill="currentColor"/><circle cx="13" cy="8" r="1.2" fill="currentColor"/></svg></a>
</div>
</td>
</tr>
<tr data-status="wip" data-name="速食牛肉面 加班治愈" onclick="location.href='pipeline.html#stage-2'">
<td>
<div class="proj-name-cell">
<div class="placeholder proj-thumb"><span class="ph-frame">9:16</span></div>
<div><div class="proj-name">速食牛肉面 · 加班治愈</div><div class="proj-sub">4 镜 · 0-12s</div></div>
</div>
</td>
<td>滋啦速食 · 6 桶装</td>
<td><span class="muted">一句话主题</span></td>
<td>
<div class="hstack">
<div class="prog"><span class="done"></span><span class="cur"></span><span></span><span></span><span></span></div>
<span class="muted-2 mono" style="font-size:11px;">2/5</span>
</div>
</td>
<td><span class="pill info"><span class="dot"></span>资产生成中</span></td>
<td class="muted-2">37 分钟前</td>
<td></td>
</tr>
<tr data-status="wip" data-name="透真防晒 通勤对比" onclick="location.href='pipeline.html#stage-4'">
<td>
<div class="proj-name-cell">
<div class="placeholder proj-thumb"><span class="ph-frame">9:16</span></div>
<div><div class="proj-name">透真防晒 · 通勤对比</div><div class="proj-sub">6 镜 · 0-18s</div></div>
</div>
</td>
<td>透真清透防晒霜</td>
<td><span class="muted">AI 全生</span></td>
<td>
<div class="hstack">
<div class="prog"><span class="done"></span><span class="done"></span><span class="done"></span><span class="cur"></span><span></span></div>
<span class="muted-2 mono" style="font-size:11px;">4/5</span>
</div>
</td>
<td><span class="pill info"><span class="dot"></span>视频生成 4/6</span></td>
<td class="muted-2">2 小时前</td>
<td></td>
</tr>
<tr data-status="fail" data-name="咖啡冻干 早八剧情" onclick="location.href='pipeline.html#stage-3'">
<td>
<div class="proj-name-cell">
<div class="placeholder proj-thumb"><span class="ph-frame">9:16</span></div>
<div><div class="proj-name">咖啡冻干 · 早八剧情</div><div class="proj-sub">5 镜 · 0-15s</div></div>
</div>
</td>
<td>三顿半同款冻干</td>
<td><span class="muted">一句话主题</span></td>
<td>
<div class="hstack">
<div class="prog"><span class="done"></span><span class="done"></span><span class="fail"></span><span></span><span></span></div>
<span class="muted-2 mono" style="font-size:11px;">3/5</span>
</div>
</td>
<td><span class="pill err"><span class="dot"></span>故事板生成失败</span></td>
<td class="muted-2">昨天 18:42</td>
<td></td>
</tr>
<tr data-status="done" data-name="蓝牙耳机 开箱测评" onclick="location.href='pipeline.html#stage-5'">
<td>
<div class="proj-name-cell">
<div class="placeholder proj-thumb"><span class="ph-frame">9:16</span></div>
<div><div class="proj-name">蓝牙耳机 · 开箱测评</div><div class="proj-sub">5 镜 · 0-15s</div></div>
</div>
</td>
<td>南卡 Lite Pro</td>
<td><span class="muted">自带脚本</span></td>
<td>
<div class="hstack">
<div class="prog"><span class="done"></span><span class="done"></span><span class="done"></span><span class="done"></span><span class="done"></span></div>
<span class="muted-2 mono" style="font-size:11px;">5/5</span>
</div>
</td>
<td><span class="pill ok"><span class="dot"></span>已完成</span></td>
<td class="muted-2">5 月 7 日</td>
<td></td>
</tr>
<tr data-status="done" data-name="瑜伽裤 通勤穿搭" onclick="location.href='pipeline.html#stage-5'">
<td>
<div class="proj-name-cell">
<div class="placeholder proj-thumb"><span class="ph-frame">9:16</span></div>
<div><div class="proj-name">瑜伽裤 · 通勤穿搭</div><div class="proj-sub">5 镜 · 0-15s</div></div>
</div>
</td>
<td>露露同款瑜伽裤</td>
<td><span class="muted">AI 全生</span></td>
<td>
<div class="hstack">
<div class="prog"><span class="done"></span><span class="done"></span><span class="done"></span><span class="done"></span><span class="done"></span></div>
<span class="muted-2 mono" style="font-size:11px;">5/5</span>
</div>
</td>
<td><span class="pill ok"><span class="dot"></span>已完成</span></td>
<td class="muted-2">5 月 6 日</td>
<td></td>
</tr>
<tr data-status="done" data-name="空气炸锅 小户型" onclick="location.href='pipeline.html#stage-5'">
<td>
<div class="proj-name-cell">
<div class="placeholder proj-thumb"><span class="ph-frame">9:16</span></div>
<div><div class="proj-name">空气炸锅 · 小户型</div><div class="proj-sub">4 镜 · 0-12s</div></div>
</div>
</td>
<td>小熊 4L 空气炸锅</td>
<td><span class="muted">一句话主题</span></td>
<td>
<div class="hstack">
<div class="prog"><span class="done"></span><span class="done"></span><span class="done"></span><span class="done"></span><span class="done"></span></div>
<span class="muted-2 mono" style="font-size:11px;">5/5</span>
</div>
</td>
<td><span class="pill ok"><span class="dot"></span>已完成</span></td>
<td class="muted-2">5 月 4 日</td>
<td></td>
</tr>
<tr data-status="archived" data-name="补水面膜 痛点种草 v1" onclick="location.href='pipeline.html#stage-5'">
<td>
<div class="proj-name-cell">
<div class="placeholder proj-thumb"><span class="ph-frame">9:16</span></div>
<div><div class="proj-name">补水面膜 · 痛点种草 · v1</div><div class="proj-sub">6 镜 · 0-15s</div></div>
</div>
</td>
<td>透真补水面膜</td>
<td><span class="muted">AI 全生</span></td>
<td>
<div class="hstack">
<div class="prog"><span class="done"></span><span class="done"></span><span class="done"></span><span class="done"></span><span class="done"></span></div>
<span class="muted-2 mono" style="font-size:11px;">5/5</span>
</div>
</td>
<td><span class="pill neutral"><span class="dot"></span>已归档</span></td>
<td class="muted-2">4 月 28 日</td>
<td></td>
</tr>
</tbody>
</table>
</div>
<!-- ============= GRID VIEW ============= -->
<div id="grid-view" style="display:none;">
<div class="proj-grid" id="grid-body">
<div class="proj-card" data-status="wip" data-name="补水面膜 痛点种草" onclick="location.href='pipeline.html#stage-3'">
<div class="placeholder card-thumb"><span class="ph-frame">9:16 · 镜 3/6</span></div>
<div class="card-body">
<div>
<div class="card-name">补水面膜 · 痛点种草 · v3</div>
<div class="card-sub" style="margin-top:4px;">透真补水面膜 · 6 镜</div>
</div>
<div class="hstack">
<div class="prog"><span class="done"></span><span class="done"></span><span class="cur"></span><span></span><span></span></div>
<span class="muted-2 mono" style="font-size:10.5px;">3/5</span>
</div>
<div class="card-foot">
<span class="pill info"><span class="dot"></span>故事板 待确认</span>
<span class="card-time">12 分钟前</span>
</div>
</div>
</div>
<div class="proj-card" data-status="wip" data-name="速食牛肉面 加班治愈" onclick="location.href='pipeline.html#stage-2'">
<div class="placeholder card-thumb"><span class="ph-frame">9:16 · 镜 2/4</span></div>
<div class="card-body">
<div>
<div class="card-name">速食牛肉面 · 加班治愈</div>
<div class="card-sub" style="margin-top:4px;">滋啦速食 · 4 镜</div>
</div>
<div class="hstack">
<div class="prog"><span class="done"></span><span class="cur"></span><span></span><span></span><span></span></div>
<span class="muted-2 mono" style="font-size:10.5px;">2/5</span>
</div>
<div class="card-foot">
<span class="pill info"><span class="dot"></span>资产生成中</span>
<span class="card-time">37 分钟前</span>
</div>
</div>
</div>
<div class="proj-card" data-status="wip" data-name="透真防晒 通勤对比" onclick="location.href='pipeline.html#stage-4'">
<div class="placeholder card-thumb"><span class="ph-frame">9:16 · 镜 4/6</span></div>
<div class="card-body">
<div>
<div class="card-name">透真防晒 · 通勤对比</div>
<div class="card-sub" style="margin-top:4px;">透真清透防晒霜 · 6 镜</div>
</div>
<div class="hstack">
<div class="prog"><span class="done"></span><span class="done"></span><span class="done"></span><span class="cur"></span><span></span></div>
<span class="muted-2 mono" style="font-size:10.5px;">4/5</span>
</div>
<div class="card-foot">
<span class="pill info"><span class="dot"></span>视频 4/6</span>
<span class="card-time">2 小时前</span>
</div>
</div>
</div>
<div class="proj-card" data-status="fail" data-name="咖啡冻干 早八剧情" onclick="location.href='pipeline.html#stage-3'">
<div class="placeholder card-thumb"><span class="ph-frame">9:16 · 镜 3/5</span></div>
<div class="card-body">
<div>
<div class="card-name">咖啡冻干 · 早八剧情</div>
<div class="card-sub" style="margin-top:4px;">三顿半同款 · 5 镜</div>
</div>
<div class="hstack">
<div class="prog"><span class="done"></span><span class="done"></span><span class="fail"></span><span></span><span></span></div>
<span class="muted-2 mono" style="font-size:10.5px;">3/5</span>
</div>
<div class="card-foot">
<span class="pill err"><span class="dot"></span>故事板失败</span>
<span class="card-time">昨天 18:42</span>
</div>
</div>
</div>
<div class="proj-card" data-status="done" data-name="蓝牙耳机 开箱测评" onclick="location.href='pipeline.html#stage-5'">
<div class="placeholder card-thumb"><span class="ph-frame">9:16 · 5/5 ✓</span></div>
<div class="card-body">
<div>
<div class="card-name">蓝牙耳机 · 开箱测评</div>
<div class="card-sub" style="margin-top:4px;">南卡 Lite Pro · 5 镜</div>
</div>
<div class="hstack">
<div class="prog"><span class="done"></span><span class="done"></span><span class="done"></span><span class="done"></span><span class="done"></span></div>
<span class="muted-2 mono" style="font-size:10.5px;">5/5</span>
</div>
<div class="card-foot">
<span class="pill ok"><span class="dot"></span>已完成</span>
<span class="card-time">5 月 7 日</span>
</div>
</div>
</div>
<div class="proj-card" data-status="done" data-name="瑜伽裤 通勤穿搭" onclick="location.href='pipeline.html#stage-5'">
<div class="placeholder card-thumb"><span class="ph-frame">9:16 · 5/5 ✓</span></div>
<div class="card-body">
<div>
<div class="card-name">瑜伽裤 · 通勤穿搭</div>
<div class="card-sub" style="margin-top:4px;">露露同款 · 5 镜</div>
</div>
<div class="hstack">
<div class="prog"><span class="done"></span><span class="done"></span><span class="done"></span><span class="done"></span><span class="done"></span></div>
<span class="muted-2 mono" style="font-size:10.5px;">5/5</span>
</div>
<div class="card-foot">
<span class="pill ok"><span class="dot"></span>已完成</span>
<span class="card-time">5 月 6 日</span>
</div>
</div>
</div>
<div class="proj-card" data-status="done" data-name="空气炸锅 小户型" onclick="location.href='pipeline.html#stage-5'">
<div class="placeholder card-thumb"><span class="ph-frame">9:16 · 5/5 ✓</span></div>
<div class="card-body">
<div>
<div class="card-name">空气炸锅 · 小户型</div>
<div class="card-sub" style="margin-top:4px;">小熊 4L · 4 镜</div>
</div>
<div class="hstack">
<div class="prog"><span class="done"></span><span class="done"></span><span class="done"></span><span class="done"></span><span class="done"></span></div>
<span class="muted-2 mono" style="font-size:10.5px;">5/5</span>
</div>
<div class="card-foot">
<span class="pill ok"><span class="dot"></span>已完成</span>
<span class="card-time">5 月 4 日</span>
</div>
</div>
</div>
<div class="proj-card" data-status="archived" data-name="补水面膜 痛点种草 v1" onclick="location.href='pipeline.html#stage-5'">
<div class="placeholder card-thumb"><span class="ph-frame">9:16 · 5/5 ✓</span></div>
<div class="card-body">
<div>
<div class="card-name">补水面膜 · 痛点种草 · v1</div>
<div class="card-sub" style="margin-top:4px;">透真补水面膜 · 6 镜</div>
</div>
<div class="hstack">
<div class="prog"><span class="done"></span><span class="done"></span><span class="done"></span><span class="done"></span><span class="done"></span></div>
<span class="muted-2 mono" style="font-size:10.5px;">5/5</span>
</div>
<div class="card-foot">
<span class="pill neutral"><span class="dot"></span>已归档</span>
<span class="card-time">4 月 28 日</span>
</div>
</div>
</div>
</div>
</div>
<!-- Empty state -->
<div class="empty-state" id="empty">
<div class="ic-empty">
<svg width="20" height="20" 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>
</div>
<h3>没有匹配的项目</h3>
<p>// 试试切换 tab 或修改搜索词</p>
</div>
</div>
<script src="assets/shell.js"></script>
<script>
Shell.render({ active: 'projects', crumbs: [{ label: '工作台', href: 'index.html' }, { label: '视频项目' }] });
// ============== Tab + search filter + view toggle ==============
const state = { filter: 'all', view: 'list', search: '' };
const TOTAL = 8; // 实际渲染的样本数
function applyFilter() {
const isList = state.view === 'list';
document.getElementById('list-view').style.display = isList ? '' : 'none';
document.getElementById('grid-view').style.display = isList ? 'none' : '';
const items = document.querySelectorAll(isList ? '#list-tbody tr' : '.proj-card');
let visible = 0;
items.forEach(el => {
const status = el.dataset.status || '';
const name = (el.dataset.name || '').toLowerCase();
const matchFilter = state.filter === 'all' || status.split(' ').includes(state.filter);
const matchSearch = !state.search || name.includes(state.search.toLowerCase());
const show = matchFilter && matchSearch;
el.style.display = show ? '' : 'none';
if (show) visible++;
});
// empty state
const empty = document.getElementById('empty');
if (visible === 0) {
empty.classList.add('show');
document.getElementById('list-view').style.display = 'none';
document.getElementById('grid-view').style.display = 'none';
} else {
empty.classList.remove('show');
}
// result meta
document.getElementById('result-meta').innerHTML = `// 显示 <span class="count">${visible}</span> / ${TOTAL} 个项目`;
}
// Tab clicks
document.querySelectorAll('#status-tabs .tab').forEach(t => {
t.addEventListener('click', () => {
document.querySelectorAll('#status-tabs .tab').forEach(x => x.classList.remove('active'));
t.classList.add('active');
state.filter = t.dataset.filter;
applyFilter();
Shell.toast('筛选: ' + t.textContent.trim().split(' ')[0], 'filter=' + state.filter);
});
});
// View toggle
document.querySelectorAll('.view-toggle button').forEach(b => {
b.addEventListener('click', () => {
document.querySelectorAll('.view-toggle button').forEach(x => x.classList.remove('active'));
b.classList.add('active');
state.view = b.dataset.view;
applyFilter();
Shell.toast('视图切换: ' + (state.view === 'list' ? '列表' : '网格'), 'view=' + state.view);
});
});
// Search
document.getElementById('search-input').addEventListener('input', e => {
state.search = e.target.value.trim();
applyFilter();
});
applyFilter();
</script>
</body>
</html>