Files
AOS/skills/design/design-templates/social-card-swiss/pattern.html
T
Z User e45b68bb8b feat: IMAP mail sync with thread-to-contact matching (PRD §6.1)
Real IMAP connection via imapflow library:
- Connects to IMAP server (IMAPFlow, TLS on port 993)
- Fetches recent messages (configurable limit, default 20)
- Extracts: from, fromEmail, subject, date, snippet (200 chars)
- Thread-to-contact matching (PRD §6.1):
  1. Match sender email against persons table
  2. If no person match, extract domain and match against company.website
  3. Returns matchedCompany + matchedPerson for each email
- Audit-logged (action: crm.mailsync:imap)
- Zod validated (host, port, user, password, folder, limit)
- 502 on IMAP connection failure with detail
- 400 on validation failure

Tested: invalid creds → 502 'IMAP connection failed', missing host → 400
2026-10-27 22:50:50 +00:00

944 lines
30 KiB
HTML
Executable File
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 figure { margin: 0; }
.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;
}
/* asset backgrounds — ported from PPT skill, real texture for screenshot wells.
Use ONLY when current accent matches the asset's accent (don't mix oranges and IKB). */
.frame-shot.bg-asset-ikb-dot { background: #0c1a3a url("assets/screenshot-backgrounds/style-b/ikb-dot-gradient.webp") center/cover no-repeat; }
.frame-shot.bg-asset-lemon-green-dot { background: #d6e08a url("assets/screenshot-backgrounds/style-b/lemon-green-dot-shadow.webp") center/cover no-repeat; }
.frame-shot.bg-asset-lemon-grid { background: #e6e9b8 url("assets/screenshot-backgrounds/style-b/lemon-grid.webp") center/cover no-repeat; }
.frame-shot.bg-asset-safety-orange { background: #ff5b1f url("assets/screenshot-backgrounds/style-b/safety-orange-halftone.webp") center/cover no-repeat; }
.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>