Compare commits

...

25 Commits

Author SHA1 Message Date
kang
25eb3276f9 docs: 更新 status.md 最终状态(47/56 套 88 key)
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-22 18:04:45 +08:00
9cb1bf5c70 auto-save 2026-04-22 18:03 (~1) 2026-04-22 18:04:02 +08:00
kang
d42a4135f8 feat: W37 DailyUI 30 天全上云 + 多变体 tab
88 个 figma 文件 key,W37 modal 现在有 30 个 tab 切换每天一个设计

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-22 18:02:30 +08:00
4bd1f3a2f0 auto-save 2026-04-22 17:58 (~1) 2026-04-22 17:58:32 +08:00
f9cabfb7c7 auto-save 2026-04-22 17:52 (~1) 2026-04-22 17:53:02 +08:00
77fc031f4a auto-save 2026-04-22 17:47 (~1) 2026-04-22 17:47:31 +08:00
971e0f0d68 auto-save 2026-04-22 17:41 (~1) 2026-04-22 17:42:01 +08:00
39fd2c2428 auto-save 2026-04-22 17:36 (~1) 2026-04-22 17:36:31 +08:00
6c398add3f auto-save 2026-04-22 17:30 (~1) 2026-04-22 17:31:01 +08:00
bd00cbafd0 auto-save 2026-04-22 17:25 (~1) 2026-04-22 17:25:21 +08:00
62763a538f auto-save 2026-04-22 17:19 (~1) 2026-04-22 17:19:51 +08:00
a90b5e8c51 auto-save 2026-04-22 17:14 (~1) 2026-04-22 17:14:19 +08:00
7c29d0479e auto-save 2026-04-22 17:08 (+1, ~1) 2026-04-22 17:08:49 +08:00
7cdc1eb31b auto-save 2026-04-22 17:03 (~1) 2026-04-22 17:03:20 +08:00
ea3e683900 auto-save 2026-04-22 16:57 (~1) 2026-04-22 16:57:50 +08:00
fb11a1d365 auto-save 2026-04-22 16:52 (~1) 2026-04-22 16:52:21 +08:00
47dcdabc35 auto-save 2026-04-22 16:46 (~6) 2026-04-22 16:46:51 +08:00
89dacb708f auto-save 2026-04-22 16:41 (~1) 2026-04-22 16:41:21 +08:00
34e05c6dc0 auto-save 2026-04-22 16:35 (~1) 2026-04-22 16:35:50 +08:00
a41e12500d auto-save 2026-04-22 16:30 (~1) 2026-04-22 16:30:21 +08:00
20eb9388c3 auto-save 2026-04-22 16:24 (~1) 2026-04-22 16:24:51 +08:00
cc9bbd07cc auto-save 2026-04-22 16:19 (~1) 2026-04-22 16:19:21 +08:00
a3126d3e7e auto-save 2026-04-22 16:13 (~1) 2026-04-22 16:13:52 +08:00
f46cf75aec auto-save 2026-04-22 16:08 (~1) 2026-04-22 16:08:22 +08:00
5a69e68e40 auto-save 2026-04-22 16:02 (~1) 2026-04-22 16:02:53 +08:00
9 changed files with 1768 additions and 298 deletions

View File

