Files
guizang-social-card-skill/assets/template-swiss-card.html
T
郭浩andClaude Opus 4.7 82d221bb90 v0.13 baseline · git 初始化 + 仓库瘦身
把所有本地 demo / 测试 / 上传图 / 调试脚本搬入 local-tests/,写
.gitignore 忽略整个测试目录与 node_modules/。本次起 skill 仓库
只跟踪:SKILL.md / HANDOFF.md / PRODUCT.md / assets/(3 模板)/
references/(15 规则文档)/ agents/openai.yaml / validate-social-deck.mjs
/ package.json + lock。

历次版本(v0.1-v0.12)的全部实质改动已包含在当前文件状态中——
HANDOFF.md 的版本历史段保留作为时间轴文字记录,但代码层面以
此 commit 为唯一基线,后续逐次 commit 对应 HANDOFF.md 新增段落。

HANDOFF/PRODUCT/category-cookbook/components/style-system 中的
demo 路径引用已加 local-tests/ 前缀。SKILL.md Step 5 line 190 的
'social-card-<slug>/' 是下游用户工作区的命名约定,未改动。

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

936 lines
29 KiB
HTML
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<!doctype html>
<html lang="zh-CN" data-accent="ikb">
<head>
<meta charset="utf-8">
<title>[必填] 替换为社交卡组标题 · Social Card Set</title>
<meta name="viewport" content="width=device-width,initial-scale=1">
<link rel="preconnect" href="https://fonts.googleapis.com">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<link rel="stylesheet" href="https://fonts.googleapis.com/css2?family=Inter:wght@200;300;400;500;600;700&family=Noto+Sans+SC:wght@200;300;400;500;600;700&family=IBM+Plex+Mono:wght@400;500;600&display=swap">
<script src="https://unpkg.com/lucide@latest/dist/umd/lucide.min.js"></script>
<style>
/* =========================================================
Swiss International — Social Card Seed Template
---------------------------------------------------------
Three boards built in one HTML:
.poster.xhs 1080×1440 (3:4 小红书)
.poster.square 1080×1080 (1:1 微信方封面)
.poster.wide 2100× 900 (21:9 微信主封面)
.pair-preview 21:9 + 1:1 配对预览
Accent switching via <html data-accent="…">:
ikb (Klein Blue) | lemon-yellow | lemon-green | safety-orange
Fill posters where marked <!-- POSTERS_HERE -->.
Each poster carries one Layout Recipe (S01-S12) from
references/layout-recipes.md.
========================================================= */
/* ---------- 1. Accent palettes (4 mutually exclusive) ---------- */
:root,
[data-accent="ikb"] {
--paper: #fafaf8;
--ink: #0a0a0a;
--grey-1: #f0f0ee;
--grey-2: #d4d4d2;
--grey-3: #737373;
--accent: #002FA7;
--accent-on: #ffffff;
}
[data-accent="lemon-yellow"] {
--paper: #fafaf8;
--ink: #0a0a0a;
--grey-1: #f0f0ee;
--grey-2: #d4d4d2;
--grey-3: #737373;
--accent: #FFD500;
--accent-on: #0a0a0a;
}
[data-accent="lemon-green"] {
--paper: #fafaf8;
--ink: #0a0a0a;
--grey-1: #f0f0ee;
--grey-2: #d4d4d2;
--grey-3: #737373;
--accent: #C5E803;
--accent-on: #0a0a0a;
}
[data-accent="safety-orange"] {
--paper: #fafaf8;
--ink: #0a0a0a;
--grey-1: #f0f0ee;
--grey-2: #d4d4d2;
--grey-3: #737373;
--accent: #FF6B35;
--accent-on: #ffffff;
}
/* ---------- 2. Typography stack ---------- */
:root {
--sans: "Inter", "Helvetica Neue", Helvetica, "Noto Sans SC",
-apple-system, "PingFang SC", "Microsoft YaHei UI", sans-serif;
--sans-zh: "Noto Sans SC", -apple-system, "PingFang SC",
"Microsoft YaHei UI", "Inter", sans-serif;
--mono: "IBM Plex Mono", ui-monospace, "SF Mono", Consolas, monospace;
}
/* ---------- 3. Spacing tokens (8/12/16/24/32/40/48/64/80/96/160) ---------- */
:root {
--sp-3: 8px;
--sp-4: 12px;
--sp-5: 16px;
--sp-6: 24px;
--sp-7: 32px;
--sp-8: 40px;
--sp-9: 48px;
--sp-10: 64px;
--sp-11: 80px;
--sp-12: 96px;
--sp-13:160px;
}
/* ---------- 4. Reset & sheet ---------- */
*,*::before,*::after { box-sizing: border-box; }
html, body { margin: 0; padding: 0; }
body {
background: #1a1a1a;
font-family: var(--sans);
color: var(--ink);
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
padding: 64px 32px;
}
.sheet {
display: flex;
flex-direction: column;
align-items: center;
gap: 48px;
}
/* ---------- 5. Poster boards (fixed export sizes) ---------- */
.poster {
position: relative;
background: var(--paper);
color: var(--ink);
overflow: hidden;
isolation: isolate;
}
.poster.xhs { width: 1080px; height: 1440px; }
.poster.square { width: 1080px; height: 1080px; }
.poster.wide { width: 2100px; height: 900px; }
.poster.xhs .content { padding: var(--sp-12) var(--sp-11); }
.poster.square .content { padding: var(--sp-11) var(--sp-11); }
.poster.wide .content { padding: var(--sp-11) var(--sp-13); }
.content { position: relative; width: 100%; height: 100%; z-index: 2; }
/* Optional decorative layers — opt in per poster */
.dot-mat,
.ring-mat,
.cross-mat {
position: absolute;
inset: 0;
pointer-events: none;
z-index: 1;
}
.dot-mat {
opacity: .08;
background-image: radial-gradient(var(--ink) 1.5px, transparent 1.5px);
background-size: 24px 24px;
}
.ring-mat {
opacity: .08;
background-image: radial-gradient(circle at 50% 50%, transparent 4px, var(--ink) 4.5px, var(--ink) 5px, transparent 5.5px);
background-size: 32px 32px;
}
.cross-mat {
opacity: .06;
background-image:
linear-gradient(var(--ink), var(--ink)),
linear-gradient(90deg, var(--ink), var(--ink));
background-size: 1px 14px, 14px 1px;
background-position: center, center;
background-repeat: repeat;
}
/* ---------- 6. Typography roles (Swiss extreme contrast) ----------
Hard rule: "the larger, the lighter"
≥ 200px → weight 200 (ExtraLight)
Smaller text gets heavier weight (≥ 500). */
.h-hero {
font-family: var(--sans);
font-weight: 200;
font-size: 240px;
line-height: 1.02;
letter-spacing: -.02em;
margin: 0;
color: var(--ink);
}
.h-statement {
font-family: var(--sans);
font-weight: 200;
font-size: 180px;
line-height: 1.05;
letter-spacing: -.015em;
margin: 0;
color: var(--ink);
}
.h-xl {
font-family: var(--sans);
font-weight: 300;
font-size: 120px;
line-height: 1.08;
letter-spacing: -.01em;
margin: 0;
color: var(--ink);
}
.h-md {
font-family: var(--sans);
font-weight: 400;
font-size: 56px;
line-height: 1.18;
margin: 0;
color: var(--ink);
}
.lead {
font-family: var(--sans-zh);
font-weight: 400;
font-size: 30px;
line-height: 1.55;
color: var(--ink);
margin: 0;
}
.body {
font-family: var(--sans-zh);
font-weight: 400;
font-size: 26px;
line-height: 1.6;
color: var(--ink);
margin: 0;
}
.t-cat {
font-family: var(--sans);
font-weight: 600;
font-size: 22px;
letter-spacing: .08em;
text-transform: uppercase;
color: var(--accent);
margin: 0;
}
.t-meta {
font-family: var(--mono);
font-weight: 500;
font-size: 20px;
letter-spacing: .14em;
text-transform: uppercase;
color: var(--grey-3);
margin: 0;
}
.num-mega {
font-family: var(--sans);
font-weight: 200;
font-size: 200px;
line-height: .92;
letter-spacing: -.03em;
color: var(--ink);
margin: 0;
}
.num-xl {
font-family: var(--sans);
font-weight: 200;
font-size: 144px;
line-height: .96;
letter-spacing: -.02em;
margin: 0;
}
.mono { font-family: var(--mono); }
/* XHS (3:4) boards: 1080×1440 has the tightest vertical budget,
so display sizes step down vs the .wide default. These caps
were validated by the smoke-ai-tools deck — 2-line h-xl @96px
leaves room for matrix + hero-stat-bottom inside 1440px. */
.poster.xhs .h-hero { font-size: 168px; }
.poster.xhs .h-statement { font-size: 124px; }
.poster.xhs .h-xl { font-size: 96px; }
.poster.xhs .num-mega { font-size: 168px; }
.poster.xhs .num-xl { font-size: 120px; }
/* Square boards tighten scales */
.poster.square .h-hero { font-size: 180px; }
.poster.square .h-statement { font-size: 140px; }
.poster.square .h-xl { font-size: 88px; }
.poster.square .num-mega { font-size: 168px; }
.poster.square .num-xl { font-size: 112px; }
/* Wide boards rely on horizontal real estate */
.poster.wide .h-hero { font-size: 200px; }
.poster.wide .h-statement { font-size: 156px; }
.poster.wide .h-xl { font-size: 104px; }
/* ---------- 7. Layout primitives ---------- */
.stack { display: flex; flex-direction: column; }
.row { display: flex; flex-direction: row; }
.gap-3 { gap: var(--sp-3); }
.gap-4 { gap: var(--sp-4); }
.gap-5 { gap: var(--sp-5); }
.gap-6 { gap: var(--sp-6); }
.gap-7 { gap: var(--sp-7); }
.gap-8 { gap: var(--sp-8); }
.gap-9 { gap: var(--sp-9); }
.gap-10 { gap: var(--sp-10); }
.grow { flex: 1; }
.grid-12 { display: grid; grid-template-columns: repeat(12, 1fr); gap: var(--sp-7); }
.grid-2-9 { display: grid; grid-template-columns: 1fr 1fr; gap: var(--sp-9); }
.grid-3 { display: grid; grid-template-columns: repeat(3, 1fr); gap: var(--sp-7); }
.grid-4 { display: grid; grid-template-columns: repeat(4, 1fr); gap: var(--sp-6); }
.span-2 { grid-column: span 2; }
.span-3 { grid-column: span 3; }
.span-4 { grid-column: span 4; }
.span-6 { grid-column: span 6; }
.span-8 { grid-column: span 8; }
.span-9 { grid-column: span 9; }
.span-12 { grid-column: span 12; }
.center { text-align: center; }
.right { text-align: right; }
.uppercase{ text-transform: uppercase; }
.hr-hairline {
height: 1px;
background: var(--grey-2);
border: 0;
margin: 0;
}
.hr-accent {
height: 3px;
background: var(--accent);
border: 0;
width: 96px;
margin: 0;
}
/* ---------- 8. Card fills (mutually exclusive — pick ONE) ----------
Hard rule: never combine card-ink/card-accent/card-fill/card-outlined
on the same node. Multi-card grids must use the same class. */
.card-ink {
background: var(--ink);
color: var(--paper);
padding: var(--sp-8);
}
.card-ink .t-cat,
.card-ink .t-meta,
.card-ink .lead,
.card-ink .body,
.card-ink .h-md { color: var(--paper); }
.card-accent {
background: var(--accent);
color: var(--accent-on);
padding: var(--sp-8);
}
.card-accent .t-cat,
.card-accent .t-meta,
.card-accent .lead,
.card-accent .body,
.card-accent .h-md { color: var(--accent-on); }
.card-fill {
background: var(--grey-1);
color: var(--ink);
padding: var(--sp-8);
}
.card-outlined {
background: transparent;
color: var(--ink);
border: 1px solid var(--grey-2);
padding: var(--sp-8);
}
/* ---------- 9. Frame containers (image holders) ---------- */
.frame-img {
position: relative;
overflow: hidden;
background: var(--grey-1);
display: block;
margin: 0;
}
.frame-img img {
width: 100%;
height: 100%;
object-fit: cover;
object-position: center 35%;
display: block;
}
.frame-img.fit-contain img { object-fit: contain; background: var(--paper); }
.frame-img.r-3x4 { aspect-ratio: 3 / 4; }
.frame-img.r-1x1 { aspect-ratio: 1 / 1; }
.frame-img.r-4x3 { aspect-ratio: 4 / 3; }
.frame-img.r-16x9 { aspect-ratio: 16 / 9; }
.frame-img.r-16x10 { aspect-ratio: 16 / 10; }
.frame-img.r-21x9 { aspect-ratio: 21 / 9; }
.swiss-img-caption {
font-family: var(--mono);
font-weight: 500;
font-size: 18px;
letter-spacing: .12em;
text-transform: uppercase;
color: var(--grey-3);
margin-top: var(--sp-5);
}
/* ---------- 9b. Screenshot treatment (.frame-shot) ---------- */
/* Swiss is stricter than Editorial: corners default square, shadows default
none. Override only when the shot must literally float off the page. */
.frame-shot {
position: relative;
overflow: hidden;
display: block;
margin: 0;
background: var(--grey-1);
border-radius: 0;
}
.frame-shot.corners-sq { border-radius: 0; }
.frame-shot.corners-sm { border-radius: 4px; }
.frame-shot.corners-md { border-radius: 10px; }
/* Swiss shadows are flat — only the subtle outline scrim is welcome */
.frame-shot.shadow-soft { box-shadow: 0 12px 28px -16px rgba(0,0,0,.18); }
.frame-shot.shadow-ed { box-shadow: 0 20px 40px -20px rgba(0,0,0,.22), 0 0 0 1px var(--grey-2); }
/* background tones — pure / dot / grid only. No accent colour fills. */
.frame-shot.bg-paper { background: var(--paper); }
.frame-shot.bg-grey-1 { background: var(--grey-1); }
.frame-shot.bg-ink { background: var(--ink); }
.frame-shot.bg-grid {
background-color: var(--paper);
background-image:
linear-gradient(to right, var(--grey-2) 1px, transparent 1px),
linear-gradient(to bottom, var(--grey-2) 1px, transparent 1px);
background-size: 48px 48px;
}
.frame-shot.bg-dot {
background-color: var(--paper);
background-image: radial-gradient(var(--grey-2) 1px, transparent 1px);
background-size: 18px 18px;
}
.frame-shot.inset-none > img,
.frame-shot.inset-none > .shot-body { padding: 0; }
.frame-shot.inset-sub { padding: 20px; }
.frame-shot.inset-bal { padding: 48px; }
.frame-shot.r-3x4 { aspect-ratio: 3 / 4; }
.frame-shot.r-1x1 { aspect-ratio: 1 / 1; }
.frame-shot.r-4x3 { aspect-ratio: 4 / 3; }
.frame-shot.r-16x9 { aspect-ratio: 16 / 9; }
.frame-shot.r-16x10 { aspect-ratio: 16 / 10; }
.frame-shot.r-21x9 { aspect-ratio: 21 / 9; }
.frame-shot.r-3x2 { aspect-ratio: 3 / 2; }
.frame-shot > img {
width: 100%;
height: 100%;
object-fit: contain;
object-position: center;
display: block;
border-radius: inherit;
}
.frame-shot.fit-cover > img { object-fit: cover; }
/* device chrome — CSS-only, no SVG */
.device-browser {
position: relative;
background: var(--paper);
border: 1px solid var(--grey-2);
border-radius: 6px;
overflow: hidden;
}
.device-browser::before {
content: '';
display: block;
height: 32px;
background: var(--grey-1);
border-bottom: 1px solid var(--grey-2);
background-image:
radial-gradient(circle at 16px 16px, var(--grey-3) 4px, transparent 4.5px),
radial-gradient(circle at 34px 16px, var(--grey-3) 4px, transparent 4.5px),
radial-gradient(circle at 52px 16px, var(--grey-3) 4px, transparent 4.5px);
}
.device-browser > .frame-shot,
.device-browser > img {
border-radius: 0;
display: block;
width: 100%;
}
.device-phone {
position: relative;
background: var(--ink);
border-radius: 28px;
padding: 12px;
box-shadow: 0 16px 36px -18px rgba(0,0,0,.32);
}
.device-phone > .frame-shot,
.device-phone > img {
border-radius: 18px;
display: block;
width: 100%;
}
/* ---------- 9c. Map block (.map-block) ---------- */
/* Static map for Swiss decks. Pins are pure HTML, no SVG text.
Use accent sparingly: at most one accent pin per board. */
.map-block {
position: relative;
background: var(--grey-1);
overflow: hidden;
aspect-ratio: 4 / 3;
width: 100%;
}
.map-block.r-16x10 { aspect-ratio: 16 / 10; }
.map-block.r-16x9 { aspect-ratio: 16 / 9; }
.map-block.r-1x1 { aspect-ratio: 1 / 1; }
.map-block.r-3x4 { aspect-ratio: 3 / 4; }
.map-block > img,
.map-block > svg {
position: absolute; inset: 0;
width: 100%; height: 100%;
display: block;
}
.map-block > img { object-fit: cover; }
/* desaturated tile filter to keep Swiss strict tone */
.map-block.tone-paper > img { filter: saturate(0) contrast(1.05) brightness(1.02); }
.map-block.tone-ink > img { filter: saturate(0) brightness(.58); }
/* schematic SVG defaults */
.map-block > svg .map-coast { fill: none; stroke: var(--grey-2); stroke-width: .4; }
.map-block > svg .map-road { fill: none; stroke: var(--grey-3); stroke-width: .28; stroke-dasharray: 1.2 .8; opacity: .7; }
.map-block > svg .map-water { fill: var(--grey-1); }
.map-block > svg .map-grid { stroke: var(--grey-2); stroke-width: .15; opacity: .5; }
.map-pin {
position: absolute;
width: 0; height: 0;
transform: translate(-50%, -50%);
}
.map-pin .dot {
position: absolute;
left: -6px; top: -6px;
width: 12px; height: 12px;
border-radius: 50%;
background: var(--ink);
border: 2px solid var(--paper);
box-shadow: 0 1px 3px rgba(0,0,0,.22);
}
.map-pin.accent .dot { background: var(--accent); }
.map-pin .line {
position: absolute;
left: 7px; top: 0;
width: 28px; height: 1px;
background: var(--ink);
opacity: .55;
}
.map-pin.left .line { left: auto; right: 7px; }
.map-pin .card {
position: absolute;
left: 38px; top: -20px;
min-width: 80px;
padding: 8px 10px;
background: rgba(250,250,248,.94);
box-shadow: 0 0 0 1px var(--grey-2);
border-radius: 2px;
white-space: nowrap;
}
.map-pin.left .card { left: auto; right: 38px; }
.map-pin .card .name {
font-family: var(--sans-zh), var(--sans);
font-weight: 500;
font-size: 15px;
line-height: 1.1;
color: var(--ink);
}
.map-pin .card .meta {
display: block;
margin-top: 4px;
font-family: var(--mono);
font-size: 10px;
letter-spacing: .14em;
text-transform: uppercase;
color: var(--grey-3);
}
.map-pin.accent .card .name { color: var(--accent); }
.map-legend {
position: absolute;
left: 14px; bottom: 14px;
z-index: 3;
background: rgba(250,250,248,.94);
padding: 8px 12px;
box-shadow: 0 0 0 1px var(--grey-2);
font-family: var(--mono);
font-size: 11px;
letter-spacing: .14em;
text-transform: uppercase;
color: var(--grey-3);
}
.map-legend.tr { left: auto; right: 14px; top: 14px; bottom: auto; }
/* ---------- 10. Swiss-specific components ---------- */
/* S08 Image Hero (port of S22) */
.image-hero {
display: grid;
grid-template-rows: auto 1fr;
gap: var(--sp-7);
height: 100%;
}
.image-hero .hero-img-wrap {
position: relative;
overflow: hidden;
background: var(--grey-1);
}
.image-hero .hero-img-wrap img {
width: 100%;
height: 100%;
object-fit: cover;
object-position: center 35%;
display: block;
}
.image-hero .hero-overlay-block {
position: absolute;
left: 0;
top: 0;
background: var(--paper);
padding: var(--sp-7) var(--sp-8);
max-width: 60%;
}
.image-hero .hero-stats {
display: grid;
grid-template-columns: repeat(3, 1fr);
gap: var(--sp-8);
align-items: end;
border-top: 1px solid var(--grey-2);
padding-top: var(--sp-7);
}
.image-hero .hero-stats .stat-block .num {
font-family: var(--sans);
font-weight: 200;
font-size: 96px;
line-height: 1;
letter-spacing: -.02em;
color: var(--ink);
margin: 0 0 var(--sp-4);
}
.image-hero .hero-stats .stat-block .lbl {
font-family: var(--mono);
font-size: 18px;
letter-spacing: .12em;
text-transform: uppercase;
color: var(--grey-3);
margin: 0;
}
/* S09 KPI Tower (port of S06) */
.kpi-tower-row {
display: grid;
grid-template-columns: repeat(4, 1fr);
gap: var(--sp-7);
align-items: end;
height: 100%;
}
.kpi-tower-row .tower-col {
display: flex;
flex-direction: column;
gap: var(--sp-5);
align-items: flex-start;
}
.kpi-tower-row .tower-col .num {
font-family: var(--sans);
font-weight: 200;
font-size: 112px;
line-height: 1;
letter-spacing: -.02em;
margin: 0;
}
.kpi-tower-row .tower-col .lbl {
font-family: var(--mono);
font-size: 20px;
letter-spacing: .12em;
text-transform: uppercase;
color: var(--grey-3);
margin: 0;
}
.kpi-tower-row .tower-col .bar-tower {
width: 100%;
height: var(--h, 240px);
background: var(--accent);
transform-origin: bottom;
}
.kpi-tower-row .tower-col.muted .bar-tower {
background: var(--grey-2);
}
.poster.xhs .kpi-tower-row { grid-template-columns: 1fr 1fr; gap: var(--sp-9); }
.poster.square .kpi-tower-row { grid-template-columns: 1fr 1fr; gap: var(--sp-9); }
/* S10 H-Bar Chart (port of S07) */
.h-bar-chart {
display: grid;
gap: var(--sp-6);
}
.h-bar-chart .bar-row {
display: grid;
grid-template-columns: 220px 1fr 120px;
gap: var(--sp-6);
align-items: center;
}
.h-bar-chart .bar-row .row-lbl {
font-family: var(--sans-zh);
font-weight: 500;
font-size: 26px;
color: var(--ink);
}
.h-bar-chart .bar-row .row-track {
position: relative;
height: 28px;
background: var(--grey-1);
}
.h-bar-chart .bar-row .row-fill {
position: absolute;
left: 0;
top: 0;
bottom: 0;
width: var(--w, 50%);
background: var(--accent);
}
.h-bar-chart .bar-row .row-val {
font-family: var(--mono);
font-weight: 500;
font-size: 26px;
letter-spacing: .04em;
text-align: right;
color: var(--ink);
}
/* On portrait the labels move above the track */
.poster.xhs .h-bar-chart .bar-row {
grid-template-columns: 1fr;
gap: var(--sp-4);
}
.poster.xhs .h-bar-chart .bar-row .row-val {
text-align: left;
}
/* S11 Stacked Ledger (port of S20) */
.stacked-ledger {
display: flex;
flex-direction: column;
}
.stacked-ledger .ledger-row {
display: grid;
grid-template-columns: 280px 1fr auto;
gap: var(--sp-7);
align-items: baseline;
padding: var(--sp-7) 0;
border-bottom: 1px solid var(--grey-2);
}
.stacked-ledger .ledger-row:first-child { padding-top: 0; }
.stacked-ledger .ledger-row:last-child { border-bottom: 0; }
.stacked-ledger .ledger-num {
font-family: var(--sans);
font-weight: 200;
font-size: 112px;
line-height: 1;
letter-spacing: -.02em;
margin: 0;
}
.stacked-ledger .ledger-lbl {
font-family: var(--sans-zh);
font-weight: 500;
font-size: 30px;
line-height: 1.3;
color: var(--ink);
}
.stacked-ledger .ledger-lbl .sub {
display: block;
font-weight: 400;
font-size: 22px;
color: var(--grey-3);
margin-top: var(--sp-3);
}
.stacked-ledger .ledger-icn {
width: 56px;
height: 56px;
color: var(--accent);
}
.poster.square .stacked-ledger .ledger-num { font-size: 88px; }
.poster.square .stacked-ledger .ledger-row { grid-template-columns: 220px 1fr auto; }
/* S12 Matrix Fill (port of S15) */
.matrix-fill {
display: grid;
grid-template-columns: repeat(4, 1fr);
grid-auto-rows: 1fr;
gap: var(--sp-5);
}
.matrix-fill .matrix-cell {
background: var(--grey-1);
padding: var(--sp-6);
display: flex;
flex-direction: column;
gap: var(--sp-4);
min-height: 160px;
}
.matrix-fill .matrix-cell .cell-nb {
font-family: var(--mono);
font-size: 18px;
letter-spacing: .12em;
color: var(--grey-3);
}
.matrix-fill .matrix-cell .cell-title {
font-family: var(--sans-zh);
font-weight: 500;
font-size: 26px;
line-height: 1.3;
color: var(--ink);
}
.matrix-fill .matrix-cell.is-accent {
background: var(--accent);
color: var(--accent-on);
}
.matrix-fill .matrix-cell.is-accent .cell-nb,
.matrix-fill .matrix-cell.is-accent .cell-title { color: var(--accent-on); }
.hero-stat-bottom {
margin-top: var(--sp-9);
padding-top: var(--sp-7);
border-top: 1px solid var(--grey-2);
display: grid;
grid-template-columns: 1fr auto;
align-items: end;
gap: var(--sp-7);
}
/* xhs uses 2 columns; up to 8 cells = 4 rows. Switch to min-content
so rows don't stretch and crowd out hero-stat-bottom (validated
in smoke-ai-tools/xhs-04). When pairing matrix-fill with
hero-stat-bottom inside .poster.xhs, prefer 6 cells (3 rows). */
.poster.xhs .matrix-fill {
grid-template-columns: repeat(2, 1fr);
grid-auto-rows: min-content;
gap: var(--sp-4);
}
.poster.xhs .matrix-fill .matrix-cell { min-height: 0; padding: var(--sp-5); }
.poster.xhs .matrix-fill .matrix-cell .cell-title { font-size: 24px; }
.poster.xhs .hero-stat-bottom {
margin-top: var(--sp-6);
padding-top: var(--sp-5);
align-items: center;
}
.poster.xhs .hero-stat-bottom .num-mega { font-size: 128px; line-height: 1; }
.poster.square .matrix-fill{ grid-template-columns: repeat(3, 1fr); grid-auto-rows: auto; }
.poster.square .matrix-fill .matrix-cell { min-height: 0; }
/* ---------- 11. Issue label + corner metadata ---------- */
.chrome-min {
display: flex;
justify-content: space-between;
align-items: center;
font-family: var(--mono);
font-size: 18px;
letter-spacing: .14em;
text-transform: uppercase;
color: var(--grey-3);
padding-bottom: var(--sp-6);
border-bottom: 1px solid var(--grey-2);
margin-bottom: var(--sp-9);
}
/* ---------- 12. Pair preview frame ---------- */
.pair-preview {
width: 2400px;
min-height: 1180px;
background: var(--grey-1);
padding: var(--sp-9);
display: grid;
grid-template-columns: 2100px 1080px;
grid-template-rows: 900px 1080px;
gap: var(--sp-9);
align-content: center;
justify-content: center;
}
.pair-preview .preview-wide { grid-column: 1; grid-row: 1 / span 2; align-self: center; }
.pair-preview .preview-square { grid-column: 2; grid-row: 1 / span 2; align-self: center; }
.pair-preview .preview-label {
font-family: var(--mono);
font-weight: 500;
font-size: 18px;
letter-spacing: .14em;
text-transform: uppercase;
color: var(--grey-3);
margin-bottom: var(--sp-5);
}
/* ---------- 13. Hard rules (visual guard rails) ----------
Swiss style refuses these — uncomment to find offenders.
.poster *[style*="border-radius"],
.poster *[style*="box-shadow"],
.poster *[style*="linear-gradient"] { outline: 4px solid red !important; }
*/
</style>
</head>
<body>
<!-- ACCENT_OVERRIDE
Change <html data-accent="…"> above to:
ikb | lemon-yellow | lemon-green | safety-orange
One deck — one accent. Never combine.
-->
<main class="sheet">
<!-- POSTERS_HERE -->
<!--
Replace the placeholder poster below with real content.
Each poster carries one Layout Recipe (S01-S12) from
references/layout-recipes.md.
Available board sizes:
<section class="poster xhs" id="xhs-01">…</section> 3:4
<section class="poster square" id="wechat-1x1">…</section> 1:1
<section class="poster wide" id="wechat-21x9">…</section> 21:9
For WeChat covers, also include a <section class="pair-preview">
that places the 21:9 + 1:1 side-by-side for review.
Hard rules every poster must honor:
- exactly one --accent in the entire deck
- no border-radius, no box-shadow, no gradients
- card-ink / card-accent / card-fill / card-outlined are
mutually exclusive — never combine on one node
- "the larger, the lighter": ≥ 200px must use weight 200
-->
<!-- ============================================================
Placeholder: S01 Accent Cover (3:4)
Replace with real cover content from layout-recipes.md.
============================================================ -->
<section class="poster xhs" id="xhs-01">
<div class="content stack gap-9">
<div class="chrome-min">
<span>Vol. 01 · Swiss</span>
<span>2026.05</span>
</div>
<div class="stack gap-7">
<p class="t-cat">封面 · Cover</p>
<h1 class="h-statement">替换<br>标题</h1>
</div>
<div class="grow"></div>
<hr class="hr-accent">
<p class="lead">用一句克制的解释,告诉读者这一组卡片在讲什么。</p>
<div class="row gap-6">
<p class="t-meta">Issue 01</p>
<p class="t-meta">/ [作者]</p>
</div>
</div>
</section>
</main>
<!-- Lucide icon initialization (used by stacked ledger / matrix accents) -->
<script>
if (window.lucide && typeof window.lucide.createIcons === 'function') {
window.lucide.createIcons();
}
</script>
</body>
</html>