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>
This commit is contained in:
commit
82d221bb90
26 files changed
+6329
No files matched your search
@@ -0,0 +1,160 @@
|
||||
/* Reusable Guizang-style electronic magazine background.
|
||||
Copy or inline into generated HTML. It renders a deterministic WebGL ink-flow
|
||||
canvas and falls back to 2D if WebGL is unavailable. */
|
||||
(function () {
|
||||
function rgb(input, fallback) {
|
||||
return Array.isArray(input) && input.length === 3 ? input.map((v) => v / 255) : fallback;
|
||||
}
|
||||
|
||||
function fallback2d(canvas, opts) {
|
||||
const ctx = canvas.getContext("2d");
|
||||
if (!ctx) return;
|
||||
const dpr = Math.min(2, window.devicePixelRatio || 1);
|
||||
const rect = canvas.getBoundingClientRect();
|
||||
canvas.width = Math.max(1, Math.floor(rect.width * dpr));
|
||||
canvas.height = Math.max(1, Math.floor(rect.height * dpr));
|
||||
ctx.setTransform(dpr, 0, 0, dpr, 0, 0);
|
||||
const w = rect.width;
|
||||
const h = rect.height;
|
||||
const ink = opts.inkCss || "rgba(26,46,31,.22)";
|
||||
const accent = opts.accentCss || "rgba(46,107,79,.18)";
|
||||
ctx.clearRect(0, 0, w, h);
|
||||
for (let i = 0; i < 5; i++) {
|
||||
const x = w * (0.14 + ((i * 0.19 + 0.17) % 0.72));
|
||||
const y = h * (0.10 + ((i * 0.29 + 0.23) % 0.78));
|
||||
const r = Math.max(w, h) * (0.22 + i * 0.045);
|
||||
const g = ctx.createRadialGradient(x, y, 0, x, y, r);
|
||||
g.addColorStop(0, i % 2 ? accent : ink);
|
||||
g.addColorStop(1, "rgba(0,0,0,0)");
|
||||
ctx.fillStyle = g;
|
||||
ctx.fillRect(0, 0, w, h);
|
||||
}
|
||||
ctx.globalAlpha = 0.09;
|
||||
ctx.strokeStyle = opts.inkCss || "rgba(26,46,31,.22)";
|
||||
for (let i = 0; i < 18; i++) {
|
||||
ctx.beginPath();
|
||||
ctx.ellipse(w * (0.5 + Math.sin(i) * 0.34), h * (0.5 + Math.cos(i * 1.7) * 0.28), 120 + i * 18, 42 + i * 9, i * 0.37, 0, Math.PI * 2);
|
||||
ctx.stroke();
|
||||
}
|
||||
ctx.globalAlpha = 1;
|
||||
}
|
||||
|
||||
function mount(canvas, options) {
|
||||
const opts = options || {};
|
||||
const gl = canvas.getContext("webgl", { alpha: true, antialias: true, preserveDrawingBuffer: true });
|
||||
if (!gl) {
|
||||
fallback2d(canvas, opts);
|
||||
return { mode: "2d" };
|
||||
}
|
||||
|
||||
const vert = `
|
||||
attribute vec2 aPos;
|
||||
void main(){ gl_Position = vec4(aPos, 0.0, 1.0); }
|
||||
`;
|
||||
const frag = `
|
||||
precision mediump float;
|
||||
uniform vec2 uRes;
|
||||
uniform float uTime;
|
||||
uniform vec3 uInk;
|
||||
uniform vec3 uPaper;
|
||||
uniform vec3 uAccent;
|
||||
uniform float uStrength;
|
||||
|
||||
float hash(vec2 p){ return fract(sin(dot(p, vec2(127.1, 311.7))) * 43758.5453123); }
|
||||
float noise(vec2 p){
|
||||
vec2 i = floor(p);
|
||||
vec2 f = fract(p);
|
||||
vec2 u = f * f * (3.0 - 2.0 * f);
|
||||
return mix(
|
||||
mix(hash(i + vec2(0.0,0.0)), hash(i + vec2(1.0,0.0)), u.x),
|
||||
mix(hash(i + vec2(0.0,1.0)), hash(i + vec2(1.0,1.0)), u.x),
|
||||
u.y
|
||||
);
|
||||
}
|
||||
float fbm(vec2 p){
|
||||
float v = 0.0;
|
||||
float a = 0.5;
|
||||
mat2 m = mat2(1.6, 1.2, -1.2, 1.6);
|
||||
for(int i=0; i<5; i++){
|
||||
v += a * noise(p);
|
||||
p = m * p * 1.18;
|
||||
a *= 0.52;
|
||||
}
|
||||
return v;
|
||||
}
|
||||
void main(){
|
||||
vec2 uv = gl_FragCoord.xy / uRes.xy;
|
||||
vec2 p = (gl_FragCoord.xy - 0.5 * uRes.xy) / min(uRes.x, uRes.y);
|
||||
float t = uTime * 0.045;
|
||||
float n = fbm(p * 2.2 + vec2(t, -t * 0.7));
|
||||
float n2 = fbm(p * 5.4 - vec2(t * 1.7, t));
|
||||
float contour = smoothstep(0.015, 0.0, abs(fract((n + n2 * 0.35) * 7.0) - 0.5));
|
||||
float wash = smoothstep(0.22, 0.88, n);
|
||||
float vignette = 1.0 - smoothstep(0.28, 0.92, length(p));
|
||||
vec3 col = mix(uPaper, uInk, wash * 0.42 + contour * 0.18);
|
||||
col = mix(col, uAccent, (0.18 + 0.25 * n2) * contour);
|
||||
float alpha = uStrength * (0.18 + wash * 0.42 + contour * 0.30) * (0.55 + 0.45 * vignette);
|
||||
gl_FragColor = vec4(col, alpha);
|
||||
}
|
||||
`;
|
||||
|
||||
function shader(type, source) {
|
||||
const sh = gl.createShader(type);
|
||||
gl.shaderSource(sh, source);
|
||||
gl.compileShader(sh);
|
||||
if (!gl.getShaderParameter(sh, gl.COMPILE_STATUS)) throw new Error(gl.getShaderInfoLog(sh));
|
||||
return sh;
|
||||
}
|
||||
|
||||
const program = gl.createProgram();
|
||||
gl.attachShader(program, shader(gl.VERTEX_SHADER, vert));
|
||||
gl.attachShader(program, shader(gl.FRAGMENT_SHADER, frag));
|
||||
gl.linkProgram(program);
|
||||
if (!gl.getProgramParameter(program, gl.LINK_STATUS)) throw new Error(gl.getProgramInfoLog(program));
|
||||
gl.useProgram(program);
|
||||
|
||||
const buffer = gl.createBuffer();
|
||||
gl.bindBuffer(gl.ARRAY_BUFFER, buffer);
|
||||
gl.bufferData(gl.ARRAY_BUFFER, new Float32Array([-1,-1, 1,-1, -1,1, -1,1, 1,-1, 1,1]), gl.STATIC_DRAW);
|
||||
const loc = gl.getAttribLocation(program, "aPos");
|
||||
gl.enableVertexAttribArray(loc);
|
||||
gl.vertexAttribPointer(loc, 2, gl.FLOAT, false, 0, 0);
|
||||
|
||||
const uRes = gl.getUniformLocation(program, "uRes");
|
||||
const uTime = gl.getUniformLocation(program, "uTime");
|
||||
const uInk = gl.getUniformLocation(program, "uInk");
|
||||
const uPaper = gl.getUniformLocation(program, "uPaper");
|
||||
const uAccent = gl.getUniformLocation(program, "uAccent");
|
||||
const uStrength = gl.getUniformLocation(program, "uStrength");
|
||||
const ink = rgb(opts.ink, [0.10, 0.18, 0.12]);
|
||||
const paper = rgb(opts.paper, [0.96, 0.94, 0.90]);
|
||||
const accent = rgb(opts.accent, [0.18, 0.42, 0.31]);
|
||||
|
||||
function resize() {
|
||||
const dpr = Math.min(2, window.devicePixelRatio || 1);
|
||||
const rect = canvas.getBoundingClientRect();
|
||||
canvas.width = Math.max(1, Math.floor(rect.width * dpr));
|
||||
canvas.height = Math.max(1, Math.floor(rect.height * dpr));
|
||||
gl.viewport(0, 0, canvas.width, canvas.height);
|
||||
}
|
||||
|
||||
function draw(time) {
|
||||
resize();
|
||||
gl.clearColor(0, 0, 0, 0);
|
||||
gl.clear(gl.COLOR_BUFFER_BIT);
|
||||
gl.uniform2f(uRes, canvas.width, canvas.height);
|
||||
gl.uniform1f(uTime, opts.frozenTime == null ? time * 0.001 : opts.frozenTime);
|
||||
gl.uniform3f(uInk, ink[0], ink[1], ink[2]);
|
||||
gl.uniform3f(uPaper, paper[0], paper[1], paper[2]);
|
||||
gl.uniform3f(uAccent, accent[0], accent[1], accent[2]);
|
||||
gl.uniform1f(uStrength, opts.strength == null ? 0.34 : opts.strength);
|
||||
gl.drawArrays(gl.TRIANGLES, 0, 6);
|
||||
}
|
||||
|
||||
draw(0);
|
||||
if (opts.frozenTime == null) requestAnimationFrame(draw);
|
||||
return { mode: "webgl", redraw: () => draw(0) };
|
||||
}
|
||||
|
||||
window.MagazineBg = { mount };
|
||||
})();
|
||||
@@ -0,0 +1,829 @@
|
||||
<!doctype html>
|
||||
<html lang="zh-CN" data-theme="ink-classic">
|
||||
<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=Noto+Serif+SC:wght@400;500;700;900&family=Noto+Sans+SC:wght@300;400;500;700&family=Playfair+Display:ital,wght@0,400;0,700;1,400&family=Inter:wght@300;400;500;700&family=IBM+Plex+Mono:wght@400;500&display=swap">
|
||||
<style>
|
||||
/* =========================================================
|
||||
Editorial Magazine × E-ink — 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 配对预览
|
||||
|
||||
Theme switching via <html data-theme="…">:
|
||||
ink-classic | indigo-porcelain | forest-ink | kraft-paper | dune | midnight-ink
|
||||
|
||||
Fill posters where marked <!-- POSTERS_HERE -->.
|
||||
Each poster carries one Layout Recipe (M01-M15) from
|
||||
references/layout-recipes.md.
|
||||
========================================================= */
|
||||
|
||||
/* ---------- 1. Theme tokens ---------- */
|
||||
:root,
|
||||
[data-theme="ink-classic"] {
|
||||
--paper: #f3f0e8;
|
||||
--paper-2: #ebe6da;
|
||||
--ink: #0a0a0b;
|
||||
--muted: #68625a;
|
||||
--line: rgba(10,10,11,.22);
|
||||
--accent: #111111;
|
||||
--accent-soft: #d8d2c6;
|
||||
--ink-rgb: 10,10,11;
|
||||
--paper-rgb: 243,240,232;
|
||||
--accent-rgb: 17,17,17;
|
||||
}
|
||||
[data-theme="indigo-porcelain"] {
|
||||
--paper: #f2f4f5;
|
||||
--paper-2: #e5ebef;
|
||||
--ink: #0a1f3d;
|
||||
--muted: #5f6d78;
|
||||
--line: rgba(10,31,61,.20);
|
||||
--accent: #315d93;
|
||||
--accent-soft: #d7e1ec;
|
||||
--ink-rgb: 10,31,61;
|
||||
--paper-rgb: 242,244,245;
|
||||
--accent-rgb: 49,93,147;
|
||||
}
|
||||
[data-theme="forest-ink"] {
|
||||
--paper: #f5f1e8;
|
||||
--paper-2: #e8dfcf;
|
||||
--ink: #16251b;
|
||||
--muted: #5d665d;
|
||||
--line: rgba(22,37,27,.22);
|
||||
--accent: #2e6b4f;
|
||||
--accent-soft: #d4dfd2;
|
||||
--ink-rgb: 22,37,27;
|
||||
--paper-rgb: 245,241,232;
|
||||
--accent-rgb: 46,107,79;
|
||||
}
|
||||
[data-theme="kraft-paper"] {
|
||||
--paper: #eedfc7;
|
||||
--paper-2: #dfc9a8;
|
||||
--ink: #2a1e13;
|
||||
--muted: #755f49;
|
||||
--line: rgba(42,30,19,.24);
|
||||
--accent: #9b5a2e;
|
||||
--accent-soft: #d5b58f;
|
||||
--ink-rgb: 42,30,19;
|
||||
--paper-rgb: 238,223,199;
|
||||
--accent-rgb: 155,90,46;
|
||||
}
|
||||
[data-theme="dune"] {
|
||||
--paper: #f0e6d2;
|
||||
--paper-2: #ded0b7;
|
||||
--ink: #1f1a14;
|
||||
--muted: #6f6557;
|
||||
--line: rgba(31,26,20,.22);
|
||||
--accent: #8f7650;
|
||||
--accent-soft: #d4c2a4;
|
||||
--ink-rgb: 31,26,20;
|
||||
--paper-rgb: 240,230,210;
|
||||
--accent-rgb: 143,118,80;
|
||||
}
|
||||
/* Midnight Ink — the ONLY official dark Editorial palette.
|
||||
Use for game key art, night scenes, dark photography, cinematic covers.
|
||||
Do not improvise other dark backgrounds. */
|
||||
[data-theme="midnight-ink"] {
|
||||
--paper: #0e0d0c;
|
||||
--paper-2: #1a1714;
|
||||
--ink: #ece2cf;
|
||||
--muted: #9a8c75;
|
||||
--line: rgba(236,226,207,.22);
|
||||
--accent: #d4a04a;
|
||||
--accent-soft: #3a2a14;
|
||||
--ink-rgb: 236,226,207;
|
||||
--paper-rgb: 14,13,12;
|
||||
--accent-rgb: 212,160,74;
|
||||
}
|
||||
|
||||
/* ---------- 2. Typography stack ---------- */
|
||||
:root {
|
||||
--serif-zh: "Noto Serif SC", "Songti SC", "STSong", serif;
|
||||
--serif-en: "Playfair Display", "Noto Serif SC", serif;
|
||||
--sans-zh: "Noto Sans SC", -apple-system, "PingFang SC", "Microsoft YaHei UI", sans-serif;
|
||||
--sans-en: "Inter", -apple-system, BlinkMacSystemFont, sans-serif;
|
||||
--mono: "IBM Plex Mono", ui-monospace, "SF Mono", Consolas, monospace;
|
||||
}
|
||||
|
||||
/* ---------- 3. Reset & sheet ---------- */
|
||||
*,*::before,*::after { box-sizing: border-box; }
|
||||
html, body { margin: 0; padding: 0; }
|
||||
body {
|
||||
background: #1a1a1a;
|
||||
font-family: var(--sans-zh);
|
||||
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;
|
||||
}
|
||||
|
||||
/* ---------- 4. 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; }
|
||||
|
||||
/* Default content padding by board */
|
||||
.poster.xhs .content { padding: 96px 88px; }
|
||||
.poster.square .content { padding: 88px 88px; }
|
||||
.poster.wide .content { padding: 88px 120px; }
|
||||
.content { position: relative; width: 100%; height: 100%; z-index: 2; }
|
||||
|
||||
/* ---------- 5. Background layers (paper grain + WebGL) ---------- */
|
||||
.mag-bg {
|
||||
position: absolute;
|
||||
inset: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
z-index: 0;
|
||||
pointer-events: none;
|
||||
}
|
||||
.grain {
|
||||
position: absolute;
|
||||
inset: 0;
|
||||
z-index: 1;
|
||||
pointer-events: none;
|
||||
opacity: .35;
|
||||
mix-blend-mode: multiply;
|
||||
background-image: radial-gradient(rgba(0,0,0,.045) 1px, transparent 1px);
|
||||
background-size: 3px 3px;
|
||||
}
|
||||
.paper-wash {
|
||||
position: absolute;
|
||||
inset: 0;
|
||||
z-index: 1;
|
||||
pointer-events: none;
|
||||
background:
|
||||
linear-gradient(180deg, rgba(var(--ink-rgb),.02), rgba(var(--ink-rgb),.05) 60%, rgba(var(--ink-rgb),.08));
|
||||
}
|
||||
/* Midnight Ink — flip grain to screen-mode warm specks, paper-wash to
|
||||
gilt + shadow vignette. Light-paper math doesn't carry over. */
|
||||
[data-theme="midnight-ink"] .grain {
|
||||
opacity: .26;
|
||||
mix-blend-mode: screen;
|
||||
background-image: radial-gradient(rgba(255,244,214,.10) 1px, transparent 1px);
|
||||
}
|
||||
[data-theme="midnight-ink"] .paper-wash {
|
||||
background:
|
||||
radial-gradient(80% 50% at 28% 16%, rgba(212,160,74,.12), transparent 64%),
|
||||
radial-gradient(70% 60% at 80% 86%, rgba(60,40,20,.20), transparent 72%),
|
||||
linear-gradient(180deg, rgba(236,226,207,.02), rgba(0,0,0,.32));
|
||||
}
|
||||
|
||||
/* ---------- 6. Typography roles ----------
|
||||
Editorial Magazine values restored from demo-showcase/editorial.html:
|
||||
light weights (500 not 900), serif body italic-tolerant, wide tracking
|
||||
on kicker/meta (.20-.22em), positive tracking on display (+.04em).
|
||||
Each card should still INLINE-tune font-size for the specific recipe;
|
||||
these defaults are intentionally restrained, not maximal. */
|
||||
.kicker {
|
||||
font-family: var(--mono);
|
||||
font-size: 21px;
|
||||
letter-spacing: .22em;
|
||||
text-transform: uppercase;
|
||||
color: rgba(var(--ink-rgb), .55);
|
||||
margin: 0 0 18px;
|
||||
}
|
||||
.h-display {
|
||||
font-family: var(--serif-zh);
|
||||
font-weight: 500;
|
||||
font-size: 124px;
|
||||
line-height: 1.06;
|
||||
letter-spacing: .04em;
|
||||
margin: 0 0 24px;
|
||||
color: var(--ink);
|
||||
}
|
||||
.h-xl {
|
||||
font-family: var(--serif-zh);
|
||||
font-weight: 500;
|
||||
font-size: 88px;
|
||||
line-height: 1.10;
|
||||
letter-spacing: .03em;
|
||||
margin: 0 0 24px;
|
||||
color: var(--ink);
|
||||
}
|
||||
.h-md {
|
||||
font-family: var(--serif-zh);
|
||||
font-weight: 500;
|
||||
font-size: 56px;
|
||||
line-height: 1.18;
|
||||
letter-spacing: .02em;
|
||||
margin: 0 0 18px;
|
||||
color: var(--ink);
|
||||
}
|
||||
.h-sub {
|
||||
font-family: var(--serif-en);
|
||||
font-style: italic;
|
||||
font-weight: 400;
|
||||
font-size: 36px;
|
||||
color: var(--muted);
|
||||
margin: 0 0 24px;
|
||||
}
|
||||
.lead {
|
||||
font-family: var(--serif-zh);
|
||||
font-weight: 400;
|
||||
font-size: 28px;
|
||||
line-height: 1.55;
|
||||
color: rgba(var(--ink-rgb), .82);
|
||||
margin: 0 0 24px;
|
||||
}
|
||||
.body {
|
||||
font-family: var(--serif-zh);
|
||||
font-weight: 400;
|
||||
font-size: 24px;
|
||||
line-height: 1.65;
|
||||
color: rgba(var(--ink-rgb), .80);
|
||||
margin: 0 0 18px;
|
||||
}
|
||||
.meta {
|
||||
font-family: var(--mono);
|
||||
font-size: 18px;
|
||||
letter-spacing: .20em;
|
||||
text-transform: uppercase;
|
||||
color: rgba(var(--ink-rgb), .55);
|
||||
}
|
||||
.label {
|
||||
font-family: var(--mono);
|
||||
font-size: 18px;
|
||||
letter-spacing: .20em;
|
||||
text-transform: uppercase;
|
||||
color: rgba(var(--ink-rgb), .55);
|
||||
}
|
||||
.pullquote {
|
||||
font-family: var(--serif-zh);
|
||||
font-style: italic;
|
||||
font-weight: 500;
|
||||
font-size: 64px;
|
||||
line-height: 1.28;
|
||||
color: var(--ink);
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
/* Square boards usually need tighter scales */
|
||||
.poster.square .h-display { font-size: 116px; }
|
||||
.poster.square .h-xl { font-size: 78px; }
|
||||
.poster.square .h-md { font-size: 48px; }
|
||||
.poster.square .lead { font-size: 28px; }
|
||||
.poster.square .body { font-size: 26px; }
|
||||
|
||||
/* Wide boards have horizontal headlines */
|
||||
.poster.wide .h-display { font-size: 128px; }
|
||||
.poster.wide .h-xl { font-size: 96px; }
|
||||
.poster.wide .lead { font-size: 34px; }
|
||||
|
||||
/* ---------- 7. Issue label + page metadata ---------- */
|
||||
.issue-row {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 16px;
|
||||
font-family: var(--mono);
|
||||
font-size: 20px;
|
||||
letter-spacing: .12em;
|
||||
text-transform: uppercase;
|
||||
color: var(--muted);
|
||||
}
|
||||
.issue-row .dot {
|
||||
width: 6px;
|
||||
height: 6px;
|
||||
border-radius: 50%;
|
||||
background: var(--accent);
|
||||
}
|
||||
.issue-strip {
|
||||
position: absolute;
|
||||
left: 88px;
|
||||
right: 88px;
|
||||
bottom: 56px;
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
gap: 32px;
|
||||
font-family: var(--mono);
|
||||
font-size: 18px;
|
||||
letter-spacing: .12em;
|
||||
text-transform: uppercase;
|
||||
color: var(--muted);
|
||||
border-top: 1px solid var(--line);
|
||||
padding-top: 18px;
|
||||
}
|
||||
.poster.wide .issue-strip { left: 120px; right: 120px; }
|
||||
.issue-strip span { white-space: nowrap; }
|
||||
|
||||
/* ---------- 8. Layout primitives ---------- */
|
||||
.stack { display: flex; flex-direction: column; }
|
||||
.gap-1 { gap: 12px; }
|
||||
.gap-2 { gap: 24px; }
|
||||
.gap-3 { gap: 36px; }
|
||||
.gap-4 { gap: 48px; }
|
||||
.gap-5 { gap: 64px; }
|
||||
.row { display: flex; flex-direction: row; }
|
||||
.col-2 { display: grid; grid-template-columns: 1fr 1fr; gap: 48px; }
|
||||
.col-3 { display: grid; grid-template-columns: repeat(3, 1fr); gap: 36px; }
|
||||
.col-2-7-5 { display: grid; grid-template-columns: 7fr 5fr; gap: 48px; }
|
||||
.col-2-8-4 { display: grid; grid-template-columns: 8fr 4fr; gap: 48px; }
|
||||
.center { text-align: center; }
|
||||
.grow { flex: 1; }
|
||||
.pad-y-md { padding-top: 36px; padding-bottom: 36px; }
|
||||
.rule { height: 1px; background: var(--line); border: 0; margin: 24px 0; }
|
||||
.rule-accent { height: 2px; background: var(--accent); border: 0; margin: 24px 0; width: 96px; }
|
||||
|
||||
/* ---------- 9. Image containers (standard ratios) ---------- */
|
||||
.frame-img {
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
background: var(--paper-2);
|
||||
display: block;
|
||||
margin: 0;
|
||||
}
|
||||
.frame-img img {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
object-fit: cover;
|
||||
object-position: center 50%;
|
||||
display: block;
|
||||
}
|
||||
.frame-img.fit-contain img { object-fit: contain; }
|
||||
[data-theme="midnight-ink"] .frame-img {
|
||||
background: #18120f;
|
||||
box-shadow: 0 0 0 1px rgba(236,226,207,.10);
|
||||
}
|
||||
.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; }
|
||||
.frame-img.r-3x2 { aspect-ratio: 3 / 2; }
|
||||
.img-cap {
|
||||
font-family: var(--mono);
|
||||
font-size: 20px;
|
||||
letter-spacing: .08em;
|
||||
text-transform: uppercase;
|
||||
color: var(--muted);
|
||||
margin-top: 14px;
|
||||
}
|
||||
|
||||
/* ---------- 9b. Screenshot treatment (.frame-shot) ---------- */
|
||||
/* Use for UI screenshots / web captures / code shots. Replaces .frame-img
|
||||
when you need rounded corners, soft shadow, padded inset, or device chrome.
|
||||
Stack: .frame-shot.r-{ratio}.bg-{tone}.inset-{none|sub|bal}.shadow-{none|soft|ed} */
|
||||
.frame-shot {
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
display: block;
|
||||
margin: 0;
|
||||
background: var(--paper-2);
|
||||
/* default corners — Editorial leans slight rounding to feel like a magazine cutout */
|
||||
border-radius: 6px;
|
||||
}
|
||||
.frame-shot.corners-sq { border-radius: 0; }
|
||||
.frame-shot.corners-sm { border-radius: 6px; }
|
||||
.frame-shot.corners-md { border-radius: 14px; }
|
||||
|
||||
/* shadows — Editorial uses warm low-contrast drop */
|
||||
.frame-shot.shadow-soft { box-shadow: 0 18px 36px -18px rgba(20,18,14,.22), 0 2px 6px rgba(20,18,14,.06); }
|
||||
.frame-shot.shadow-ed { box-shadow: 0 28px 60px -24px rgba(20,18,14,.32), 0 6px 14px rgba(20,18,14,.08); }
|
||||
|
||||
/* background tones — programmatic, no asset deps */
|
||||
.frame-shot.bg-paper { background: var(--paper); }
|
||||
.frame-shot.bg-paper-2 { background: var(--paper-2); }
|
||||
.frame-shot.bg-ink { background: var(--ink); }
|
||||
.frame-shot.bg-grid {
|
||||
background:
|
||||
linear-gradient(var(--paper-2), var(--paper-2)),
|
||||
repeating-linear-gradient(0deg, var(--line) 0 1px, transparent 1px 48px),
|
||||
repeating-linear-gradient(90deg, var(--line) 0 1px, transparent 1px 48px);
|
||||
background-blend-mode: multiply;
|
||||
}
|
||||
.frame-shot.bg-dot {
|
||||
background-color: var(--paper-2);
|
||||
background-image: radial-gradient(var(--line) 1px, transparent 1px);
|
||||
background-size: 18px 18px;
|
||||
}
|
||||
|
||||
/* padding wraps the inner shot — content gets to "float" on the bg tone */
|
||||
.frame-shot.inset-none > img,
|
||||
.frame-shot.inset-none > .shot-body { padding: 0; }
|
||||
.frame-shot.inset-sub { padding: 24px; }
|
||||
.frame-shot.inset-bal { padding: 56px; }
|
||||
|
||||
/* aspect-ratio reused from .frame-img */
|
||||
.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; }
|
||||
|
||||
/* inner img defaults to contain (screenshots must not crop UI) */
|
||||
.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-frame variants (CSS-only chrome, no SVG / images) */
|
||||
.device-browser {
|
||||
position: relative;
|
||||
background: var(--paper);
|
||||
border: 1px solid var(--line);
|
||||
border-radius: 10px;
|
||||
overflow: hidden;
|
||||
}
|
||||
.device-browser::before {
|
||||
content: '';
|
||||
display: block;
|
||||
height: 36px;
|
||||
background: var(--paper-2);
|
||||
border-bottom: 1px solid var(--line);
|
||||
background-image:
|
||||
radial-gradient(circle at 18px 18px, #f08080 5px, transparent 5.5px),
|
||||
radial-gradient(circle at 38px 18px, #f5c450 5px, transparent 5.5px),
|
||||
radial-gradient(circle at 58px 18px, #7ec98f 5px, transparent 5.5px);
|
||||
}
|
||||
.device-browser > .frame-shot,
|
||||
.device-browser > img {
|
||||
border-radius: 0;
|
||||
display: block;
|
||||
width: 100%;
|
||||
}
|
||||
.device-phone {
|
||||
position: relative;
|
||||
background: var(--ink);
|
||||
border-radius: 36px;
|
||||
padding: 14px;
|
||||
box-shadow: 0 24px 48px -20px rgba(20,18,14,.42);
|
||||
}
|
||||
.device-phone > .frame-shot,
|
||||
.device-phone > img {
|
||||
border-radius: 24px;
|
||||
display: block;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
/* ---------- 9c. Map block (.map-block) ---------- */
|
||||
/* Static map container — pure CSS frame, content is either an <img> from
|
||||
Mapbox Static API or an SVG schematic. Pins overlay as absolute children. */
|
||||
.map-block {
|
||||
position: relative;
|
||||
background: var(--paper-2);
|
||||
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; }
|
||||
/* tint mapbox / OSM tiles to fit editorial paper tone */
|
||||
.map-block.tone-paper > img { filter: saturate(.36) contrast(.92) brightness(1.04); mix-blend-mode: multiply; }
|
||||
.map-block.tone-ink > img { filter: saturate(.18) brightness(.62); }
|
||||
/* schematic mode: SVG drawn at viewBox 0 0 100 100, lon/lat pre-mapped to 0-100 */
|
||||
.map-block > svg .map-coast { fill: none; stroke: var(--line); stroke-width: .4; }
|
||||
.map-block > svg .map-road { fill: none; stroke: var(--muted); stroke-width: .28; stroke-dasharray: 1.2 .8; opacity: .65; }
|
||||
.map-block > svg .map-water { fill: var(--paper-2); }
|
||||
.map-block > svg .map-grid { stroke: var(--line); stroke-width: .15; opacity: .55; }
|
||||
|
||||
/* pins overlay — positioned by % coordinates */
|
||||
.map-pin {
|
||||
position: absolute;
|
||||
width: 0; height: 0;
|
||||
transform: translate(-50%, -50%);
|
||||
}
|
||||
.map-pin .dot {
|
||||
position: absolute;
|
||||
left: -7px; top: -7px;
|
||||
width: 14px; height: 14px;
|
||||
border-radius: 50%;
|
||||
background: var(--ink);
|
||||
border: 2px solid var(--paper);
|
||||
box-shadow: 0 1px 4px rgba(20,18,14,.32);
|
||||
}
|
||||
.map-pin.accent .dot { background: var(--accent); }
|
||||
.map-pin .line {
|
||||
position: absolute;
|
||||
left: 8px; top: 0;
|
||||
width: 32px; height: 1px;
|
||||
background: var(--ink);
|
||||
opacity: .5;
|
||||
}
|
||||
.map-pin.left .line { left: auto; right: 8px; }
|
||||
.map-pin .card {
|
||||
position: absolute;
|
||||
left: 44px; top: -22px;
|
||||
min-width: 86px;
|
||||
padding: 8px 10px;
|
||||
background: rgba(245,241,232,.94);
|
||||
box-shadow: 0 0 0 1px var(--line);
|
||||
border-radius: 2px;
|
||||
white-space: nowrap;
|
||||
}
|
||||
.map-pin.left .card { left: auto; right: 44px; }
|
||||
.map-pin .card .name {
|
||||
font-family: var(--serif-zh);
|
||||
font-size: 16px;
|
||||
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(--muted);
|
||||
}
|
||||
.map-pin.accent .card .name { color: var(--accent); }
|
||||
|
||||
.map-legend {
|
||||
position: absolute;
|
||||
left: 14px; bottom: 14px;
|
||||
z-index: 3;
|
||||
background: rgba(245,241,232,.92);
|
||||
padding: 8px 12px;
|
||||
box-shadow: 0 0 0 1px var(--line);
|
||||
font-family: var(--mono);
|
||||
font-size: 11px;
|
||||
letter-spacing: .14em;
|
||||
text-transform: uppercase;
|
||||
color: var(--muted);
|
||||
}
|
||||
.map-legend.tr { left: auto; right: 14px; top: 14px; bottom: auto; }
|
||||
|
||||
/* ---------- 10. Editorial components ---------- */
|
||||
.callout {
|
||||
border-left: 3px solid var(--accent);
|
||||
padding: 18px 28px;
|
||||
font-family: var(--serif-zh);
|
||||
font-style: italic;
|
||||
font-size: 36px;
|
||||
line-height: 1.35;
|
||||
color: var(--ink);
|
||||
background: var(--paper-2);
|
||||
}
|
||||
.callout .callout-src {
|
||||
display: block;
|
||||
margin-top: 18px;
|
||||
font-style: normal;
|
||||
font-family: var(--mono);
|
||||
font-size: 20px;
|
||||
letter-spacing: .12em;
|
||||
color: var(--muted);
|
||||
}
|
||||
|
||||
.ledger { display: flex; flex-direction: column; }
|
||||
.ledger-row {
|
||||
display: grid;
|
||||
grid-template-columns: 96px 1fr auto;
|
||||
gap: 24px;
|
||||
align-items: baseline;
|
||||
padding: 28px 0;
|
||||
border-bottom: 1px solid var(--line);
|
||||
}
|
||||
.ledger-row .ledger-nb {
|
||||
font-family: var(--mono);
|
||||
font-size: 28px;
|
||||
color: var(--accent);
|
||||
letter-spacing: .08em;
|
||||
}
|
||||
.ledger-row .ledger-title {
|
||||
font-family: var(--serif-zh);
|
||||
font-weight: 500;
|
||||
font-size: 42px;
|
||||
letter-spacing: .02em;
|
||||
color: var(--ink);
|
||||
}
|
||||
.ledger-row .ledger-note {
|
||||
font-family: var(--serif-zh);
|
||||
font-weight: 400;
|
||||
font-size: 22px;
|
||||
line-height: 1.55;
|
||||
color: rgba(var(--ink-rgb), .72);
|
||||
}
|
||||
|
||||
.marginalia {
|
||||
display: grid;
|
||||
grid-template-columns: 1fr 220px;
|
||||
gap: 48px;
|
||||
}
|
||||
.marginalia .mg-col {
|
||||
border-left: 1px solid var(--line);
|
||||
padding-left: 24px;
|
||||
}
|
||||
.marginalia .mg-col p {
|
||||
font-family: var(--mono);
|
||||
font-size: 20px;
|
||||
letter-spacing: .04em;
|
||||
color: var(--muted);
|
||||
margin: 0 0 16px;
|
||||
}
|
||||
|
||||
.pipeline-v { display: flex; flex-direction: column; gap: 28px; }
|
||||
.pipeline-v .step {
|
||||
display: grid;
|
||||
grid-template-columns: 80px 1fr;
|
||||
gap: 28px;
|
||||
align-items: baseline;
|
||||
padding-bottom: 24px;
|
||||
border-bottom: 1px solid var(--line);
|
||||
}
|
||||
.pipeline-v .step:last-child { border-bottom: 0; }
|
||||
.pipeline-v .step-nb {
|
||||
font-family: var(--mono);
|
||||
font-size: 32px;
|
||||
color: var(--accent);
|
||||
}
|
||||
.pipeline-v .step-title {
|
||||
font-family: var(--serif-zh);
|
||||
font-weight: 500;
|
||||
font-size: 40px;
|
||||
letter-spacing: .02em;
|
||||
margin: 0 0 8px;
|
||||
}
|
||||
.pipeline-v .step-desc {
|
||||
font-family: var(--serif-zh);
|
||||
font-weight: 400;
|
||||
font-size: 24px;
|
||||
line-height: 1.6;
|
||||
color: rgba(var(--ink-rgb), .72);
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.beforeafter {
|
||||
display: grid;
|
||||
grid-template-rows: 1fr 1fr;
|
||||
gap: 32px;
|
||||
height: 100%;
|
||||
}
|
||||
.beforeafter .ba-block {
|
||||
padding: 32px;
|
||||
background: var(--paper-2);
|
||||
border-left: 3px solid var(--accent);
|
||||
}
|
||||
.beforeafter .ba-block.before { opacity: .68; }
|
||||
|
||||
/* ---------- 11. Pair preview frame ---------- */
|
||||
.pair-preview {
|
||||
width: 2400px;
|
||||
min-height: 1180px;
|
||||
background: var(--paper-2);
|
||||
padding: 48px;
|
||||
display: grid;
|
||||
grid-template-columns: 2100px 1080px;
|
||||
grid-template-rows: 900px 1080px;
|
||||
gap: 48px;
|
||||
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-size: 20px;
|
||||
letter-spacing: .12em;
|
||||
text-transform: uppercase;
|
||||
color: var(--muted);
|
||||
margin-bottom: 14px;
|
||||
}
|
||||
|
||||
/* ---------- 12. Per-poster mounting helpers ---------- */
|
||||
.poster .corner-tl,
|
||||
.poster .corner-tr,
|
||||
.poster .corner-bl,
|
||||
.poster .corner-br {
|
||||
position: absolute;
|
||||
font-family: var(--mono);
|
||||
font-size: 18px;
|
||||
letter-spacing: .12em;
|
||||
text-transform: uppercase;
|
||||
color: var(--muted);
|
||||
z-index: 3;
|
||||
}
|
||||
.poster.xhs .corner-tl,
|
||||
.poster.square .corner-tl { left: 88px; top: 56px; }
|
||||
.poster.xhs .corner-tr,
|
||||
.poster.square .corner-tr { right: 88px; top: 56px; }
|
||||
.poster.xhs .corner-bl,
|
||||
.poster.square .corner-bl { left: 88px; bottom: 56px; }
|
||||
.poster.xhs .corner-br,
|
||||
.poster.square .corner-br { right: 88px; bottom: 56px; }
|
||||
.poster.wide .corner-tl { left: 120px; top: 56px; }
|
||||
.poster.wide .corner-tr { right: 120px; top: 56px; }
|
||||
.poster.wide .corner-bl { left: 120px; bottom: 56px; }
|
||||
.poster.wide .corner-br { right: 120px; bottom: 56px; }
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<!-- THEME_OVERRIDE
|
||||
Change <html data-theme="…"> above to:
|
||||
ink-classic | indigo-porcelain | forest-ink | kraft-paper | dune | midnight-ink
|
||||
-->
|
||||
<main class="sheet">
|
||||
|
||||
<!-- POSTERS_HERE -->
|
||||
<!--
|
||||
Replace the placeholder posters below with real content.
|
||||
Each poster carries one Layout Recipe (M01-M15) 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.
|
||||
-->
|
||||
|
||||
<!-- ============================================================
|
||||
Placeholder: M01 Magazine Issue Cover (3:4)
|
||||
Replace with real cover content from layout-recipes.md.
|
||||
============================================================ -->
|
||||
<section class="poster xhs" id="xhs-01">
|
||||
<canvas class="mag-bg" data-bg="ink-flow"></canvas>
|
||||
<div class="grain"></div>
|
||||
<div class="content stack gap-4">
|
||||
<div class="issue-row">
|
||||
<span>Vol. 01</span><span class="dot"></span><span>2026.05</span>
|
||||
</div>
|
||||
<div class="stack gap-2">
|
||||
<p class="kicker">封面 · Cover</p>
|
||||
<h1 class="h-display">[标题占位]<br>替换为真实文案</h1>
|
||||
<p class="h-sub">A working subtitle</p>
|
||||
</div>
|
||||
<figure class="frame-img r-16x10">
|
||||
<img src="assets/cover.jpg" alt="cover image placeholder">
|
||||
</figure>
|
||||
<p class="lead">用一段克制的引语解释为什么这一组卡片值得读完。</p>
|
||||
</div>
|
||||
<div class="issue-strip">
|
||||
<span>Issue · Magazine</span>
|
||||
<span>—</span>
|
||||
<span>by [作者]</span>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
</main>
|
||||
|
||||
<!-- Magazine WebGL background (deterministic ink-flow) -->
|
||||
<script src="assets/magazine-bg-webgl.js"></script>
|
||||
<script>
|
||||
(function () {
|
||||
function cssVarRgb(name, fallback) {
|
||||
var raw = getComputedStyle(document.documentElement).getPropertyValue(name).trim();
|
||||
if (!raw) return fallback;
|
||||
var parts = raw.split(',').map(function (x) { return parseInt(x.trim(), 10); });
|
||||
return parts.length === 3 && parts.every(function (n) { return !isNaN(n); }) ? parts : fallback;
|
||||
}
|
||||
function mountAll() {
|
||||
if (!window.MagazineBg || !window.MagazineBg.mount) return;
|
||||
var ink = cssVarRgb('--ink-rgb', [10, 10, 11]);
|
||||
var paper = cssVarRgb('--paper-rgb', [243, 240, 232]);
|
||||
var accent = cssVarRgb('--accent-rgb', [17, 17, 17]);
|
||||
document.querySelectorAll('canvas.mag-bg').forEach(function (c) {
|
||||
window.MagazineBg.mount(c, {
|
||||
ink: ink,
|
||||
paper: paper,
|
||||
accent: accent,
|
||||
strength: 0.32,
|
||||
frozenTime: 12.5
|
||||
});
|
||||
});
|
||||
}
|
||||
if (document.readyState === 'complete') mountAll();
|
||||
else window.addEventListener('load', mountAll);
|
||||
})();
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,935 @@
|
||||
<!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>
|
||||
Reference in new issue
Block a user