@@ -31,13 +31,14 @@
└── .memory/
```
## 已写入 Figma Draftsslim 模式 43 个
## 已写入 Figma Drafts最终 88 个文件 key47/56 套
- W1 测试 1 个(手动 open+ 42 个 slim 批量
- **W37 Daily UI**30 天的 kit 只入了第 1 天("Day 03 - Videos Website Landing"),其他 29 天本地 `extracted/W37/` 保留
- W5 Wiloa 全 4 变体Hotel/Plant/Restaurant/Travel
- W56 Orabel 全 6 页Home/About/Portfolio/Blog/Contact/Open Menu
- 其他 32 个 template 各 1 个 fig
- 35 套 `.fig` 原生全上云
- 12 套 `.sketch` 经 Figma 自动转换上云
- **W37 Daily UI 30 天全部上云**30 个 variantsmodal 8 行折行 tabs
- W5 Wiloa 4 变体 / W12 Premise 2 变体 / W36 AppStarter 4 变体 / W56 Orabel 6 页 全部多变体 tab 切换
- 9 套 XD/PSD 只本地保留Figma Import 不收 .xd/.psd
- 有 8 个重复 fileKeyPlaywright 崩溃 + Figma 自动重试导致API 403 删不了,需手动 Drafts 页删
## 非 Figma 原生的 21 套(已搁置)

View File

@@ -34,6 +34,188 @@
"message": "auto-save 2026-04-22 15:51 (~1)",
"hash": "54a0ca8",
"files_changed": 1
},
{
"ts": "2026-04-22T15:57:23+08:00",
"type": "commit",
"message": "auto-save 2026-04-22 15:57 (~2)",
"hash": "be6d4f7",
"files_changed": 2
},
{
"ts": "2026-04-22T15:58:06+08:00",
"type": "commit",
"message": "feat: 12 套 Sketch 也上 Figma47/47 模板全部投射就位",
"hash": "3237ef8",
"files_changed": 3
},
{
"ts": "2026-04-22T16:02:53+08:00",
"type": "commit",
"message": "auto-save 2026-04-22 16:02 (~1)",
"hash": "5a69e68",
"files_changed": 1
},
{
"ts": "2026-04-22T16:08:22+08:00",
"type": "commit",
"message": "auto-save 2026-04-22 16:08 (~1)",
"hash": "f46cf75",
"files_changed": 1
},
{
"ts": "2026-04-22T16:13:52+08:00",
"type": "commit",
"message": "auto-save 2026-04-22 16:13 (~1)",
"hash": "a3126d3",
"files_changed": 1
},
{
"ts": "2026-04-22T16:19:21+08:00",
"type": "commit",
"message": "auto-save 2026-04-22 16:19 (~1)",
"hash": "cc9bbd0",
"files_changed": 1
},
{
"ts": "2026-04-22T16:24:51+08:00",
"type": "commit",
"message": "auto-save 2026-04-22 16:24 (~1)",
"hash": "20eb938",
"files_changed": 1
},
{
"ts": "2026-04-22T16:30:21+08:00",
"type": "commit",
"message": "auto-save 2026-04-22 16:30 (~1)",
"hash": "a41e125",
"files_changed": 1
},
{
"ts": "2026-04-22T16:35:50+08:00",
"type": "commit",
"message": "auto-save 2026-04-22 16:35 (~1)",
"hash": "34e05c6",
"files_changed": 1
},
{
"ts": "2026-04-22T16:41:21+08:00",
"type": "commit",
"message": "auto-save 2026-04-22 16:41 (~1)",
"hash": "89dacb7",
"files_changed": 1
},
{
"ts": "2026-04-22T16:46:51+08:00",
"type": "commit",
"message": "auto-save 2026-04-22 16:46 (~6)",
"hash": "47dcdab",
"files_changed": 6
},
{
"ts": "2026-04-22T16:52:21+08:00",
"type": "commit",
"message": "auto-save 2026-04-22 16:52 (~1)",
"hash": "fb11a1d",
"files_changed": 1
},
{
"ts": "2026-04-22T16:57:50+08:00",
"type": "commit",
"message": "auto-save 2026-04-22 16:57 (~1)",
"hash": "ea3e683",
"files_changed": 1
},
{
"ts": "2026-04-22T17:03:20+08:00",
"type": "commit",
"message": "auto-save 2026-04-22 17:03 (~1)",
"hash": "7cdc1eb",
"files_changed": 1
},
{
"ts": "2026-04-22T17:08:49+08:00",
"type": "commit",
"message": "auto-save 2026-04-22 17:08 (+1, ~1)",
"hash": "7c29d04",
"files_changed": 2
},
{
"ts": "2026-04-22T17:14:19+08:00",
"type": "commit",
"message": "auto-save 2026-04-22 17:14 (~1)",
"hash": "a90b5e8",
"files_changed": 1
},
{
"ts": "2026-04-22T17:19:51+08:00",
"type": "commit",
"message": "auto-save 2026-04-22 17:19 (~1)",
"hash": "62763a5",
"files_changed": 1
},
{
"ts": "2026-04-22T17:25:21+08:00",
"type": "commit",
"message": "auto-save 2026-04-22 17:25 (~1)",
"hash": "bd00cba",
"files_changed": 1
},
{
"ts": "2026-04-22T17:31:01+08:00",
"type": "commit",
"message": "auto-save 2026-04-22 17:30 (~1)",
"hash": "6c398ad",
"files_changed": 1
},
{
"ts": "2026-04-22T17:36:31+08:00",
"type": "commit",
"message": "auto-save 2026-04-22 17:36 (~1)",
"hash": "39fd2c2",
"files_changed": 1
},
{
"ts": "2026-04-22T17:42:01+08:00",
"type": "commit",
"message": "auto-save 2026-04-22 17:41 (~1)",
"hash": "971e0f0",
"files_changed": 1
},
{
"ts": "2026-04-22T17:47:31+08:00",
"type": "commit",
"message": "auto-save 2026-04-22 17:47 (~1)",
"hash": "77fc031",
"files_changed": 1
},
{
"ts": "2026-04-22T17:53:02+08:00",
"type": "commit",
"message": "auto-save 2026-04-22 17:52 (~1)",
"hash": "f9cabfb",
"files_changed": 1
},
{
"ts": "2026-04-22T17:58:32+08:00",
"type": "commit",
"message": "auto-save 2026-04-22 17:58 (~1)",
"hash": "4bd1f3a",
"files_changed": 1
},
{
"ts": "2026-04-22T18:02:30+08:00",
"type": "commit",
"message": "feat: W37 DailyUI 30 天全上云 + 多变体 tab",
"hash": "d42a413",
"files_changed": 4
},
{
"ts": "2026-04-22T18:04:02+08:00",
"type": "commit",
"message": "auto-save 2026-04-22 18:03 (~1)",
"hash": "9cb1bf5",
"files_changed": 1
}
]
}

23
.project.json Normal file
View File

@@ -0,0 +1,23 @@
{
"created" : "2026-04-22",
"name" : "figma模板库",
"ownership" : "personal",
"pin_order" : 1776848911,
"pinned" : true,
"status" : "new",
"urls" : [
{
"label" : "git",
"type" : "repo",
"url" : "https:\/\/git.kang-kang.com\/kangwan\/figma-templates-showcase"
},
{
"label" : "figma-lib",
"type" : "app",
"url" : "https:\/\/figma-lib.kang-kang.com"
}
],
"worklog" : {
"path" : "\/Users\/kangwan\/Projects\/research\/20260422-figma模板库\/.memory\/worklog.json"
}
}

View File

@@ -258,5 +258,121 @@
{
"key": "IGfS4r69yANQazOxLSjnyQ",
"name": "yoyolabs-Limitless-for-Web-file"
},
{
"key": "IdA0ICXerQVBhYUfjXGL1H",
"name": "02_DailyUI - Discover Mountain Tour"
},
{
"key": "eVqhxYBPehYLPEGTCRipyW",
"name": "02_DailyUI - Coffee Shop Subscription Website Landing"
},
{
"key": "r0qplmCsBONsRIIDavwYF3",
"name": "02_DailyUI - Coffee Shop Website Landing"
},
{
"key": "0fNvQCQqC8KbFaquti76RM",
"name": "04_DailyUI - Coffee Shop Website Landing"
},
{
"key": "8yrmXShwcW73BB3cx1RzUO",
"name": "04_DailyUI - Coffee Shop Website Landing"
},
{
"key": "wppvrY7Zkept7rFW8BSEJv",
"name": "04_DailyUI - Product Page Website Landing"
},
{
"key": "eLgBq75wWJQFA4cnyCv6I5",
"name": "04_DailyUI - Preloved Electronic Ecommerce Website Landing"
},
{
"key": "hHHcJtfuhLAdrMLTAlF5Ba",
"name": "04_DailyUI - Crypto Trading App Website Landing"
},
{
"key": "3Bs5YdV3g3jOgqAOVv7IOW",
"name": "04_DailyUI - Creative Digital Agency Website"
},
{
"key": "z669xSruU8CdphtgLhRjcO",
"name": "04_DailyUI - Technology Blog News Website UI"
},
{
"key": "0GK6JkzINs5VzztfvA73m2",
"name": "04_DailyUI - Web Development Agency Website"
},
{
"key": "DnblGVf6MKSfWi0wPj8KZc",
"name": "04_DailyUI - Travel Tour Booking Website Landing"
},
{
"key": "cWaRxvQvuam5z03BkeESwG",
"name": "04_DailyUI - Nursery & Kids Furniture Product Sale"
},
{
"key": "KaWKPHRdIJn5GiJabbwSzp",
"name": "02_DailyUI - Dentist Service Appointment"
},
{
"key": "9IEKPzpkUgZniVdnmC10Aw",
"name": "03_DailyUI - Baby Fashion Landing"
},
{
"key": "pB2abP5veEWUYLsf7HS9tF",
"name": "03_DailyUI - Movie Streaming Landing"
},
{
"key": "hvcfo3wknaJGqnjPBv9a0P",
"name": "03_DailyUI - Female Boots Product Detail"
},
{
"key": "sMyOUigPVBCqS1lt7YpIEV",
"name": "03_DailyUI - Freelancer Hiring Website UI"
},
{
"key": "oUvrAAeDBqoeFpRa2hh7Rc",
"name": "03_DailyUI - Online Daily Catering Order"
},
{
"key": "ZUA5LAN9mmwgckGYYov4Nx",
"name": "03_DailyUI House Rental Booking"
},
{
"key": "U8Ivq70rEJ0PfApY7UozDO",
"name": "03_DailyUI_Bridal_Dress_Rent"
},
{
"key": "oDTnlaSfecAEE7BwM1skE9",
"name": "03_DailyUI_Bridal_Dress_Rent"
},
{
"key": "4uZw8jE1RTmULcmfHUQaiq",
"name": "03_DailyUI_Restaurant_Booking"
},
{
"key": "0FrxkoqSlwBmCfUkyQcnFf",
"name": "03_DailyUI_Series_Movie"
},
{
"key": "5WPExo1H1neQp1JyIwqHeZ",
"name": "04_DailyUI_Laptop_Product_Detail"
},
{
"key": "wJgnKYmet4emAvRir9b9F7",
"name": "02_DailyUI_Shoes_Shop"
},
{
"key": "x0mb4vUi4NXV9ehkwiHVUt",
"name": "04_MovieTime_DailyUI"
},
{
"key": "nBqXtoL8mA0ei5R1v6UPqa",
"name": "04_Gowezz_Bike_Product_Detail"
},
{
"key": "0vZIEbrB7t4RUTVggvABZH",
"name": "04_DailyUI_v4_UI_Workspace_Website"
}
]

File diff suppressed because it is too large Load Diff

View File

@@ -35,61 +35,62 @@ def main():
sys.exit(1)
figma_files = json.loads(figma_files_path.read_text()) # list of {key, name, ...}
# Match each template (fig preferred, else sketch) against cloud files
# Match each template source file to ALL matching cloud files (supports multi-variant)
matches = []
used_keys = set()
for t in manifest['templates']:
source_files = t['fig'] if t['fig'] else t['sketch']
if not source_files: continue
kind = 'fig' if t['fig'] else 'sketch'
targets = [Path(f).stem for f in source_files]
if t.get('archive'):
targets.append(Path(t['archive']).stem)
targets.append(t['name'])
best_overall = (0, None)
for tgt in targets:
score, cand = best_match(tgt, [f for f in figma_files if f['key'] not in used_keys])
if score > best_overall[0]:
best_overall = (score, cand)
if score > 0.95:
break
score, cand = best_overall
stem = Path(source_files[0]).stem
if cand and score >= 0.6:
used_keys.add(cand['key'])
matches.append({
'W': t['id'], 'name': t['name'], 'kind': kind, 'source_stem': stem,
'matched': cand['name'], 'key': cand['key'], 'score': round(score, 3)
})
else:
matches.append({
'W': t['id'], 'name': t['name'], 'kind': kind, 'source_stem': stem,
'matched': None, 'best_score': round(best_overall[0], 3) if cand else 0,
'best_candidate': cand['name'] if cand else None
})
variants = []
for src in source_files:
stem = Path(src).stem
# Match this specific source stem to best unused cloud file
score, cand = best_match(stem, [f for f in figma_files if f['key'] not in used_keys])
if cand and score >= 0.6:
used_keys.add(cand['key'])
variants.append({
'source_stem': stem,
'matched': cand['name'],
'key': cand['key'],
'score': round(score, 3)
})
matches.append({
'W': t['id'], 'name': t['name'], 'kind': kind,
'source_count': len(source_files),
'variants': variants,
})
# Update web/data.json
data_path = ROOT/'web'/'data.json'
data = json.loads(data_path.read_text())
by_W = {m['W']: m for m in matches if m.get('key')}
by_W = {m['W']: m for m in matches}
for t in data['templates']:
m = by_W.get(t['id'])
if m:
t['figma_key'] = m['key']
t['figma_url'] = f"https://www.figma.com/file/{m['key']}"
if m and m['variants']:
first = m['variants'][0]
t['figma_key'] = first['key']
t['figma_url'] = f"https://www.figma.com/file/{first['key']}"
t['figma_variants'] = [
{'name': v['source_stem'], 'matched': v['matched'], 'key': v['key'],
'url': f"https://www.figma.com/file/{v['key']}"}
for v in m['variants']
]
else:
t['figma_key'] = None
t['figma_url'] = None
t['figma_variants'] = []
# update banner with imported count
# update banner with imported count + variant total
imported = sum(1 for t in data['templates'] if t['figma_key'])
total_variants = sum(len(t.get('figma_variants', [])) for t in data['templates'])
fig_cnt = sum(1 for t in manifest['templates'] if t['fig'])
sketch_only_cnt = sum(1 for t in manifest['templates'] if not t['fig'] and t['sketch'])
data['imported_summary'] = (
f"✅ <b>{imported}/56 套</b>已云端就位在 "
f"✅ <b>{imported}/56 套</b>(共 {total_variants} 个文件)已云端就位在 "
f"<a href='https://www.figma.com/files/team/1304178887825899477/drafts' target='_blank'>Figma Drafts</a>"
f"{fig_cnt} .fig 原生 + {sketch_only_cnt} .sketch 经 Figma 转换)。"
f"点卡片 → modal → iframe 实时投射。"
f"{fig_cnt} .fig + {sketch_only_cnt} .sketch 转换)。"
f"点卡片 → modal → 多变体 tab 切换 + iframe 实时投射。"
)
data_path.write_text(json.dumps(data, ensure_ascii=False, indent=2))
@@ -97,13 +98,14 @@ def main():
report = ROOT/'figma-match-report.json'
report.write_text(json.dumps(matches, ensure_ascii=False, indent=2))
total_matchable = sum(1 for t in manifest['templates'] if t['fig'] or t['sketch'])
print(f"Matched {imported}/{total_matchable} templates ({fig_cnt} fig + {sketch_only_cnt} sketch-only)")
multi_variant = sum(1 for m in matches if len(m['variants']) > 1)
print(f"Matched {imported}/{total_matchable} templates, {total_variants} total file keys, {multi_variant} with multi variants")
print(f"Report: {report.relative_to(ROOT)}")
unmatched = [m for m in matches if not m.get('key')]
unmatched = [m for m in matches if not m['variants']]
if unmatched:
print(f"\n⚠️ {len(unmatched)} unmatched:")
for m in unmatched:
print(f" {m['W']:4s} {m['name'][:50]:50s} best='{m.get('best_candidate','')[:40]}' score={m.get('best_score',0)}")
print(f" {m['W']:4s} {m['name'][:50]:50s}")
if __name__ == '__main__':
main()

View File

@@ -89,11 +89,28 @@ function openModal(id) {
const t = DATA.templates.find(x => x.id === id);
if (!t) return;
const body = document.getElementById('modalBody');
const figmaSection = t.figma_key
? `<iframe class="figma-embed" src="https://embed.figma.com/design/${t.figma_key}/?embed-host=kang&footer=false" allowfullscreen></iframe>`
const variants = t.figma_variants || (t.figma_key ? [{name: t.name, key: t.figma_key, url: t.figma_url}] : []);
const hasMulti = variants.length > 1;
const tabsHtml = hasMulti
? `<div class="variant-tabs" id="variantTabs">
${variants.map((v, i) => `<button class="vtab ${i===0?'active':''}" data-idx="${i}">${escapeHtml(v.name || v.matched || `变体 ${i+1}`)}</button>`).join('')}
</div>`
: '';
const firstVariant = variants[0];
const iframeHtml = firstVariant
? `<iframe class="figma-embed" id="variantFrame" src="https://embed.figma.com/design/${firstVariant.key}/?embed-host=kang&footer=false" allowfullscreen></iframe>`
: '';
const openBtn = firstVariant
? `<a class="btn" id="variantOpenBtn" href="${firstVariant.url}" target="_blank">在 Figma 打开 →</a>`
: (t.has_fig
? `<a class="btn" href="https://www.figma.com/files/recent" target="_blank">在 Figma Drafts 搜 "${escapeAttr(t.name.split(/[\s\-–—]/)[0])}" →</a>`
: '');
body.innerHTML = `
<h2>${escapeHtml(t.name)}</h2>
<h2>${escapeHtml(t.name)}${hasMulti ? ` <span class="vcount">${variants.length} 变体</span>` : ''}</h2>
<div class="sub2">
<code>${t.id}</code>
<span>${t.archive_size_mb} MB</span>
@@ -103,24 +120,35 @@ function openModal(id) {
${t.has_psd ? `<span class="badge psd">PSD</span>` : ''}
</div>
<div class="actions">
${t.figma_url
? `<a class="btn" href="${t.figma_url}" target="_blank">在 Figma 打开 →</a>`
: (t.has_fig
? `<a class="btn" href="https://www.figma.com/files/recent" target="_blank">在 Figma Drafts 搜 "${escapeAttr(t.name.split(/[\s\-–—]/)[0])}" →</a>`
: '')}
${openBtn}
<a class="btn ghost" href="${t.source_rel}" onclick="revealSource('${t.id}');return false;">在 Finder 中显示源包</a>
<a class="btn ghost" href="javascript:void(0)" onclick="copyPath('${t.id}');">复制源文件路径</a>
</div>
${figmaSection}
${tabsHtml}
${iframeHtml}
<div class="spec">
<div class="k">源包</div><div class="v">source/${t.id}/${t.archive}</div>
<div class="k">解压目录</div><div class="v">extracted/${t.id}/</div>
${t.figma_key ? `<div class="k">Figma Key</div><div class="v">${t.figma_key}</div>` : ''}
${firstVariant ? `<div class="k">Figma Key</div><div class="v" id="variantKey">${firstVariant.key}</div>` : ''}
</div>
<div class="gallery">
${(t.gallery||[]).map(g => `<img src="${g}" loading="lazy">`).join('')}
</div>
`;
if (hasMulti) {
document.getElementById('variantTabs').addEventListener('click', e => {
const btn = e.target.closest('.vtab');
if (!btn) return;
const idx = +btn.dataset.idx;
const v = variants[idx];
document.querySelectorAll('.vtab').forEach(b => b.classList.toggle('active', b === btn));
document.getElementById('variantFrame').src = `https://embed.figma.com/design/${v.key}/?embed-host=kang&footer=false`;
document.getElementById('variantOpenBtn').href = v.url;
document.getElementById('variantKey').textContent = v.key;
});
}
document.getElementById('modal').hidden = false;
document.body.style.overflow = 'hidden';
}

File diff suppressed because it is too large Load Diff

View File

@@ -79,6 +79,12 @@ main{padding:32px 0 80px}
.spec .v{color:var(--text);font-family:ui-monospace,monospace;font-size:12px;word-break:break-all}
.figma-embed{width:100%;aspect-ratio:16/10;border:0;border-radius:10px;background:var(--panel2);margin-bottom:16px}
.modal-body h2 .vcount{display:inline-block;margin-left:8px;padding:2px 10px;background:rgba(107,92,255,.2);color:#b5bbf8;font-size:12px;font-weight:500;border-radius:999px;vertical-align:middle}
.variant-tabs{display:flex;gap:6px;flex-wrap:wrap;margin-bottom:12px;padding-bottom:12px;border-bottom:1px solid var(--line)}
.vtab{background:var(--panel2);border:1px solid var(--line);color:var(--muted);padding:6px 12px;border-radius:8px;cursor:pointer;font-size:12px;transition:all .15s;font-family:ui-monospace,SFMono-Regular,Menlo,monospace;max-width:240px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
.vtab:hover{color:var(--text);border-color:#3a3f55}
.vtab.active{background:var(--brand);border-color:var(--brand);color:#fff}
/* FOOTER */
footer{border-top:1px solid var(--line);padding:20px 0;color:var(--muted);font-size:12px}
footer .wrap{display:flex;justify-content:space-between;gap:20px;flex-wrap:wrap}