From eeb6819c2c9df5faa6a22a83e9ff455c843bf102 Mon Sep 17 00:00:00 2001 From: Fini Date: Sun, 9 Aug 2026 19:55:26 +0800 Subject: [PATCH] feat(ai): teach the corpus how a projector board differs from a page MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Deck output kept inheriting page reflexes — screen-sized type, page margins, a row clipped instead of split — because nothing in the corpus stated the laws a 16:9 board is read under. `deck-contract.md` states them once (back-row type floors, density budgets, narrative arc, the slop bans), and six style guides give the new deck templates the same authored voice when a request names one. The generation budget moves to 13200 so the deck material is additive rather than evicting the knowledge skills it depends on, and a standing test pins the report to the knapsack's own accounting — a budget-exhausted drop now provably means the skill did not fit. The CJK kerning rule lands on minimal-keynote, whose display tracking was tight enough to collide glyph side bearings at large sizes. Claude-Session: https://claude.ai/code/session_01FqKQqNj8exYwopGDpYUU7x --- .../skills/domains/cjk-typography.md | 13 +- .../skills/domains/deck-contract.md | 99 ++++++++ .../skills/domains/deck-patterns.md | 2 +- .../skills/knowledge/design-principles.md | 2 + .../skills/style-guides/banxin-rule.md | 181 +++++++++++++++ .../skills/style-guides/dossier-linen.md | 216 ++++++++++++++++++ .../skills/style-guides/gridpaper-graphite.md | 184 +++++++++++++++ .../skills/style-guides/ledger-tick.md | 186 +++++++++++++++ .../skills/style-guides/sounding-navy.md | 182 +++++++++++++++ .../skills/style-guides/tidemark-slate.md | 210 +++++++++++++++++ crates/op-ai-skills/src/deck_corpus_tests.rs | 142 +++++++++++- crates/op-ai-skills/src/resolve.rs | 66 +++++- crates/op-ai-skills/src/types.rs | 17 +- .../assets/scene_templates/minimal-keynote.op | 8 +- 14 files changed, 1491 insertions(+), 17 deletions(-) create mode 100644 crates/op-ai-skills/skills/domains/deck-contract.md create mode 100644 crates/op-ai-skills/skills/style-guides/banxin-rule.md create mode 100644 crates/op-ai-skills/skills/style-guides/dossier-linen.md create mode 100644 crates/op-ai-skills/skills/style-guides/gridpaper-graphite.md create mode 100644 crates/op-ai-skills/skills/style-guides/ledger-tick.md create mode 100644 crates/op-ai-skills/skills/style-guides/sounding-navy.md create mode 100644 crates/op-ai-skills/skills/style-guides/tidemark-slate.md diff --git a/crates/op-ai-skills/skills/domains/cjk-typography.md b/crates/op-ai-skills/skills/domains/cjk-typography.md index 7ea9696b6..099de4936 100644 --- a/crates/op-ai-skills/skills/domains/cjk-typography.md +++ b/crates/op-ai-skills/skills/domains/cjk-typography.md @@ -6,7 +6,7 @@ trigger: keywords: - "/[\\u4e00-\\u9fff\\u3040-\\u309f\\u30a0-\\u30ff\\uac00-\\ud7af]/" priority: 21 -budget: 500 +budget: 700 category: domain --- @@ -14,7 +14,12 @@ CJK TYPOGRAPHY (Chinese/Japanese/Korean): - LANGUAGE CONSISTENCY (critical): when the request is in Chinese/Japanese/Korean, write EVERY UI string in that language — labels, nav tabs, buttons, placeholders, badges, section titles. Do NOT leave English boilerplate mixed in (e.g. "Deliver to", "See all", "Home", "Order now") next to CJK text; translate those too ("配送至", "查看全部", "首页", "立即下单"). A half-translated screen ("Deliver to · 现在") reads as broken. Brand/product proper nouns may stay in their original form. - Headings: "Noto Sans SC" (Chinese) / "Noto Sans JP" (Japanese) / "Noto Sans KR" (Korean). NEVER "Space Grotesk" / "Manrope" for CJK — no CJK glyphs. -- Body: ALWAYS "Inter" (uses system CJK fallback at render time). Do NOT pick a script-specific Noto for body — that rule is HEADING-only. Matches the `text` section of get_design_prompt, `decomposition.md` ("body='Inter'"), and `add_body_text_v0`. -- CJK lineHeight: headings 1.3-1.4 (NOT 1.1 like Latin), body 1.6-1.8 (Latin body is 1.4-1.6). letterSpacing: 0, NEVER negative (causes CJK character overlap). +- Body family, DESIGN layer: from the chosen tier's font pairing, exactly as in a Latin design. Script-specific Noto is a HEADING-only rule. +- Body family, RENDER layer: when that family is not in the font bundle, emit "Inter" and let the system CJK fallback carry the glyphs — a FALLBACK, never the reason a body style was chosen. Matches get_design_prompt's `text` section, `decomposition.md` ("body='Inter'"), `add_body_text_v0`. +- lineHeight bands by FONT SIZE, not by heading-vs-body (1.3 tears a 96px title apart): >=64px 1.02-1.15; 48-63px 1.15-1.25; 40-47px 1.3-1.4; body 1.7-1.8 (Latin 1.5-1.6 — CJK is +0.2); captions 1.45-1.5. +- letterSpacing is absolute px here, not em. <48px: ALWAYS 0, never negative — negative tracking collides CJK glyphs. >=64px display: `round(fontSize * -0.02)` is allowed and is the FLOOR, never further (96 -> -2, 168 -> -3). Uppercase Latin micro-labels may take +1 to +2 — decide by the run's ACTUAL script, not the label's style: a CJK label that looks like a small-caps tag still takes 0, because positive tracking opens gaps in an already full-width em box. - CJK buttons: each char is approximately fontSize wide. Container width >= (charCount × fontSize) + padding. -- Detect CJK from user request language — apply CJK rules (script-specific Noto for headings; Inter + CJK lineHeight/letterSpacing for body; 0 letterSpacing everywhere). +- Line length: body runs <=30 Han chars per line (a 1728-wide box at 32px fits 54) — narrow the block or split it into columns; available width is not permission to use it. +- Never truncate a CJK title with an ellipsis: rewrite the copy or change the layout. +- Mixed runs: one space between a CJK run and a Latin/number run, written into the copy (not letterSpacing); none between a number and its unit. Digits and Latin take the Latin family — a CJK family rendering digits makes column widths wobble; numbers read down a column need monospace or fixed widths. +- Detect CJK from the request language — apply these rules (script-specific Noto for headings; the size-banded lineHeight/letterSpacing above everywhere). diff --git a/crates/op-ai-skills/skills/domains/deck-contract.md b/crates/op-ai-skills/skills/domains/deck-contract.md new file mode 100644 index 000000000..9dc5228fb --- /dev/null +++ b/crates/op-ai-skills/skills/domains/deck-contract.md @@ -0,0 +1,99 @@ +--- +name: deck-contract +description: Cross-tier deck contract — overflow/density/consistency laws, narrative arc, page-type routing, deck-specific slop bans +phase: [generation] +trigger: + keywords: [slide, slides, deck, presentation, pitch deck, keynote, ppt, 幻灯片, 演示, 演示文稿, 路演, 课件, 汇报] +priority: 23 +budget: 1700 +category: domain +--- + +DECK CONTRACT + +Holds for every deck. `slides` picks the tier and `deck-patterns` emits the skeleton; this decides what goes on a page, and in what order. + +## State the communication job first + +One sentence, before any page: "After this, [audience] should [decide / do / believe what], because [the one claim]." The audience is NOT necessarily whoever asked — a board, a class and a customer need different claims. Every page advances that sentence or is cut. + +## Law 1 — overflow splits the page, it never shrinks the type + +Content does not fit. Act in this order, always: + +1. Cut the copy. Good layout cannot rescue bad editing. +2. Move to a denser page type (Law 2). +3. Split into two pages. + +FORBIDDEN as fixes: a smaller font; more elements in the same region; `clipContent` to crop the excess; line-height below the floor. One extra page costs nothing, one crammed page costs the argument — this overrides any instinct to hold a page count. + +## Law 2 — the density budget belongs to the page type, not the deck + +A slot = one independent text node: title, kicker, bullet, figure, label, caption, footnote each count 1. + +| density | slots | page types | +|---|---|---| +| low | <=3 (cover <=6) | cover, divider, statement, one big number — one thing to read, no bullets added to fill space | +| medium | 4-6 | argument, two-column compare, process — one claim plus light support | +| medium-high | 7-10 | three-column points, image+text, swimlane — structured, still an obvious primary | +| high | 11-16 | matrix, detail table, numbered paragraphs — evidence-dense; past the cap you SPLIT, not compress | + +The cap follows the layout's carrying capacity — a matrix page may carry 16 slots where a process page may not carry 7. In a table a **row** is one slot, not a cell; header, legend, source and conclusion lines count 1 each. Counting cells would outlaw table pages, and tables are the reason the high tier exists. Check while planning, not after it breaks. + +## Law 3 — visual language locked, information structure varies + +Two mirror-image failures: eight pages of one template, and eight pages of eight styles. One table solves both. + +| LOCKED across pages | MUST change across pages | +|---|---| +| aspect ratio; safe margins | position and form of the lead visual | +| type system (family + weight ladder) | information structure (compare / flow / list / data / narrate) | +| colour tokens and their roles | spatial anchor — never the same x/y twice | +| page-number and page-label position | rhythm: a dense page is followed by an open one | +| corner radius; strokes; icon style | emphasis words; compositional centre of gravity | + +Adjacent pages may not share a page type (two consecutive tables is the only exception, and page 3 must change). A deck of >=6 pages covers >=4 page-type families. Do not centre every page. + +Pre-flight, no rendering: say the page-type sequence aloud. If it repeats — "grid, grid, two-column, grid" — so does the deck. Fix the outline. + +## Narrative + +Pick exactly one arc: context-stakes-evidence-implication-action / question-analysis-answer / problem-cause-recommendation / today-shift-tomorrow / chronology or process progression. + +**An agenda is not a narrative.** The sequence must accumulate: each page answers the question the page before it raised. Opening and closing are statements, not information. Never close on a detail page, a technical artefact, an unframed summary, or a "thank you". + +**Ghost deck test**: read only the titles in order — do they carry the whole argument? If not, fix the outline before drawing. After each title the next should feel inevitable; a page that could sit anywhere is misplaced. + +**Titles state conclusions, not topics** — "Churn concentrates in month two", not "Churn analysis". The judge: a real speaker would say this sentence out loud. If it reads like a prompt or a slogan, rewrite it. + +## Page-type routing + +Shared avoid rule: **when content fights the page type's region count, density cap or lead-visual role, change the page type — never bend the content into it.** + +| page type | use when | avoid when | +|---|---|---| +| cover | one claim + one qualifier | it must carry agenda items or logo walls | +| statement / quote | one sentence is the whole point | it needs numbers beside it | +| big number | one figure carries the page (+ source) | two or more figures compete — use tiles or a matrix | +| three-column points | items are parallel and comparable | they differ in weight, or there are 4+ | +| two-column compare | before/after, option A/B | a 50/50 split (use 7:3) or mirrored sides | +| process / timeline | order carries meaning | the steps are unordered — that is a list | +| table / matrix | dense evidence read by column | under 3 rows, or one cell is the real message | +| image-led | the visual IS the evidence | decorative stock; one image reused in a deck | + +## One accent, undecorated charts + +- **One accent colour, at most once per page.** Occurrences count, not hues: an accent used 11 times is not an accent — the second occurrence goes neutral. The palette needs a lead: one colour over most of the area, one support, one accent — three sharing the stage equally is no position. +- **Charts carry no decoration**: no gridlines, legend or y-axis; values labelled on the marks; only the key series takes the accent, the rest a neutral ramp. Never pie, 3D, shadowed bars or gradient bars. +- Asymmetry beats symmetry: a perfectly centred page is static and directionless. + +## Deck slop — each is a recognisable fingerprint + +1. **Everything in a card.** Containers are for real grouping: a statement is the large type itself, a number is the number itself, a contrast can be space alone. +2. **Comparison pages mirroring one structure with only the colour swapped** — one template run twice. The sides must differ compositionally. +3. **The same spatial anchor on every page**; identical margins and content origin cancel out every other kind of variety. +4. **A rule under every title** — the clearest giveaway. Separate with space, colour, or a change of layout. +5. **Decorative shapes at 4-6% opacity** — pretending to design. A motif reads as a decision, or it is deleted. +6. **Flat hierarchy** — every size inside one 20px band. Largest >=2.5x body; no size above 60px anywhere means no hierarchy. Max 4 steps of the scale per page. +7. **Implementation language in visible copy** — "this slide", "the generated chart", "as mentioned above". Delete it. +8. **Formulaic titles** — "From X to Y", ad slogans, parallel phrasing on every page. diff --git a/crates/op-ai-skills/skills/domains/deck-patterns.md b/crates/op-ai-skills/skills/domains/deck-patterns.md index ea58d20cf..8c271059e 100644 --- a/crates/op-ai-skills/skills/domains/deck-patterns.md +++ b/crates/op-ai-skills/skills/domains/deck-patterns.md @@ -4,7 +4,7 @@ description: Slide-pattern skeletons for presentation decks — board placement, phase: [generation] trigger: keywords: [slide, slides, deck, presentation, pitch deck, keynote, ppt, 幻灯片, 演示, 演示文稿, 路演, 课件, 汇报] -priority: 23 +priority: 22 budget: 1900 category: domain --- diff --git a/crates/op-ai-skills/skills/knowledge/design-principles.md b/crates/op-ai-skills/skills/knowledge/design-principles.md index 4078b69f8..542a8be97 100644 --- a/crates/op-ai-skills/skills/knowledge/design-principles.md +++ b/crates/op-ai-skills/skills/knowledge/design-principles.md @@ -10,6 +10,8 @@ category: knowledge DESIGN CRAFT: +SCOPE — every number below is the SCREEN / PAGE scale (web app, landing page, mobile). A presentation deck overrides them wholesale: its type floors, margins, background policy and container rules come from `slides` / `deck-contract`, where body is 32px and a display title runs 88-168px. Never apply the sizes below to a slide. + - Type scale with real contrast: display 48-64, heading 28-36, body 16. Weight: 700 titles, 500 subtitles, 400 body. - Line height: tighter at large sizes (1.05-1.15 for 40px+), looser at small (1.5-1.6 for 16px). - Palette: 1 primary action color, 1 accent, neutral scale. Max 2 saturated colors. Page bg slightly tinted (#F8FAFC not #FFFFFF). diff --git a/crates/op-ai-skills/skills/style-guides/banxin-rule.md b/crates/op-ai-skills/skills/style-guides/banxin-rule.md new file mode 100644 index 000000000..35c9401bc --- /dev/null +++ b/crates/op-ai-skills/skills/style-guides/banxin-rule.md @@ -0,0 +1,181 @@ +--- +name: 'banxin-rule' +tags: [cjk-type, light-mode, classical, editorial, serif, dual-font, warm-tones, red-accent, sharp-corners, flat, austere, refined] +platform: slides +--- + +## Style Scope + +This guide is self-contained and written for **Chinese-native presentation decks** — a 1920×1080 fixed stage where the Chinese body text itself is the protagonist, not a caption under a Latin layout. Apply its palette, grid, rules, and folio marks only when this exact guide is selected; do not borrow dashboard, landing-page, or Latin-keynote patterns into it. Treat unnamed layout frames as structural by default: no fill, stroke, radius, or shadow unless the node is deliberately a 界行 rule, a quote block, a table zebra row, or a dark case page. Hierarchy comes from the head/foot asymmetry, the rules, and the size jump — never from wrapper cards. + +## Style Summary + +Anchored on the **page geometry of a woodblock-printed book**: 版心 (banxin, the type area bounded by the head and foot margins), 界行 (jiehang, the thin vertical rules that separate columns), 天头/地脚 (head margin far taller than foot margin), 鱼尾 (the geometric folio mark at the foot), and 函套 (the dark protective case the volumes sit in). What is anchored is the **page structure**, not any ornament. There is no brush, no wash, no seal, no cloud pattern anywhere in this style. + +Sampling ran over the yellow of xuan paper, the brown cast of ink, cinnabar collation marks, and the dark brown of a book case. It converged on three facts: the light ground is xuan white at L0.955 C0.010 H88; the dark ground is case ink at L0.215 C0.014 H60; the only chromatic colour is cinnabar at H32 C0.140, and every neutral sits warm at H60. + +Three arguments carry the whole style: + +- **Ink is taken at L0.265 with 0.012 of warm chroma, never pure black.** Paper reflects warm; pure black on a warm white sheet floats a layer above it. This is the exact line between this deck and a black-on-white slide. +- **Head margin > foot margin, about 1.35 : 1 (144 : 108).** Books did this so the head could carry annotations. We keep it for two contemporary reasons: a projected slide's lower edge is routinely blocked by the heads in the room, and the asymmetry instantly breaks the "everything centred, all four margins equal" AI fingerprint. One decision solving a physical problem and an aesthetic one at once. +- **Cinnabar appears only as the folio mark and one emphasis per page, never as area.** The standard collapse of a Chinese palette is red spread wide — the moment it spreads, the page stops being a book leaf and becomes a festival poster. + +Key aesthetics: + +- **Two grounds, used as bookends**: xuan paper for the content pages, case ink for the cover, contents, and closing +- **界行 as the structural device**: a 1px vertical rule, full type-area height, marking the page rather than boxing the content +- **Warm neutral ramp only**: every grey is H60 warm; a cold grey goes green against this paper within one step +- **Cinnabar rationed to a folio mark**: one accent colour, two sanctioned uses, no third +- **Head-note as a third text layer**: title / body / 注疏 — annotation living in the head margin, not a header +- **Zero radius, zero shadow, zero gradient**: a printed leaf has none of the three +- **Two rule weights only**: 1px 界行, 2px divider — nothing between and nothing above +- **Numerals leave the Chinese family**: figures and Latin take mono or the paired Latin serif + +## Color System + +### Grounds + +| Token | Value | Usage | +| ------------- | ------- | --------------------------------------------------------------------- | +| Xuan White | #F3F0E9 | Content-page ground. The paper; never a block colour | +| Xuan Deep | #E5E1D7 | Quote blocks and table zebra rows. 11.73:1 under ink text | +| Case Ink | #1E1813 | Cover / divider / closing ground — the 函套 case | +| Case Ink Low | #130E0A | A deeper block laid on a case-ink page | + +Four steps, all warm (H60–H88). A hue shift between grounds would read as two different papers. + +### Text Colors + +| Token | Value | Usage | +| ----------------- | ------- | ---------------------------------------------------------------- | +| Ink Text | #2A241F | Body copy and titles on paper. 13.46:1 | +| Ink Soft | #5F5A55 | Secondary text, side notes, table sources. 5.99:1 | +| Ink Faint | #807A76 | Head-notes and folio numerals. 3.72:1 — **legal at ≥24px only** | +| Xuan on Case | #F0ECE7 | Primary text on a case-ink page. 14.94:1 | +| Xuan Dim on Case | #B4B0AA | Secondary text on a case-ink page. 8.14:1 | + +Ink Faint is the one tone in this palette that fails at small sizes by design — it exists for the two elements that are supposed to recede, and both of them are set at 24px or above. Never move it onto body copy to make a page "quieter". + +### Rule and Accent + +| Token | Value | Usage | +| ---------------- | ------- | ------------------------------------------------------------------------------ | +| Jie Rule | #D0CBC5 | The 界行 rules. **Non-text only** — never carries a glyph | +| Cinnabar Folio | #AA4331 | **The sole accent**: folio mark, folio numeral, one emphasis per page. 5.19:1 | +| Cinnabar Wash | #F9D7CE | Pale cinnabar ground that carries ink text. 11.41:1 | + +Two cinnabar tones, not interchangeable: the strong tone is a mark, the wash is a ground. There is no third chromatic colour in this style, and no second accent may be introduced for a chart series, a status tag, or a highlight — the neutral ramp carries all of that. + +## Typography + +### Font Families + +| Role | Preferred | Fallback (ship this) | Usage | +| ----------------- | ------------------------------------------ | --------------------- | ---------------------------------------- | +| Display | 源流明體 / 思源宋體 Semibold | Noto Sans SC 700 | Cover title, page titles, closing line | +| Body | 霞鶩文楷 (LXGW WenKai, OFL) | Noto Sans SC 400 | Body copy, side notes, table cells | +| Numerals / Latin | IBM Plex Mono + Source Serif 4 | Inter 400 | Folio numerals, contents numbering, data | + +霞鶩文楷 is the preferred body face because it stays readable across long Chinese passages, which is the load this deck is built to carry. The Latin face is written **first** in every fallback chain: font matching is per-character, Latin faces carry no Han codepoints, so Han falls through to the Chinese face automatically. Written the other way round, the Chinese face's own weak Latin swallows every digit and the paired face never appears. + +**Never mix more than two Chinese families.** 宋 + 黑 is the ceiling, and 宋 is display-only. + +### Type Scale + +| Level | Size | Font | Line Height | Usage | +| -------------- | ---- | -------- | ----------- | ------------------------------------------------------ | +| Cover Title | 96px | Display | — | Cover only, ≤2 lines | +| Closing Line | 64px | Display | — | Closing page only, ≤2 lines | +| Page Title | 56px | Display | — | Every interior page | +| Section Title | 40px | Display | — | Contents entries, in-page section heads | +| Quote Body | 36px | Body | 1.8 | Quote-page block only, ≤4 lines | +| Body | 32px | Body | **1.75** | **Default body copy**; also the cover subtitle | +| Note | 26px | Body | — | Head-notes, side notes, quote sources | +| Folio | 24px | Numerals | — | Folio numeral under the fishtail mark | + +96px is the cover ceiling **because Han strokes are dense** — at the same visual weight a Chinese title sits one step below its Latin equivalent, so 96 here does the work 120 does in a Latin deck. Line-height and letter-spacing bands come from `cjk-typography`; the only value this style pins beyond them is body 32/1.75, which is deliberately looser than the band's floor because the body block is the protagonist. + +### Font Weights + +| Weight | Value | Usage | +| -------- | ----- | ------------------------------------------------------------------------ | +| Regular | 400 | All body copy, side notes, table cells, folio numerals | +| Semibold | 600 | The preferred display face (思源宋體 Semibold) at every display level | +| Bold | 700 | The fallback display face (Noto Sans SC) — it needs 700 to hold the same weight | + +Chinese has no italic. Never set `font-style: italic` on Han glyphs; the synthesised skew deforms the strokes. Emphasis moves to weight, to a `Cinnabar Wash` ground, or to a change of face — never to a colour change on the text itself, because the only chromatic colour is reserved. + +### The measure, and why 界行 exists + +The body block is **never allowed to fill the 1680px content width** — at 32px that is 54 characters to the line, far past the ceiling in `cjk-typography`. The body block therefore narrows to **8 columns (≈1128px)** or splits into two columns. That constraint is the entire functional reason the 界行 motif exists: the rules are what make a narrowed or split measure read as a designed page instead of a short paragraph floating in white space. They are structure earning its keep, not decoration. + +## Layout Grammar + +- Stage 1920×1080, one top-level frame per page. +- Margins **top 144 / bottom 108 / left and right 120** — 天头 > 地脚, and this asymmetry is not negotiable. +- Content width **1680 = 12 columns × 118 + 11 gutters × 24**. +- Corner radius **0**, everywhere, on everything. +- Rule weights: **1px** for 界行, **2px** for a divider. There is no third weight. +- Gap base **24**; every vertical rhythm is a multiple of it. +- Draw every rule as a `rectangle` (a 1–3px-high or -wide rect), never as a `line` node. + +## Spacing System + +Every value below is fixed by the page geometry; none of them is a free parameter. + +| Value | Usage | +| -------- | ---------------------------------------------------------------------------- | +| 4px | The gap between the two fishtail tips — the smallest measure in the style | +| 8px | The side of each fishtail triangle | +| 12px | Fishtail mark to folio numeral | +| 24px | **The gap base.** Column gutter, and the unit every vertical rhythm multiplies | +| 32px | Text clearance on each side of a 界行 rule | +| 56px | Page top edge to the head-note baseline row | +| 108px | Foot margin | +| 120px | Left and right margins | +| 144px | Head margin — 1.35× the foot | + +A value that is not on this list and not a multiple of 24 is a value someone invented. The 4 / 8 / 12 trio exists solely inside the folio mark and appears nowhere else in the deck. + +## Signature Motifs + +**1 · 界行 `jie-rule`** — column separation is a 1px vertical `Jie Rule` line that **spans the full type-area height**, from the underside of the head margin to the top of the foot margin. It does not stretch or shrink with the text beside it. This is the root difference from card-based columns: a card boxes content, a 界行 marks the page. Text keeps **32px** clearance on each side of the rule. Horizontal 界行 rules follow the same weight and colour and are used between contents entries and between table rows. + +**2 · 鱼尾页码 `folio-mark`** — centred in the foot margin: two **8px** isosceles triangles set point-to-point with a **4px** gap between the tips, filled `Cinnabar Folio`; **12px** below it, the folio numeral at mono **24px** in `Ink Faint`. This is an original geometric form — **draw no fish and no representational object of any kind**. It repeats on every page and is the deck's constant identity anchor across spreads. + +**3 · 天头批注 `head-note`** — inside the head-margin band, **56px** from the top edge, a single line of **26px** `Ink Faint` annotation. It is not a title and not a running header: it carries meta-information about where this page sits in the argument, and it constitutes the third text layer of the style (title / body / 注疏). Head-notes are **optional**, but the moment one page uses one, **at least three pages in the deck must** — a single occurrence reads as a stray element. + +## Page Types + +| # | Page | Density | Slot cap | Structure | +| -- | ----------------- | ----------- | -------- | -------------------------------------------------------------------------------------------------------------------- | +| 01 | Cover · case | low | 4 | Case-ink ground; title 96 (≤2 lines) + a 96×2 cinnabar short rule + subtitle 32 + mono byline | +| 02 | Contents · case | low | 7 | Case-ink ground; 5–6 entries, each a mono numeral + a 40px entry name, 1px horizontal 界行 between entries | +| 03 | Argument · paper | medium-high | 8 | Head-note + page title 56 + body across 8 columns at 32/1.75 (≤6 lines) + a 3-column side note (26px) right of the rule | +| 04 | Parallel reading | medium-high | 10 | Page title 56 + two 5-column columns split by a 1px 界行; left 「其说」 (their claim), right 「我见」 (my reading); the right column's opening sentence sits on `Cinnabar Wash` | +| 05 | Quote | medium | 5 | Page title 56 + a `Xuan Deep` quote block (36/1.8, ≤4 lines) + right-aligned source at 26 + 界行 bracketing the block left and right | +| 06 | Table | medium-high | 12 | Page title 56 + table (2px ink rule under the header; 1px 界行 between rows; **no rule on the last row**; zebra in `Xuan Deep`) + source | +| 07 | Closing · case | low | 4 | Case-ink ground; closing line 64 (≤2 lines) + an enlarged cinnabar fishtail centred + byline | + +**Dark and light pages are bookends only** — the first two pages and the last. The middle of the deck never alternates grounds. + +## Strictly Avoid + +- **No ink wash, brushwork, flying white, bleed, seals, key-fret or cloud patterns, ruyi motifs, or decorative vertical lettering.** The identity of this style is page geometry; the instant any of these appear it collapses into a generic "China-style template". +- **No corner radius, no shadow, no gradient.** Not on a quote block, not on a table, not on the cover. +- **No cinnabar as area.** Never a large ground, never a gradient, never a text colour — the two exceptions are the folio and the single per-page emphasis. +- **No 楷 or 宋 above 88px.** Stroke ink distributes unevenly at giant sizes and the glyphs shimmer; giant type goes to the sans face. +- **界行 rules carry no text, are never a background, and never exceed 2px.** A thickened rule stops being a page mark and becomes a border. +- **No third Chinese family.** 宋 + 黑 is the ceiling, and 宋 stays display-only. +- **Head-notes never carry a title, a page number, or "本页要点"-style meta commentary.** That is a running header, and this style does not have one. + +## Anti-Patterns + +- **The 界行 that hugs its text.** A rule sized to the paragraph beside it is a card border wearing a different name. It runs the full type area or it is deleted. +- **The symmetric page.** Equal head and foot margins undo the one decision that makes this style legible as a book leaf — and hand back the AI fingerprint the asymmetry was bought to break. +- **A fishtail redrawn per page.** The mark is the identity anchor precisely because it is byte-identical on all seven pages; a "variation" on page four reads as a mistake. +- **Cinnabar as a chart palette.** Series colour comes from the warm neutral ramp; the accent marks one thing on a page, and a chart with four cinnabar bars marks nothing. +- **A card wrapper around body copy on a paper page.** The paper is already the container. Rules and margins do the grouping. +- **Alternating grounds through the middle of the deck.** Case ink is a binding, not a rhythm device — pages 3 through 6 are paper, without exception. +- **A head-note on exactly one page.** Below three occurrences the layer does not exist; it just looks like something landed in the margin. +- **A cold grey borrowed from another guide.** Every neutral here is warm at H60; one cold grey dropped in reads green against this paper and gives away that the palette was assembled rather than derived. diff --git a/crates/op-ai-skills/skills/style-guides/dossier-linen.md b/crates/op-ai-skills/skills/style-guides/dossier-linen.md new file mode 100644 index 000000000..804149dc2 --- /dev/null +++ b/crates/op-ai-skills/skills/style-guides/dossier-linen.md @@ -0,0 +1,216 @@ +--- +name: 'dossier-linen' +tags: [light-mode, monochrome, editorial, corporate, enterprise, serif, monospace, cjk-type, austere, quiet, sharp-corners, flat] +platform: slides +--- + +## Style Scope + +This guide is self-contained and written for a **document memo deck** — a run of pages +that are read, not presented. Every page is a sheet that can be pulled out of the folder +and understood on its own; nothing on it depends on the page before it. Apply this +palette, grid, and type treatment only when this exact guide is selected; do not borrow +dashboard, keynote, poster, or landing-page patterns into it. +Treat unnamed layout frames as structural by default: no fill, no stroke, no cornerRadius, +no shadow, unless the node is deliberately the binding rail, a quote block, a decision +block, or a table band. +Hierarchy comes from clause numbering and from where a line starts on the grid — never +from a card shell. + +## Style Summary + +Linen paper with file ink on it. The ground is `#F3EEE4`, the warm-grey yellow of a linen +document envelope — chroma 0.014, which is what makes it read as *official stock* rather +than a cream journal page or a newsprint tint. Above it there is one colour and one only: +a rust thread at chroma 0.090, and it is allowed to be a binding line, a page number, and +a clause number. Nothing else. + +**This is the "high density ≠ small type" reference deck.** Its body density is the +highest in the whole system, and its type floor is identical to every other deck's. +Density here is bought with **line length, paragraph structure, and where the whitespace +is spent** — never by shrinking the body. Body 30 at line-height 1.8 is the absolute +floor of this style. When a page will not fit, you delete a clause or add a page. You do +not go to 28. + +Two derivations are load-bearing and should not be re-tuned: + +- **One chromatic ink, on purpose.** A memo persuades through structure, not through + colour. A second hue would send the reader hunting for "the highlight colour", and in + this deck the emphasis lives inside the sentence. +- **Rust stops at chroma 0.090.** Pushed to 0.13 and above, the binding line stops being + a *thread* and starts reading as a red annotation rule — a different meaning entirely. + +Key aesthetics: + +- **Linen ground**: `#F3EEE4` at chroma 0.014 — paper stock, never a panel colour +- **A single chromatic ink**: rust, on the binding rail, folio numbers, and clause numbers +- **Zero bullets anywhere**: numbered clauses and whole paragraphs, at most a ruled table +- **Asymmetric margins**: 216 left, 120 right — the binding takes the difference +- **Tables, never charts**: this deck reports; it does not present +- **Radius 0, no shadow, no gradient, no icon, no photo, no card** +- **The rail never moves**: same x on every page, and so is the body start + +## Color System + +### Ground + +| Token | Value | Usage | +| --- | --- | --- | +| Linen | #F3EEE4 | Page ground. Every page, no exceptions | +| Linen Band | #EFE9DE | Table zebra rows only. 14.00:1 under file ink | +| Linen Deep | #E6DFD3 | Quote blocks and decision blocks. 12.77:1 under file ink | + +Three steps at hue 80-82, separated by lightness alone (0.950 / 0.935 / 0.905). The steps +are deliberately close: they mark a change of *register*, not a change of surface. If a +block needs to be seen from across the room, this is the wrong deck. + +### Ink + +| Token | Value | Usage | +| --- | --- | --- | +| File Ink | #201C19 | Body copy, section titles, table text. 14.63:1 on linen | +| Soft Ink | #56524E | Secondary lines, source rows, table headers. 6.70:1 | +| Faint Ink | #746F6B | Folio labels and clause numbers when unaccented. 4.29:1 | + +All three sit at chroma 0.008 hue 60 — a warm near-black rather than a true black. A +neutral grey secondary tone goes cold against linen within one step and reads as a +different document. + +### Rule and Thread + +| Token | Value | Usage | +| --- | --- | --- | +| Hairline | #C0BDB8 | 1px rules: table lines, question/answer separators, folio underscore. Never text | +| Bind Thread | #864737 | **The only chromatic ink**: binding rail, folio numbers, clause numbers. 6.11:1 | + +Bind Thread is a line and a numeral. It is never a fill behind text, never a heading +colour, and never a second accent's excuse. + +## Typography + +### Font Families + +| Role | Family | Usage | +| --- | --- | --- | +| Display | 思源宋体 Semibold, Source Serif 4 | Document title and section titles | +| Body | 霞鹜文楷, Source Serif 4 | Every paragraph, quote, and table cell | +| Mono | IBM Plex Mono | Clause numbers, folio tabs, dates, owners, deadlines | + +Fallbacks: display `Noto Sans SC` 600, body `Noto Sans SC` 400, mono `Inter` 400. 霞鹜文楷 +is the body face because long CJK paragraphs are what this deck is made of, and it holds +readability at paragraph length better than a screen sans. Write the Latin face **first** +in every fallback chain — matching is per-character, so Han falls through to the CJK face +automatically, whereas the reverse order lets the CJK face's own Latin swallow every +digit and page number. + +### Type Scale + +| Level | Size | Font | Line Height | Usage | +| --- | --- | --- | --- | --- | +| Document Title | 76 | Display | — | Cover page only, once in the whole deck. 76 rather than 72 because the largest size must be >= 2.5x the 30px body | +| Section Title | 44 | Display | — | Every interior page | +| Body | 30 | Body | 1.8 | Paragraphs, quotes, question/answer text. **The floor** | +| Table Body | 28 | Body | — | Inside ruled tables only | +| Clause Number | 26 | Mono | — | §2.1 markers in the hanging indent | +| Folio | 26 | Mono | — | Page tab and document code. Folded up from 24 so the cover runs 76/44/30/26 — four steps, not five | + +Section title is **44 — the smallest interior title in the system**, and that is the +point: it is a document's section heading, not a slide's page title. It announces where +you are in the file; it does not perform. + +### Line Height + +Body is **1.8**, and it goes *up* rather than down as density rises. This is the +counter-intuitive rule of the deck: once line length grows and paragraph count grows, +leading is the only variable still holding readability, so it is the last thing to be +spent. Titles and mono runs set solid enough to keep the page compact; the paragraph is +where the air goes. + +### Letter Spacing + +The document code in the top-left folio position sets uppercase at **+2** tracking. Body, +titles, and table text take no tracking adjustment — a memo's job is to be transparent. + +## Layout Grammar + +Margins are **96 top, 96 bottom, 216 left, 120 right**. The left margin is not a taste +decision: it is the space the binding takes. Body copy starts 96px to the right of the +binding rail, and the clause numbers hang in the gap to its left. That asymmetry is the +strongest identity signal in the deck, and it is also what immunises it against the +"every page anchored at the same spot" tell. + +The content band is **10 columns × 144 with 9 gutters × 16 = 1584 wide** — the gutter is 16 here rather than 24 because that is the only value that divides this band into whole columns. Running text +narrows further, to **eight of those ten columns**, purely to hold line length — the +content band is the table's width, not the paragraph's. + +Corner radius is **0** everywhere. Zero shadow, zero gradient. Nothing in this deck is a +card, so nothing needs a corner. + +## Signature Motifs + +**1. Binding rail (`binding-rail`).** A 2px Bind Thread vertical line at **x = 120** from +the left page edge, running the full page height. Three 12×12 squares filled in Linen and +stroked 1px Hairline sit on the line at equal spacing — the binding holes. The rail +repeats on every page at exactly the same x, with zero drift. + +**2. Numbered clause (`numbered-clause`).** The clause number is set in mono at 26 and +hangs in the margin **to the left of the rail**, right-aligned to rail − 24. The clause +body starts at rail + 96 and runs as ordinary prose at 30/1.8. There are **no bullet +glyphs anywhere in the deck** — depth is carried entirely by the number (§2 / §2.1 / +§2.1.1), and it stops at three levels. + +**3. Folio tab (`folio-tab`).** Top-right: mono 24 reading `p. 3 / 8`, with a 48 × 1px +Hairline rule directly beneath it. Top-left, at the mirrored position: the document code +in mono, uppercase, +2 tracking. Together they tell the reader this is sheet N of a file, +which is the whole premise of the deck. + +## Page Inventory (8 pages) + +| # | Page | Structure | +| --- | --- | --- | +| 01 | Cover sheet | Document code + title 76 + one abstract paragraph at 30/1.8 (≤5 lines) + three mono lines: recipient, date, classification | +| 02 | Background | Section title 44 + clauses §1.1–§1.3, each ≤6 lines | +| 03 | Current data | Section title 44 + a ruled table of 6-8 rows + a source line. **Data appears as a table; never as a chart** | +| 04 | Analysis | Section title 44 + clauses §3.1–§3.2 + one Linen Deep quote block | +| 05 | Options | Section title 44 + a comparison table (rows = criteria, columns = options); the final row is the recommendation, set bold, with a 2px Bind Thread rule above it | +| 06 | Retrospective narrative | Section title 44 + one past-tense "assume this succeeded — here is what it looked like" passage at 30/1.8 (≤8 lines) + a single attributed line, indented italic 30 | +| 07 | Open questions | Section title 44 + 4-5 question/answer pairs (question mono bold 30; answer 30/1.8, ≤3 lines) separated by 1px Hairline | +| 08 | Resolutions | Section title 44 + a Linen Deep decision block holding 3 items, each one sentence at 30 plus a mono owner and a mono deadline + a sign-off slot | + +## Strictly Avoid + +1. **No bullet glyph, anywhere in the deck.** Numbered clauses, whole paragraphs, ruled + tables. That is the complete inventory of list forms. +2. **No charts.** A chart in this deck is a presentation artefact, and this is not a + presentation. Data goes into a table. +3. **No second chromatic ink.** Outside the rust thread, everything is neutral. +4. **No radius, shadow, gradient, icon, photograph, or card.** +5. **No rail drift.** The binding rail and the body start sit on the same x on every + single page. +6. **Clause depth stops at three.** A fourth level means the section should be split into + its own page or merged upward. +7. **Density never touches the type floor.** Body 30 is absolute. If it does not fit, + remove a clause or add a page — shrinking the body is the failure mode this deck + exists to demonstrate you do not need. +8. **Folio must be a fraction.** `p. 3 / 8`, never `p. 3`. A one-sided page number tells + an asynchronous reader nothing. +9. **No order-dependent phrasing.** "As shown on the previous page" is banned; every page + must survive being pulled out of the folder alone. +10. **Tables use horizontal rules and zebra bands only.** No coloured header row, no full + box border, no vertical rules. + +## Anti-Patterns + +- **Small type standing in for editing.** The moment a page is set at 28 to make it fit, + the deck has lost the argument it was built to make. Density is a structural result, + not a font-size result. +- **The rust thread as a highlight colour.** Rust behind a heading, rust as an emphasis + fill, rust on a keyword — each turns the binding into decoration and immediately reads + as a template. +- **A card wrapper around a clause.** A filled, rounded, shadowed container around body + copy converts a memo into a slide. Linen Deep blocks exist for quotes and decisions + only, and they carry no radius. +- **A symmetric left margin.** Setting 120/120 destroys the hanging indent, strands the + clause numbers, and removes the one thing that makes these pages look like a bound file. +- **Chart-shaped tables.** Coloured header bands, alternating column fills, and boxed + cells are a chart wearing a table's clothes; keep to rules and bands. diff --git a/crates/op-ai-skills/skills/style-guides/gridpaper-graphite.md b/crates/op-ai-skills/skills/style-guides/gridpaper-graphite.md new file mode 100644 index 000000000..ab79191e9 --- /dev/null +++ b/crates/op-ai-skills/skills/style-guides/gridpaper-graphite.md @@ -0,0 +1,184 @@ +--- +name: 'gridpaper-graphite' +tags: [light-mode, education, data-focused, cjk-type, monospace, dual-font, swiss, minimal, sharp-corners, austere, crisp, blue-accent] +platform: slides +--- + +## Style Scope + +This guide is self-contained and written for the **academic defence deck** — a presented slide sequence whose job is to let an audience check an argument step by step. Every page carries one claim, one exhibit, and one so-what. It is not a pitch deck, not a keynote, not a report laid out sideways. Apply its palette, grid, and motifs only when this guide is selected; do not borrow dashboard, landing-page, or marketing-deck patterns into it. Treat unnamed layout frames as structural by default: no fill, stroke, cornerRadius, or shadow unless the node is deliberately an index tab, a note panel, or a table band — hierarchy here comes from the lattice and from type size, never from a wrapper shell. General deck law — overflow splits pages instead of shrinking type, the density-slot budget, the locked/variable split, the narrative arc, the ghost-deck test, the AI-slop checklist — lives in the deck contract and is **not** restated here. + +Formality is high, density is medium-high, ground is light. + +## Style Summary + +The anchor is a **lab notebook**: an almost-invisible printed grid, the cool grey of a 2H pencil, and three coloured index tabs stuck along the page edge. What is being anchored is not a discipline — no beakers, no molecular diagrams, no gears — it is the single idea that **the process was written down, therefore it can be re-checked**. + +Three arguments fix the system: + +**One — the grid line sits at L0.925, only 0.05 below the L0.975 ground.** The grid's function is to make the audience believe the elements are aligned. The moment it can be actively read it stops doing that job and becomes decorative noise. It must be there and lose every competition. + +**Two — the three index colours are semantic slots, not a colour scheme.** Blue means *definition / method*. Green means *example / result*. Amber means *limitation / open question*. A slide's colour is decided by what the slide is claiming, never by what looks good next to the previous slide. Amber is pushed down to L0.585 to reach **3.97** contrast, because these labels routinely appear at 20–24px and have to hold at that size. + +**Three — body text is a chroma-carrying cool grey, not pure black.** `#1F2329` at chroma 0.012, hue 250, so it reads as graphite catching light rather than ink. That single move is the whole distance between this style and a white-background black-text conference template — and it is enough. + +Key aesthetics: + +- **Paper ground with a printed grid**: `#F4F8F8`, grid lines `#E0E8E9` on a 48px square lattice +- **Everything lands on the 48 grid**: alignment here is meant to be *visible* — that is where the credibility comes from +- **Zero corner radius everywhere**: nothing is rounded, nothing is soft +- **Graphite, not black**: a cool neutral ramp at hue 250 against a hue-200 ground +- **Three index colours as fixed semantics**: blue = definition, green = result, amber = limitation +- **Every exhibit is paired with its so-what**: an unpaired figure does not get on a page +- **Every page with data, a figure, or someone else's conclusion carries a citation gutter**: an empty gutter is a defect +- **Numerals and tabs replace bullets**: there is no bullet glyph in this system + +## Color System + +### Core Backgrounds + +| Token | Value | Usage | +| ---------- | ------- | -------------------------------------------------------------- | +| grid.paper | #F4F8F8 | Page ground. oklch 0.975 0.004 200 — the notebook sheet | +| grid.line | #E0E8E9 | The printed grid, 48px pitch. oklch 0.925 0.008 210 | +| panel.note | #E8EEEF | Exhibit ground and pull-quote blocks. Graphite on it reads 13.46 | + +Three steps and no more. `panel.note` is the only raised surface in the system; a panel on a panel does not exist here, because a notebook has one sheet. + +### Border Colors + +| Token | Value | Usage | +| --------- | ------- | ----------------------------------------------------------- | +| rule.hair | #CBD0D1 | Dividers, table row rules, the citation-gutter hairline | + +One border colour, drawn at 1px. The only 2px rule in the system is the `index.blue` line under a table header or above a so-what line — and it is an accent, not a border. + +### Text Colors + +| Token | Value | Usage | +| -------------- | ------- | --------------------------------------------------------- | +| graphite | #1F2329 | Claim titles and body copy. 14.75:1 on the ground | +| graphite.soft | #575B60 | Secondary copy, exhibit sub-labels. 6.39:1 | +| graphite.faint | #797E83 | Citations and page numbers. 3.83:1 — **≥24px only** | + +All three sit at hue 250 with chroma 0.010–0.012 against a hue-200 ground. The neutrals lean colder than the paper on purpose: that is the pencil reading against the sheet. + +`graphite.faint` is below 4.5:1 and is therefore restricted by size, not by taste. It may carry citation lines and page numbers at 24px and nothing else. It never carries a sentence a reader has to parse. + +### Index Colors + +| Token | Value | Usage | +| ----------- | ------- | ----------------------------------------------------------------------- | +| index.blue | #1D6294 | Definition / method. Also the deck's single accent. 6.08:1 both ways | +| index.green | #33724C | Example / result. 5.37:1 | +| index.amber | #A17221 | Limitation / open question. 3.97:1 — labels at 20–24px | + +`index.blue` is the only colour that also works as a general accent: the cover rule, the 2px table-header underline, the so-what top line. It is reversible — `grid.paper` type on an `index.blue` fill is the same 6.08:1 as the reverse, which is why the tab tongue can be filled solid and still carry a label. + +Green and amber are **never** general accents. They appear only where their semantic slot applies: green on a results block, amber on a limitations block. A green heading on a methods page is a bug, not a variation. + +## Typography + +### Font Families + +| Role | Family | Fallback | Usage | +| --------------- | -------------------------------------------- | ---------------- | ------------------------------------------------ | +| Display | 思源黑体 Bold, Geist | Noto Sans SC 700 | Cover title, claim titles, exhibit titles | +| Body | 霞鹜新晰黑, Geist | Noto Sans SC 400 | Body copy, captions, sub-labels | +| Data / Citation | IBM Plex Mono | Inter 400 | Formulas, figures, step numerals, citation lines | +| Quotation | Source Serif 4 | — | Latin pull quotes only | + +The Latin face is written **first** in every fallback chain. Font matching is per-character: Latin faces carry no CJK codepoints, so Han characters fall through to the Chinese face automatically. Written the other way round, the Chinese face's own Latin swallows every digit and the paired Latin face never appears — which matters more here than in most styles, because this deck is full of numerals. + +### Type Scale + +| Level | Size | Font | Line Height | Usage | +| -------------- | ---- | ------- | ----------- | ------------------------------------------------ | +| Cover Title | 84px | Display | — | Cover only, once per deck | +| Claim Title | 60px | Display | — | The page title, written as a claim. **≤2 lines** | +| Exhibit Title | 36px | Display | — | Exhibit headings, numbered step headings | +| Body | 30px | Body | **1.7** | **Default body copy — and the floor** | +| Caption | 26px | Body | — | Figure captions, table notes | +| Citation | 24px | Mono | — | Citation-gutter lines, in-text superscripts | +| Page Number | 24px | Mono | — | Page numbers | + +The page title is a **claim sentence**, not a topic label. "Method" is a topic; "A two-stage sampler removes the bias without extra passes" is a claim. Titles that are topics defeat the entire deck. + +Body at 30px is lower than most decks go, and it is deliberate: this style has to fit citations on the page. **30 is the floor, not the target.** If content does not fit at 30, delete content — do not go to 28. + +Four roles, and each has one job. Display carries titles. Body carries prose. Mono carries anything that is a number, a step index, or a reference — that is what makes the citation gutter read as apparatus rather than as more text. Source Serif 4 appears for Latin pull quotes and nowhere else. + +### Line Height + +Body runs at **1.7**. Han glyphs fill the em box top to bottom, so Chinese needs roughly 0.2 more leading than the Latin equivalent; at 1.5 the citation-dense body of this deck reads as a solid grey block. + +## Spacing System + +### Layout Grid + +- Margins: **top 96, bottom 112, left 112, right 128**. The bottom is heavier because the citation gutter lives there; the sides are deliberately unequal so the 1680 content width divides by the 48 lattice exactly (48 × 35), and a page that is not centred between equal margins cannot read as the centred-and-equidistant AI default +- Grid: **48px square**. Every element origin and every element edge lands on a multiple of 48 +- Columns: **12 × 118 with 11 gutters of 24 = 1680** content width +- Base gap: **24** + +The alignment in this style is meant to be seen. "Close enough" is not a tolerance — an element off the 48 lattice is a defect on the same footing as a typo, because the visible regularity of the grid is what makes the argument look checkable. + +The base gap of 24 is exactly half the 48 lattice pitch and exactly the column gutter, so stacks built from 24 and 48 resolve back onto the lattice on their own. Anything that needs a gap the lattice cannot express is the wrong structure, not a spacing problem. + +The one sanctioned exception is the index tab, which bleeds 24px *outside* the left margin. That break is the point of the motif (see below). + +## Corner Radius + +| Value | Usage | +| ----- | ---------- | +| 0px | Everything | + +Panels, tabs, tables, exhibit frames, the cover rule: all square. The tab tongue's one non-square edge is an **8px bevel**, a cut corner — not a radius. Nothing in this deck is a capsule, a pill, or a rounded card. + +## Signature Motifs + +**1. Index tab tongue — `index-tab`.** A filled rectangle pinned to a content block's **top-left corner and bled 24px to the left of it**. Height **32**. The left end is cut back by an **8px bevel** (a chamfer, never a radius). Fill is one of the three index colours; inside sits a **20px uppercase label** in `grid.paper`, and the tongue's width hugs that label. Semantics are fixed and non-negotiable: `index.blue` = definition / method, `index.green` = result / example, `index.amber` = limitation / open question. + +The leftward bleed is the whole trick. It makes the tab read as something *stuck on* rather than *laid out into*, and it deliberately breaks the otherwise uniform left margin — which is exactly why it reads as a physical tab and not as a coloured heading. + +**2. Exhibit couplet — `exhibit-couplet`.** An exhibit and its so-what are one unit, never separable. Directly beneath the exhibit runs a **2px `index.blue` top line**; beneath that line sits a **30px conclusion sentence** — not a figure caption, but a statement of *what this exhibit changes*. + +Two rules govern it, and they cut both ways. An exhibit with no so-what does not get on a page. And if the sentence still stands with the exhibit covered up, the exhibit is redundant — delete the figure, keep the sentence. + +**3. Citation gutter — `citation-gutter`.** At the page bottom: a **1px `rule.hair`** hairline, and below it a **40px band** carrying a **mono 24px** citation in the form `[3] Author, Year, Journal`. In-body references are superscript numerals pointing into it. + +Any page carrying data, a figure, or somebody else's conclusion **must** have a filled gutter. An empty gutter on such a page is a defect, and "Source: internal" is not a citation. + +## Page Plan + +Eight pages, deliberately. Fifteen would force a section-navigation layer, and this deck is sized to never need one. + +| # | Page | Structure | +| -- | ------------------------ | ----------------------------------------------------------------------------------------------------- | +| 01 | Cover | Title 84 + subtitle 30 + author / institution / date in mono + one `index.blue` rule, 48 wide | +| 02 | Problem and gap | Claim title + three numbered parts (state of the art / gap / this work) at 36 + 30, plus gutter | +| 03 | Method | Claim title + `index.blue` tab on the method block + 3–4 steps (mono numeral + 36 + 30), plus gutter | +| 04 | Result, figure and read | Claim title + **exhibit in the left 7 columns, reading in the right 5** + couplet + gutter | +| 05 | Result, table | Claim title + table (2px `index.blue` header underline; 1px `rule.hair` rows; no rule on the last row) | +| 06 | Limitations | Claim title + `index.amber` tab on three limitations (36 + 30), plus gutter | +| 07 | Conclusions | Title reads **"Conclusions", never "Thank you"** — three one-sentence conclusions at 36 + one 30 line | +| 08 | References | Full-page mono 24 reference list of 8–12 entries + contact line in the footer | + +Evidence precedes interpretation on page 04 because the eye runs left to right; flipping the columns tells the audience the conclusion was decided before the data. + +## Icons + +This style has no icon layer. Hierarchy is carried by mono numerals, index tabs, and rules. Where another style would place a glyph, place a numbered step or a tab. + +## Anti-Patterns + +- **The closing page is never "Thank You" and never blank.** It stays on screen for the entire Q&A; that time belongs to the conclusions. +- **No exhibit without a so-what**, and no exhibit that survives its own removal. +- **No gradients, no shadows, no rounded corners, no decorative icons, no photographs, no script or novelty faces.** +- **Index colours are not a palette.** Never as chart series colours, never as a large background fill, never as text colour — the label inside a tab tongue is the only exception. +- **Never off the 48 lattice.** "Looks about right" is not accepted here. +- **Never below 30px body.** Cut content instead. +- **No bullet glyphs.** Levels come from mono numerals and tab tongues. +- **Charts for trend, comparison, and distribution; tables only for exact value comparison** — and never both on one page. +- **No unverifiable sourcing.** The citation gutter cannot be omitted and cannot be filled with "Source: internal data". +- **No section-navigation layer.** Past 15 pages a deck needs one; this style stays at 8 precisely so it never does. diff --git a/crates/op-ai-skills/skills/style-guides/ledger-tick.md b/crates/op-ai-skills/skills/style-guides/ledger-tick.md new file mode 100644 index 000000000..0e30d1e76 --- /dev/null +++ b/crates/op-ai-skills/skills/style-guides/ledger-tick.md @@ -0,0 +1,186 @@ +--- +name: 'ledger-tick' +tags: [light-mode, warm-tones, data-focused, corporate, enterprise, monospace, cjk-type, crisp, flat, sharp-corners, stroke-based] +platform: slides +--- + +## Style Scope + +This guide is self-contained and written for **one deck genre only: the competitive matrix** — an evaluation deck whose job is to lay out "what we can do and what they can do" as a ledger a reader can check cell by cell. Apply its palette, rules, and mark language only when this exact guide is selected; do not borrow dashboard, keynote, editorial, or pitch-deck patterns into it. Treat unnamed layout frames as structural by default: no fill, stroke, radius, or shadow unless the node is deliberately the paper ground, a zebra band, a ledger rule, the own-column wash, or one of the three tick marks. Hierarchy comes from the rules and the marks, not from card shells. + +## Style Summary + +The anchor is a **hand-kept account book**: warm paper, blue printed grid lines, red header and totals lines, a mark drawn into each cell, a double rule under the tally. What is anchored is not the look of the book — it is the *act of checking a row against a column*. Every decision below serves that act. + +Three arguments define the tier and none of them are optional: + +1. **"Not met" is an empty space, not a cross.** Unmet is a short pale dash. A red X is a value judgement about a competitor and turns the whole deck into an attack pitch instead of an assessment. A blank is a statement of fact. This also sidesteps red/green scoring, a known credibility killer. +2. **Only the own column is coloured.** Every competitor column is neutral, at identical column width, row height, and type size. Colour is a position, and a position gets stated exactly once. +3. **The two rule colours sit at 3.13 contrast on purpose.** They are *lines, not text*. Push their contrast up and they start competing with the marks for attention. A contrast checker must carry them on its exemption list; they never carry a glyph. + +Key aesthetics: + +- **Ledger paper ground**: `#F5F2E6` at oklch L0.960 C0.016 H92 — warm account-book stock, never a white slide +- **Two rule hues, blue and red**: blue rules the grid, red rules the header and the tally; neither ever sets type +- **Three marks that differ by shape**: a check, a half-filled square, a short dash — distinguishable with colour removed +- **One wash, one place**: `#DFF3E2` appears on the own column and nowhere else in the whole deck +- **Zebra bands, no boxes**: rows separate by 1px blue rules and alternating bands; there is no table border box +- **Mono for every figure**: all numerals and every mark column are forced to IBM Plex Mono +- **Radius 0 everywhere**: a ruled ledger has no rounded corners, no shadow, and no gradient +- **The tally line is a sentence**: the double rule carries a written verdict, never a weighted score + +## Color System + +### Ground + +| Token | Value | Usage | +| ------------- | ------- | ------------------------------------------------------------------ | +| ledger.paper | #F5F2E6 | Page ground on every slide; also the ink colour inside a green chip | +| ledger.band | #EBE6D8 | Zebra row band, and the four segments of a quartile bar (13.16:1) | + +### Ink + +| Token | Value | Usage | +| ---------- | ------- | -------------------------------------------------- | +| ink.entry | #221F1B | Primary text: titles, row labels, verdicts (14.63:1) | +| ink.soft | #5A5652 | Secondary text: definitions, current-state lines (6.48:1) | +| ink.faint | #78746F | Footnotes, sources, page numbers, dates (4.14:1) | + +### Rules — lines only, never type + +| Token | Value | Usage | +| ---------- | ------- | ------------------------------------------------------------- | +| rule.blue | #88A2B9 | The ledger grid: 1px cell rules and row separators (3.13:1) | +| rule.red | #BD7670 | The 2px header underline and the tally double rule (3.13:1) | + +Both are deliberately low-contrast and are exempt from text-contrast checks **because no glyph is ever painted in them**. Setting a label, a number, or a caption in `rule.blue` or `rule.red` is the single fastest way to break this style. + +### Marks + +| Token | Value | Usage | +| ---------- | ------- | -------------------------------------------------------------- | +| tick.green | #2F7442 | Met: the check mark, the own-column frame and chip (5.06:1; 4.88:1 on own.wash) | +| tick.grey | #888682 | Partially met: the half-filled square (3.24:1 — a mark, not text) | +| tick.pale | #C6C4C1 | Not met: the short dash standing in for an empty cell | + +### Own column + +| Token | Value | Usage | +| --------- | ------- | --------------------------------------------------------- | +| own.wash | #DFF3E2 | Full-height fill behind the own column only (14.11:1 against ink.entry — the table text stays readable inside it) | + +`own.wash` is capped at one appearance per deck. A second washed column means two positions are being argued, and the matrix stops being an assessment. + +## Typography + +### Font Families + +| Role | Family | Usage | +| --------------- | ---------------------------------------- | ------------------------------------------------------ | +| Display | 思源黑体 Bold / Familjen Grotesk | Cover title, page titles | +| Body | 霞鹜新晰黑 / Familjen Grotesk | Row labels, definitions, notes, verdict sentences | +| Data / Marks | **IBM Plex Mono** | **Mandatory** for every figure and every mark column | + +Fallback chain: display `Noto Sans SC` 700 / body `Noto Sans SC` 400 / mono `Inter` 500. + +The mono rule is not decoration. Column headers, dates, page numbers, and every cell in a mark column are set in IBM Plex Mono so glyph advances are uniform and marks land on the same optical centre down a column. A proportional face makes an eight-row column visibly ragged, and a ragged column cannot be checked at a glance. + +### Type Scale + +| Level | Size | Font | Usage | +| -------------- | ---- | ------- | ------------------------------------------------------- | +| Cover title | 88 | Display | Cover slide only | +| Page title | 56 | Display | Every interior slide | +| Verdict | 44 | Display | The single judgement sentence on the conclusion slide | +| Mark glyph | 32 | Mono | The tri-tick cell mark, and the tally conclusion line | +| Item title | 32 | Body | One gap / one advantage / one criterion heading | +| Cover subtitle | 30 | Body | Cover subtitle only | +| Table header | 28 | Mono | Uppercase, letterSpacing **+2** on Latin, **0 on CJK** | +| Row label | 28 | Body | The left-hand criterion label in the matrix | +| Body note | 28 | Body | Definitions, current state, plan, reading notes | +| Footnote | 24 | Body | Data cut-off date, sources, page numbers | +| Chip label | 20 | Mono | Uppercase text inside the own-column chip, in ledger.paper | + +Table headers are the only uppercase run in the system, and +2 tracking is what makes an uppercase mono header read as a ledger column head rather than as shouting. Tracking follows the run's actual script: a Latin header takes +2, a Chinese one takes 0, because positive tracking on Han glyphs loosens an already full-width em box into gaps. + +## Layout Grammar + +Margins: **top 88 / bottom 104 / left and right 96**. Base gap: **16**. + +The matrix grid: + +- **Row-label column fixed at 360px.** Every remaining column splits the leftover width equally — competitor and own columns are always the same width. +- **Row height 72.** Uniform, including the header row. The mark sits centred horizontally and vertically in its cell. +- **6–8 rows, at most 5 columns** (the row-label column counts toward the 5; one of the remaining columns is the own column). Over the cap, split into two slides — never shrink the row height. +- On the 1920-wide slide frame the content band is 1728 wide; after the 360 label column, 1368 is shared equally by the comparison columns. + +The rules: + +- Cell / row rules: **1px `rule.blue`** +- Header underline: **2px `rule.red`**, directly under the header row +- Tally: a **1px + 2px double rule** in `rule.red`, **4px apart**, above the last line of the table +- No outer table border, no vertical box, no coloured header fill + +## Corner Radius + +| Value | Usage | +| ----- | ------------------------------------------------------------------ | +| 0px | Everything: chips, bands, quartile segments, the own-column frame | + +There is no second row in this table on purpose. A ledger is ruled, not rounded; a radius anywhere reads as a UI card dropped onto an account page. + +## Signature Motifs + +### 1. `tri-tick` — the three-state cell mark + +- **Met**: a **24px** check in `tick.green` — two straight segments, **2.5px** stroke, square ends. It is a drawn polyline, not a glyph in a circle and not a dot. +- **Partially met**: a **24×24** square in `tick.grey` with its **left half filled** — a half cell, literally. +- **Not met**: a **20×2px** dash in `tick.pale`, centred in the cell. + +The three states differ *by shape*, so the matrix survives greyscale printing and colour-blind readers. That is the accessibility floor and also what separates this from a red/green score sheet. + +### 2. `own-column` — the one coloured column + +- The full column is filled with `own.wash`. +- A **2px `tick.green`** frame runs around it, from the top of the header row down to the tally rule. +- The column head carries one solid `tick.green` chip: **height 32, radius 0**, containing **20px uppercase `ledger.paper`** text (the product name). +- Column width, row height, and type size are identical to every competitor column. Only the wash and the frame differ. + +### 3. `tally-rule` — the double rule and its sentence + +- Above the closing line: **1px + 2px `rule.red`**, 4px apart. +- Below it sits a single **32px `ink.entry`** sentence, left-aligned, spanning the full table width. +- It is a conclusion, not an arithmetic total. The ledger's tally convention is borrowed to carry the "so what", which is the one thing a matrix otherwise leaves unsaid. + +## Page Types + +| # | Page | Density | Slot cap | Structure | +| -- | ---------------- | ----------- | -------- | ------------------------------------------------------------------------------------------------- | +| 01 | Cover | low | 5 | Title 88 + subtitle 30 + one line naming the evaluation scope + date in mono | +| 02 | Criteria | medium | 8 | Page title 56 + 4–5 criterion definitions (title 32 + note 28) + data cut-off footnote | +| 03 | Main matrix | **high** | **16** | Page title 56 + matrix (6–8 rows × up to 5 columns, own column included) + one legend row + tally double rule + conclusion line | +| 04 | Quartile scale | medium-high | 9 | Page title 56 + 3 quartile bars (four equal `ledger.band` segments + 1px separators + a 6px `tick.green` position marker) + one reading note each | +| 05 | Gaps | medium-high | 9 | Page title 56 + 3 gaps (`tick.grey` half-cell + title 32 + current 28 + plan 28), separated by 1px `rule.blue` | +| 06 | Advantages | medium-high | 9 | Page title 56 + 3 advantages (same structure, but the mark is the `tick.green` check) + one verifiable proof line each | +| 07 | Conclusion | medium | 6 | Page title 56 + one verdict sentence 44 + 3 imperative actions + data-source footnote | + +## Strictly Avoid + +1. **No red cross, and no red/green scoring.** Unmet is the pale dash. +2. No competitor logos, no competitor product screenshots, no visual that can be read as disparagement. +3. Only the own column is coloured. Competitor columns stay neutral, at identical width, row height, and size. +4. `rule.blue` and `rule.red` never carry text. +5. No rounded corners, no shadow, no gradient. +6. The three marks must be distinguishable by shape. Never render them as three dots in three colours. +7. One mark per cell, and nothing else — no explanatory text inside a cell. Explanations belong on slides 05 and 06. +8. Every criterion must be given a checkable definition on slide 02. An undefined criterion does not go into the matrix. +9. Matrix caps: 8 rows, 5 columns. Over the cap, split into two slides — **never reduce the row height**. +10. The tally line is a sentence, not a score. No weighted totals — a single number hides every trade-off the matrix exists to expose. + +## Anti-Patterns + +- **Scoring instead of recording.** The moment a column of numbers is summed, the deck stops being checkable and becomes a claim. Cells record state; the sentence under the tally carries the argument. +- **Decorating the competitor columns.** Tinting them, greying them out, or narrowing them all say the same thing — that the comparison was arranged rather than made. +- **Raising the rule contrast "for legibility".** The grid is meant to sit beneath the marks. If the blue lines are noticeable before the green checks are, the page has inverted. +- **A second washed column, or a green accent anywhere off the own column.** `tick.green` is a mark colour and the own-column frame; it is not a general highlight. +- **A tick glyph borrowed from an icon set.** The check is drawn as a two-segment 2.5px polyline at 24px so it matches the half-cell and dash in weight; an icon-font check arrives at a different optical weight and breaks the three-way shape reading. diff --git a/crates/op-ai-skills/skills/style-guides/sounding-navy.md b/crates/op-ai-skills/skills/style-guides/sounding-navy.md new file mode 100644 index 000000000..7ea0fda9b --- /dev/null +++ b/crates/op-ai-skills/skills/style-guides/sounding-navy.md @@ -0,0 +1,182 @@ +--- +name: 'sounding-navy' +tags: [light-mode, high-contrast, corporate, enterprise, data-focused, cjk-type, monospace, blue-accent, sharp-corners, flat, stroke-based, austere] +platform: slides +--- + +## Style Scope + +This guide is self-contained and written for **Chinese consulting / strategy decks** — high formality, medium density, a **mixed** ground structure where dark pages bookend a light body. Apply its palette, type pairing, layout grammar, and motifs only when this exact guide is selected; do not borrow dashboard, landing-page, or social-card patterns into it. The generic deck laws (overflow splits into a new page rather than shrinking type, the density-slot table, the locked-vs-free split, narrative arc, the ghost-deck test, the single-accent rule) live in the deck contract and are **not** restated here. What follows is only what makes this deck *this* deck. + +Treat unnamed layout frames as structural by default: no fill, no stroke, no cornerRadius, no shadow. Separation comes from a 1px rule and from whitespace, never from a container shell. + +## Style Summary + +The anchor is a **nautical sounding chart** — the warm off-white of chart paper, the steel blue of depth contours, the ink navy of deep water, superscript numerals at every sounding point, and an ochre warning tint over the shallows. What is being anchored is not boats or anchors or compasses; it is one fact: **every number printed on a chart is a number somebody went down and measured.** That is exactly what a strategy deck has to transmit. A conclusion here is not an opinion, it is a reading. + +Three constraints define the surface. **The dark ground is not black** — it is `#091E32`, navy carrying real chroma, so the steel accent on top of it reads as "the deep end of the same family" rather than "a blue thing stuck on black". **The light ground runs warm and the dark ground runs cold** — paper is warm, water is cold, and that split gives the mixed structure a genuine difference of material instead of two brightness steps of one colour. **The ochre is dark enough to be read** — it sits at 4.00:1 on paper, because a warning colour that cannot be read is a contradiction in terms. + +Key aesthetics: + +- **Chart paper, not white**: `#F7F4EE`, an anti-glare warm off-white; every body page sits on it +- **Navy with chroma as the dark ground**: cover, section, and closing pages only — never mid-deck +- **Zero corner radius, everywhere**: a sounding chart has no rounded corners +- **Three stroke weights only**: hair 1 / rule 2 / heavy 3 — there is no fourth +- **Numerals are a separate voice**: every figure is set in a mono face, 48-72px, and never in the body face +- **Conclusion-shaped titles**: a full sentence that states the finding, never a topic label +- **One warning per deck**: `#A46D1A` appears exactly once across all pages +- **Depth reads downward**: the signature chart hangs from a baseline instead of floating on one +- **No source, no number**: any page carrying a figure carries its provenance line + +## Color System + +### Grounds + +| Token | Value | Usage | +| --- | --- | --- | +| chart.paper | #F7F4EE | Body-page ground. Every content page sits on this | +| chart.paper.deep | #EBE7DF | Zone surface / table zebra rows. Two authorised uses in the whole deck | +| deep.navy | #091E32 | Cover, section, and closing page ground | +| deep.navy.low | #031223 | A deeper block placed on a dark page | + +The two grounds are deliberately on opposite thermal sides — paper carries a warm cast, navy a cold one. They are two materials, not two levels of one colour, and that is what lets the mixed structure work without reading as a theme toggle. + +### Text Colors + +| Token | Value | Usage | +| --- | --- | --- | +| ink.sounding | #1D2228 | Primary text on light pages. 14.58:1 on paper | +| ink.soft | #565B61 | Secondary text on light pages. 6.24:1 | +| ink.faint | #7C8186 | Footnotes, sources, page numbers. 3.58:1 — **legal at 24px and above only** | +| paper.on.navy | #F0F4F7 | Primary text on a dark page. 15.27:1 on navy | +| paper.dim.on.navy | #B3B8BE | Secondary text on a dark page. 8.46:1 | + +`ink.faint` is the one token in this palette with a size condition attached. It exists for the source line and the page number and nothing else; using it at body size is a contrast defect, not a stylistic choice. + +### Accent Colors + +| Token | Value | Usage | +| --- | --- | --- | +| contour.steel | #266EA4 | **Primary accent**: depth contours, the key series, conclusion rules. 4.97:1 on paper | +| contour.deep | #044A7D | The steel block that carries white text. 8.39:1 with white | +| contour.lift | #83B0D7 | The accent form used on dark pages. 7.36:1 on navy | +| shoal.ochre | #A46D1A | Second colour — risk and warning. **Once per deck, total.** 4.00:1 | + +Steel has three states and they are not interchangeable: `contour.steel` is for marks and type sitting on paper, `contour.deep` is for a filled block that carries white text, `contour.lift` is the only steel that reads on navy. Picking the wrong one is the fastest way to break this style — `contour.steel` on the navy ground barely separates from it. + +The two chromatic hues sit 173° apart, far past the 60° separation floor, which is why one warning mark next to a field of steel reads instantly. Spending the ochre twice destroys that: a warning that occurs twice is no longer a warning. + +### Rules + +| Token | Value | Usage | +| --- | --- | --- | +| rule.chart | #D3D1CD | Column rules and table rules. **Never text** | + +One rule colour, three weights, nothing else. If two regions need to be told apart, they are told apart by a `rule.chart` hairline and by space. + +## Typography + +### Font Families + +| Role | Family | Usage | +| --- | --- | --- | +| Display | Noto Sans SC 700 | Cover title, page titles, closing line | +| Body | Noto Sans SC 400 | Lead-in sentences, body copy, table body, annotations | +| Numerals | Inter 500 | Every figure, readout, and index number | + +These are the **production floor** — the layer that renders today, and the layer every shipped page uses. The preferred layer, for when the faces are available, is 思源宋体 Semibold for display (a conclusion-shaped title wants the settled weight of a serif), 霞鹜新晰黑 (OFL) for body, and IBM Plex Mono for figures; the Latin companions are `Source Serif 4` and `Geist`. + +Fallback chains, Latin face written first in every one: + +- Display — `"Source Serif 4","思源宋体","Noto Serif SC",serif` +- Body — `"Geist","LXGW Neo XiHei","Noto Sans SC",sans-serif` +- Numerals — `"IBM Plex Mono",monospace` + +Latin faces carry no Han codepoints, so Chinese characters fall through to the CJK face per character automatically. Written the other way round, the CJK face's own mediocre Latin swallows every digit and the paired Latin face never appears. + +### Type Scale + +| Level | Size | Font | Usage | +| --- | --- | --- | --- | +| Cover title | 100-116px | Display | Cover only, 2 lines maximum | +| Closing line | 72px | Display | The closing page's settling sentence, 2 lines maximum | +| Page title | 64px | Display | The conclusion sentence on every body page, 2 lines maximum | +| Argument title | 40px | Display | The heading of a numbered argument | +| Lead-in | 36px | Body | The sentence under a page title; also annotation-column headings | +| Body | 32px | Body | Body copy, argument detail, action lines, readout conclusion | +| Table body | 30px | Body | Table cells; also annotation-column detail | +| Source | 24px | Body | Footnotes, sources, page numbers — **the only place 24px is permitted** | + +### Numerals + +Figures are set in the mono face at **48-72px** and are the second-loudest thing on any page after the title. They are never set in the body face, never set at body size, and never wrapped in a coloured pill. The readout numeral at the foot of a page is 48px; a page whose whole point is one number may take it to 72px. + +## Layout Grammar + +- **Margins**: top 96 / bottom 120 / left 120 / right 120. The bottom is deliberately larger than the top — in a projected room the lower edge of the screen is the part heads block. This is a physical constraint, not a taste. +- **Grid**: 12 columns × 118 + 11 gutters × 24 = **1680px content width**, which with the 120px side margins fills the stage exactly. +- **Corner radius**: **0**, on every node, without exception. There is no radius scale in this style. +- **Stroke weights**: hair **1** (table rules, profile connectors, hollow index dots at 1.5) / rule **2** (track line, table-header underline) / heavy **3** (the readout's top rule). Nothing takes a fourth weight. +- **Gap**: multiples of **24** only. +- **Asymmetry**: the two-column body page splits **7:3** — evidence left, judgement right — divided by a 1px `rule.chart` seam, with the narrow column filled `chart.paper.deep`. +- **Annotation column**: a fixed **480px** rail on the right of a chart page, carrying at most two heading + detail pairs. + +## Signature Motifs + +These three carry the deck's recognisability. Each is described so it can be built out of primitives. + +### 1. Depth profile `depth-profile` + +The original expression for "we started here, ended there, and here is what ate the difference". + +Draw a horizontal 2px `rule.chart` baseline across the plot area at the **top**. Every bar is a rectangle whose top edge sits on that baseline and which extends **downward** — the bars hang, they do not float. Under the lower end of each bar, place its value as a mono numeral. Fill by role: decrements `ink.faint` neutral grey, increments `contour.steel`, the opening and closing totals `contour.deep` solid. Decrements do NOT take the ochre: a warning colour that shows up three times on an evidence page is no longer a warning, and the deck's single ochre is spent on the trade-off page. Draw no connector between bar tops — the bars' own height difference already states the relationship, and a connector at that height runs straight through the value labels. + +*The divergence from the conventional floating waterfall is deliberate.* A floating bar reads as a **position**; a hanging bar reads as a **depth**, which runs in the same direction as loss and consumption. It also leaves the baseline as the single alignment anchor on the page. + +### 2. Track index `track-index` + +A section page opens with a 2px `rule.chart` line running the full content width near the top — the track. On it, place N dots of **12px** at equal intervals. The dot for the current section is filled `contour.steel` and carries the section's Chinese name **24px below it**; every other dot is hollow, 1.5px stroke, with no label at all. + +The track is redrawn on **every** section page with only the filled dot moving. That repetition is what turns it from an ornament into a cross-page progress anchor. + +### 3. Sounding readout `sounding-readout` + +Above the footer, draw a **3px** `contour.steel` rule spanning the content width. Directly under it sits one row: a mono numeral at 48px on the left, and one conclusion sentence at 32px in `ink.sounding` on the right. **At most one per page** — it is that page's takeaway landing point. + +It goes in the footer rather than in a callout box on purpose. A callout box competes with the body for visual weight; the footer position is, structurally, where "after you have read it" lives. + +## Page Inventory + +Seven page types. Density labels reference the contract's slot table; the numbers below are this style's assignments. + +| # | Page type | Density | Text slots | Structure | +| --- | --- | --- | --- | --- | +| 01 | Cover · dark | low | 4 | `deep.navy` ground; mono project code above the title, title 100-116 (≤2 lines), a 120×3 `contour.lift` bar, client + date on one mono line | +| 02 | Agenda track · dark | low | 6 | Section overview: the track index fully expanded, all 5 nodes labelled, no body copy | +| 03 | Conclusion · light | medium | 6 | Title 64 + lead-in 36 + three numbered arguments (title 40 + detail 32, ≤2 lines each) + readout | +| 04 | Evidence · light | medium | 6 | Title 64 + **depth profile** + the 480px annotation rail (2 pairs of 36 + 30) + source line | +| 05 | Data · light | medium-high | 10 | Title 64 + table (header underlined 2px `contour.steel`; body rows 1px `rule.chart`; **last row unruled**) + source line | +| 06 | Trade-off · light | medium | 6 | Title 64 + the 7:3 split, 1px seam, right column on `chart.paper.deep`; the deck's single `shoal.ochre` appearance happens here | +| 07 | Action · dark | low | 5 | `deep.navy` ground; closing line 72 (≤2 lines) + three imperative actions (mono index + 32px copy) + mono sign-off | + +## Strictly Avoid + +1. **No corner radius.** The moment a radius appears, this deck slides from "sounding chart" to "SaaS landing page". +2. **No shadow, no gradient, no glassmorphism, no simulated depth of any kind.** The only depth in this system is the semantic depth the profile chart expresses. +3. **No content in cards.** Regions are separated by a 1px rule and by whitespace, not by rounded containers. `chart.paper.deep` surfaces are permitted in exactly two places: table zebra rows and the narrow column of the trade-off page. +4. **`shoal.ochre` once per deck.** The second occurrence voids the first. +5. **No topic titles.** Never "Market Overview"; always a complete conclusion sentence — "Only one of the three segments is still expanding". And never draw a rule under a title. +6. **No legend, no gridlines, no y-axis.** Values are labelled directly on the shapes; every non-key series is neutral grey. +7. **Dark and light pages never alternate.** Dark is permitted on the first one or two pages and the last page — a bookend — and every page between them is light. +8. **Nothing that implies interactivity.** No button states, no tabs, no pills, no badges, no navigation bars. +9. **No unsourced figures.** Any page containing a number carries a source line. If the source cannot be written, delete the number. +10. **No third chromatic hue.** Everything outside steel and ochre runs on the neutral ramp. + +## Anti-Patterns + +- **Black as the dark ground.** Pure black behind white type is the factory default of every AI-generated deck. Chroma in the navy is what makes the steel accent belong to the ground instead of sitting on it. +- **Matching the two grounds' hue.** Making the paper cold so it matches the navy collapses the mixed structure into a light/dark toggle of one colour and throws away the paper-versus-water distinction that justifies having two grounds at all. +- **A lighter, prettier ochre.** The warning colour was pushed down specifically to buy readable contrast on paper. Lifting it for the sake of a nicer swatch produces a warning nobody can read, which is worse than no warning. +- **Floating the profile bars.** Once the bars float, the baseline stops being an anchor and the chart becomes a generic waterfall — the single most recognisable thing in the style, spent. +- **A second readout on one page.** Two takeaways per page means neither is the takeaway. +- **Figurative nautical props.** No ships, anchors, compasses, helms, waves, or lighthouses. The anchor of this style is a measurement practice, not a maritime theme; the first literal boat turns it into a travel brochure. diff --git a/crates/op-ai-skills/skills/style-guides/tidemark-slate.md b/crates/op-ai-skills/skills/style-guides/tidemark-slate.md new file mode 100644 index 000000000..d800216c0 --- /dev/null +++ b/crates/op-ai-skills/skills/style-guides/tidemark-slate.md @@ -0,0 +1,210 @@ +--- +name: 'tidemark-slate' +tags: [light-mode, data-focused, enterprise, corporate, monospace, cjk-type, blue-accent, clean, crisp, flat, dual-font] +platform: slides +--- + +## Style Scope + +This guide is self-contained and written for the **data-review deck**: medium-high formality × **high** density × light. It is the deck you present when a cycle has closed and the room needs to know what happened and where it sits relative to last cycle. Every device below exists to serve that one sentence — metrics carry a scale, statuses carry meaning, roadmaps run in lanes. + +Apply its palette, grid, and motifs only when this exact guide is selected; do not borrow dashboard, landing-page, or keynote patterns into it. Treat unnamed layout frames as structural by default: no fill, stroke, cornerRadius, or shadow unless the node is deliberately a tile, a status pill, or a lane bar — hierarchy comes from the grid and the type scale, not from wrapper shells. Read the general deck rules from the deck contract; they are not repeated here. This file covers only what is specific to tidemark-slate: its palette and the semantic sub-system inside it, its type pairing and scale, its page geometry, its three signature motifs, and the failure modes that destroy it. + +## Style Summary + +The anchor is a **tidemark stone** in a harbour basin — slate scoured by repeated rise and fall, tide marks cut into its face, three-colour channel buoys sitting off it. What is anchored is one idea: **a cut mark is where the water stood last time**. A review is not a recital of numbers; it is a report of position relative to the previous reading. Nothing figurative is anchored — no waves, no vessels, no lighthouses. + +The ground is a cold white slate at oklch L 0.965, chroma 0.004, hue 240. That coldness is a decision, not a default: **any warm ground on a data page makes the numbers read as though they were packaged by an atmosphere.** A near-neutral cold ground is the only ground that does not interfere with numeric judgement. + +Above it sits exactly one chromatic accent — tide blue at hue 235, chroma 0.115 — and a **separate semantic sub-system** of three buoy colours. The buoys are not part of the palette allocation. They do not compete for the "one primary, one secondary, one accent" budget; they are meaning, rendered as dots and pills, and nothing else. + +Key aesthetics: + +- **Cold slate ground**: `#F1F4F6`, hue 240 at chroma 0.004 — near-neutral by intent, never warm +- **One chromatic accent**: tide blue, for scale rules and the key chart series and nothing beyond +- **Semantics as a sub-system**: green / amber / red are status, never palette, never chart series +- **Every numeral is mono**: across tiles, tables, and axis labels, so columns align on their own +- **Scale over label**: distance to target is drawn as a position on a rule, not written as text +- **Shadow is a single hairline**, and only tiles are allowed to carry it +- **Radius 8, nowhere else**: the tile needs to detach from the ground without earning a border +- **Charts are one hue ramp**: no legend, no gridlines, no y-axis; values sit on the marks + +## Color System + +### Surfaces + +| Token | Value | Usage | +| ----------- | ------- | -------------------------------------------------------------- | +| slate.wash | #F1F4F6 | Page ground. oklch 0.965 0.004 240 | +| slate.panel | #E4E8EB | Tile and card surface. oklch 0.930 0.006 240 | +| slate.rule | #CED1D4 | Dividers and table rules. oklch 0.860 0.006 240 | + +Three steps, all hue 240, chroma held at or below 0.006. The ramp is lightness only. A hue shift between two surfaces would read as two different papers on one page, and this deck's whole claim is that the page is one slab. + +### Text Colors + +| Token | Value | Usage | +| --------- | ------- | ------------------------------------------------------------------------ | +| ink.slate | #19212A | Primary text. 14.71:1 on wash, 13.19:1 on panel | +| ink.soft | #515961 | Secondary text. 6.44:1 on wash, 5.77:1 on panel | +| ink.faint | #737A81 | Labels, page numbers, quarter chips. 3.94:1 — **24px and above only** | + +ink.faint carries the same hue 250 as the other two and is legal only at 24px and up. Below that size it is not "quiet type", it is a contrast failure that a projector will finish off. + +### Accent Colors + +| Token | Value | Usage | +| --------------- | ------- | ---------------------------------------------------------------------- | +| mark.tide | #006C9B | **The single accent.** Scale rules, target marks, the key series. 5.25:1 | +| mark.tide.deep | #004C77 | The blue block that carries white text. 8.27:1 with white | +| mark.tide.wash | #D6ECF9 | Non-key chart series and pale grounds. 13.33:1 with ink.slate | + +The three tide tones are one hue (235) at three lightnesses, and they are not interchangeable. `mark.tide` is for marks and rules sitting on the slate; `mark.tide.deep` is the only tone that may be filled behind white text; `mark.tide.wash` is what every non-key series in a chart becomes. A chart in this deck is a **single-hue ramp** — key bars in `mark.tide`, everything else in `mark.tide.wash`. + +### Semantic Buoy Colors + +| Token | Value | Usage | +| ---------- | ------- | ---------------------------------------- | +| buoy.green | #35824B | Semantic: on track. 4.28:1 | +| buoy.amber | #AF7100 | Semantic: at risk. 3.67:1 | +| buoy.red | #B24037 | Semantic: blocked. 5.16:1 | + +**These three are a sub-system, not palette colours.** They take no part in the primary/secondary/accent allocation, they appear only as 12px dots or as pills, and they never become chart series colours. The reason is precise: the moment a green bar, an amber bar, and a red bar stand in one chart, the colours have been demoted from *meaning* to *category* — and categorical colour is the one thing this deck does not want, because its charts are single-ramp by design. + +The amber was pulled down from L 0.660 to **L 0.600** to buy 3.67:1. A semantic colour that works on hue but not on lightness fails twice over: for colour-blind viewers, and for anyone in the room watching a washed-out projector. + +## Typography + +### Font Families + +| Role | Family | Usage | +| ---------------- | ----------------------------------------------- | ------------------------------------------------------- | +| Display | Familjen Grotesk, 思源黑体 Bold | Cover headline and page titles | +| Body | Geist, 霞鹜新晰黑 | Body copy, table text, annotations, captions | +| Numerals | IBM Plex Mono | **Every figure, everywhere, without exception** | + +Fallback stack: display `Noto Sans SC` 700 / body `Noto Sans SC` 400 / numerals `Inter` 500 with **tabular figures on**. If the render stack cannot switch on tabular figures, numeric column widths must be written as fixed values instead — an un-tabular numeral column is a broken column. + +The Latin face is written **first** in every chain. Font matching is per-character: Latin faces carry no CJK codepoints, so Han characters fall through to the Chinese face automatically. Written the other way round, the Chinese face's own mediocre Latin swallows every digit and the paired Latin face never appears. + +Forcing all numerals to mono is the cheapest and highest-yield credibility device in this deck. Figures line up across tiles, across table rows, and across axis labels without a single alignment rule being written. + +### Type Scale + +| Level | Size | Font | Tracking | Usage | +| ----------------- | -------- | -------- | -------- | ------------------------------------------------ | +| Cover headline | 88px | Display | — | Cover page only | +| Page title | 56px | Display | — | Every interior page | +| Tile value | 64px | Numerals | — | The actual value inside a tidemark tile | +| Risk item title | 36px | Display | — | The heading of a risk entry | +| Resolution line | 32px | Body | — | One-sentence decision on the resolutions page | +| Table header | 28px | Body | — | Column headers | +| Table body | 28px | Body | — | Row text, impact lines, remediation lines | +| Tile sub-row | 26px | Body | — | The line under a tile value | +| Annotation | 26px | Body | — | Notes, explanatory copy, source lines | +| Tile label | 24px | Body | +2 / 0 | Uppercase, above the tile value. +2 on Latin runs, **0 on CJK** | +| Page number | 24px | Numerals | — | Page numbers and quarter chips | + +**The page title is 56px, one step smaller than a comparable low-density deck sets it.** That is deliberate: at this density the title has to yield to the content. A 72px title on a 16-slot page is a title that has taken space away from the thing the page exists to show. + +## Spacing System + +### Page Margins + +| Edge | Value | Note | +| ------------ | ----- | ----------------------------------------------------------- | +| Top | 88px | Tightened for density | +| Bottom | 104px | Larger than the top; the source line lives here | +| Left / Right | 96px | Never below the 72px soft floor | + +### Column Grid + +**12 columns × 122 + 11 gutters × 24 = 1728.** That 1728 is the content width, and with 96px side margins it lands the deck on a 1920 canvas exactly. Every structural block — tile row, table, lane chart, annotation rail — snaps to this grid. + +The 440px annotation rail on the trend page is a grid citizen: it sits at the right of the content width and the chart takes what remains. + +### Gap Scale + +The breathing unit for this deck is **16**, and every gap is an integer multiple of it. This is one step tighter than a low-density deck's unit, which is the whole point — high density is achieved by shrinking the rhythm, never by shrinking the type. + +## Corner Radius + +| Value | Usage | +| ----- | ---------------------------------------------------------------------- | +| 8px | Tidemark tiles, lane bars | +| 14px | Buoy pills only — half of the 28px pill height | +| 0px | Everything else: tables, rules, dividers, charts, sections, page grounds | + +8 is the **only** radius this deck introduces, and it exists for one reason: a tile has to separate from the ground without earning a border. Radius anywhere else is radius creep, and radius creep is what turns a review deck into a product page. + +## Shadow + +One shadow exists in the entire deck: `0 1px 2px rgba(20,32,45,.06)`, single layer, **tiles only**. Here a shadow is *readable lift*, not decoration. A second layer, a larger blur, or a shadow on any non-tile element is a defect. + +## Signature Motifs + +### 1. Tidemark tile — `tidemark-tile` + +The core device, and what separates this deck from a generic KPI card. Four stacked layers inside one `slate.panel` block at radius 8: + +1. **Label** — 24px, uppercase, letterSpacing +2 for Latin (**0 for a CJK label**), `ink.faint` +2. **Value** — mono 64px, `ink.slate` +3. **Scale rule** — a 2px horizontal rule spanning the full tile width, with a **3px vertical marker in `mark.tide`** placed at the target's position along that rule +4. **Delta chip** — a `buoy.*` dot, a mono figure, and a comparison word + +Layer 3 is the whole idea. **The target is drawn as a position on a scale, not written as "Target: X".** Written as text, distance-to-target must be computed by the reader; drawn as a mark, it is simply seen. A tile that states its target in words has been rebuilt as a KPI card and no longer belongs to this deck. + +Tile rows are **fixed at four tiles**. Three leaves an awkward gap; five squeezes the mono 64 value into a wrap. + +### 2. Buoy dot — `buoy-dot` + +A strictly semantic marker. Two forms only: + +- **Dot** — 12px circle in `buoy.green` / `buoy.amber` / `buoy.red` +- **Pill** — height 36, radius 18, horizontal padding 16, same three fills. The height is set by the label, not the other way round: 24px CJK at 1.4 needs a ~34px line box, so a 28px pill could only fit by shrinking the type — which is the one thing a deck may never do + +The hard rule, not a suggestion: **any row carrying a buoy dot must also carry an owner column and a due-date column.** A status with no owner and no date is an ownerless status, which is worse than no status at all. If either column cannot be filled, delete the dot. + +### 3. Tide lane — `tide-lane` + +The roadmap device. + +- **Lane name column**: 200px fixed, on the left +- **Time region**: the remainder, with months separated by 1px dashed `slate.rule` lines +- **Bar**: height 32, radius 8, filled `mark.tide.wash`, with a **6px solid `mark.tide` segment at its left end** +- **Milestone**: a 16×16 square rotated 45°, filled `mark.tide.deep` + +The milestone is a rotated square rather than a dot on purpose. **A milestone and a status marker must be distinguishable by shape, not by colour alone** — colour-only differentiation fails on a dim projector and fails for colour-blind viewers. + +## Page Inventory + +| # | Page type | Density | Slot cap | Structure | +| -- | --------------- | ----------- | -------- | -------------------------------------------------------------------------------------------------- | +| 01 | Cover | low | 5 | 88px headline + mono period chip + one scope line + presenter/date | +| 02 | Tile panorama | high | **16** | 56px conclusion title + a row of 4 tidemark tiles (4 slots each) + one source line | +| 03 | Trend | medium-high | 8 | 56px title + single-ramp bar chart (key `mark.tide`, rest `mark.tide.wash`) + 440px annotation rail, 2 groups | +| 04 | Detail table | high | **14** | 56px title + 6–8 table rows (buoy column + owner column + due column) + source | +| 05 | Risks | medium-high | 9 | 56px title + 3 risk entries (buoy dot + 36px title + 28px impact + 28px remediation), 1px `slate.rule` between entries | +| 06 | Lane roadmap | medium-high | 10 | 56px title + tide lanes (3–4 lanes × one quarter) + one legend line | +| 07 | Resolutions | medium | 6 | 56px title + 3 pending decisions (32px decision + mono decider + mono deadline) + a closing line | + +## Strictly Avoid + +1. **The three buoy colours never become chart series colours.** Dots and pills only; charts stay single-ramp. +2. **No gridlines, no legend, no y-axis ticks.** Values are labelled at the end of the bar. +3. **No pie charts, no donut charts, no 3D, no gradient bars, no shadowed bars.** +4. **Nothing but a tile carries a shadow.** In this deck shadow is readable lift, not ornament. +5. **A row with a buoy dot must have an owner and a due date.** Missing either — delete the dot. +6. **Page titles state a conclusion, not a topic.** "Adoption is up but two integrations are dragging", not "Usage update". +7. **No second non-semantic chromatic colour.** Beyond tide blue there are neutrals and the three semantic tones, nothing else. +8. **Numerals never use the body face.** One numeral set in a proportional face voids the column-alignment promise for the entire deck. +9. **No two-colour quarter-over-quarter bar comparison.** The comparison runs as a grey ghost bar, never as a second chromatic colour. +10. **Tile rows are fixed at four.** Not three, not five. + +## Anti-Patterns + +- **A warm or cream ground.** Even a slight warm cast reframes the numbers as something curated for mood. The ground is hue 240 at chroma 0.004 and stays there. +- **Rebuilding the tile as a KPI card.** Dropping the scale rule and writing "vs target" as a text line is the single most common way this deck collapses into a generic dashboard slide. +- **Radius creep.** Radius 8 is licensed for tiles and lane bars. Once table cells, section blocks, or chart bars pick it up, the deck stops reading as a cut slab. +- **Distinguishing a milestone from a status by colour.** They must differ in shape; a `mark.tide.deep` dot and a `buoy.*` dot are the same object to half the room. +- **A number without a source.** Any page carrying a figure carries a source line. If the source cannot be written, the figure should not be on the page. diff --git a/crates/op-ai-skills/src/deck_corpus_tests.rs b/crates/op-ai-skills/src/deck_corpus_tests.rs index f05fefb19..c2d01ff18 100644 --- a/crates/op-ai-skills/src/deck_corpus_tests.rs +++ b/crates/op-ai-skills/src/deck_corpus_tests.rs @@ -1,4 +1,5 @@ -//! Corpus guards for the deck skills (`slides` + `deck-patterns`). +//! Corpus guards for the deck skills (`slides`, `deck-patterns`, +//! `deck-contract`) and the `cjk-typography` rules they depend on. //! //! Delivery into the assembled system prompt is guarded on the orchestrator //! side (`prompt_deck_skill_tests`); this file guards the corpus itself — @@ -38,6 +39,140 @@ fn deck_patterns_registers_as_a_keyword_gated_generation_domain_skill() { } } +#[test] +fn deck_contract_fills_between_the_skeletons_and_the_tier_selector() { + let contract = get_skill_by_name("deck-contract").expect("deck-contract must be registered"); + assert_eq!(contract.meta.category, SkillCategory::Domain); + assert!(contract.meta.phase.contains(&Phase::Generation)); + assert_eq!(contract.meta.budget, 1700); + // The three deck skills are orthogonal and fill in a fixed order: + // `deck-patterns` (what to emit) → `deck-contract` (what may go on a page) + // → `slides` (which tier). Budget fill walks Domain skills by ascending + // priority, so this ordering decides who keeps its tail when a prompt also + // drags in `dashboard` / `web-app`. + let patterns = get_skill_by_name("deck-patterns").expect("deck-patterns registered"); + let slides = get_skill_by_name("slides").expect("slides registered"); + assert!( + patterns.meta.priority < contract.meta.priority + && contract.meta.priority < slides.meta.priority, + "deck-contract ({}) must sit between deck-patterns ({}) and slides ({})", + contract.meta.priority, + patterns.meta.priority, + slides.meta.priority + ); + match &contract.meta.trigger { + SkillTrigger::Keywords(keywords) => { + for word in ["deck", "ppt", "幻灯片", "课件", "路演"] { + assert!( + keywords.iter().any(|k| k == word), + "deck-contract must trigger on {word:?}" + ); + } + } + other => panic!("deck-contract must be keyword-gated, got {other:?}"), + } +} + +#[test] +fn deck_contract_carries_the_three_laws_and_the_negative_constraints() { + let body = &get_skill_by_name("deck-contract") + .expect("deck-contract registered") + .content; + for rule in [ + // Law 1 — the deck-specific overflow policy, which deliberately + // contradicts the web `clipContent` floor. + "overflow splits the page, it never shrinks the type", + "`clipContent` to crop the excess", + // Law 2 — density is a property of the page type, not of the deck. + "A slot = one independent text node", + "past the cap you SPLIT, not compress", + // Law 3 — the lock/vary split that prevents both mechanical and + // collage decks. + "LOCKED across pages", + "MUST change across pages", + // Narrative: the one line that separates a deck from a table of + // contents, plus the outline-first gate. + "An agenda is not a narrative", + "Ghost deck test", + "Titles state conclusions, not topics", + // Routing: the shared negative signal is the point of the table. + "never bend the content into it", + // The accent rule that occurrences — not hues — decide. + "an accent used 11 times is not an accent", + // Slop fingerprints the model reproduces most often. + "A rule under every title", + "Decorative shapes at 4-6% opacity", + "Flat hierarchy", + ] { + assert!(body.contains(rule), "deck-contract must teach {rule:?}"); + } +} + +/// `cjk-typography` used to contradict both this corpus and the shipped +/// `minimal-keynote` template in three places: line-height banded by +/// heading-vs-body (1.3–1.4 tears a 96px CJK display title apart, while the +/// template ships 1.02–1.12), `letterSpacing: 0, NEVER negative` (correct for +/// body, wrong for display, and the template already shipped negative +/// tracking), and `Body: ALWAYS "Inter"` — a render-time fallback written as +/// though it were a design rule, which collides with the anti-slop ban on +/// Inter-as-a-choice. A model reading two contradictory rules picks one at +/// random, so these are locked here. +#[test] +fn cjk_typography_bands_by_size_and_separates_fallback_from_choice() { + let body = &get_skill_by_name("cjk-typography") + .expect("cjk-typography registered") + .content; + for rule in [ + "lineHeight bands by FONT SIZE", + ">=64px 1.02-1.15", + "letterSpacing is absolute px here, not em", + "<48px: ALWAYS 0, never negative", + "`round(fontSize * -0.02)` is allowed and is the FLOOR", + "DESIGN layer", + "RENDER layer", + ] { + assert!(body.contains(rule), "cjk-typography must teach {rule:?}"); + } + for contradiction in [ + "headings 1.3-1.4", + "letterSpacing: 0, NEVER negative", + "Body: ALWAYS \"Inter\"", + ] { + assert!( + !body.contains(contradiction), + "cjk-typography still carries the retired rule {contradiction:?}" + ); + } +} + +/// `design-principles` is an always-on Knowledge skill whose numbers are the +/// screen/page scale — display 48-64, body 16, `#F8FAFC` alternating section +/// backgrounds, hero/nav recipes. Every one of those contradicts the deck +/// floors (`slides`: body 32, display 88-168, a deck whose largest size is +/// under 60px has no hierarchy at all). It is budget-evicted on most deck +/// prompts today, which happens to be the right outcome — but "happens to be" +/// is not a contract: one budget change puts the conflicting numbers back in +/// front of the model, which then picks one of the two scales at random. The +/// scope line is what makes the outcome intentional either way. +#[test] +fn design_principles_scopes_its_type_scale_away_from_decks() { + let body = &get_skill_by_name("design-principles") + .expect("design-principles registered") + .content; + assert!( + body.contains("SCOPE"), + "design-principles must declare which scale its numbers belong to" + ); + assert!( + body.contains("Never apply the sizes below to a slide"), + "the deck carve-out must be explicit" + ); + assert!( + body.contains("`slides` / `deck-contract`"), + "the carve-out must name where deck numbers actually come from" + ); +} + #[test] fn a_presentation_deck_prompt_no_longer_pulls_the_stacked_card_worked_example() { // `shapes-and-decks` teaches concentric rings and STACKED CARD decks. Its @@ -225,7 +360,7 @@ fn decomposition_derives_slide_count_from_the_material_not_the_outline_length() } #[test] -fn every_deck_prompt_resolves_both_deck_skills_untruncated() { +fn every_deck_prompt_resolves_all_three_deck_skills_untruncated() { // The generation-phase total is what actually decides this; these prompts // are the ones that used to lose `slides` (each pulls a second, larger // domain skill: dashboard / web-app / landing-page / mobile-app). @@ -239,7 +374,7 @@ fn every_deck_prompt_resolves_both_deck_skills_untruncated() { ] { let ctx = crate::resolve_skills(Phase::Generation, prompt, &crate::ResolveOptions::default()); - for name in ["slides", "deck-patterns"] { + for name in ["slides", "deck-patterns", "deck-contract"] { let entry = ctx .report .included @@ -317,5 +452,6 @@ fn the_deck_corpus_is_reachable_from_the_generation_phase_registry() { .map(|s| s.meta.name.as_str()) .collect(); assert!(names.contains(&"deck-patterns"), "got {names:?}"); + assert!(names.contains(&"deck-contract"), "got {names:?}"); assert!(names.contains(&"slides"), "got {names:?}"); } diff --git a/crates/op-ai-skills/src/resolve.rs b/crates/op-ai-skills/src/resolve.rs index 187a52ef5..9bea6f774 100644 --- a/crates/op-ai-skills/src/resolve.rs +++ b/crates/op-ai-skills/src/resolve.rs @@ -182,7 +182,7 @@ mod tests { "design a login form", &ResolveOptions::default(), ); - assert_eq!(ctx.budget_max, 12000); + assert_eq!(ctx.budget_max, 13200); assert!(ctx.budget_used <= ctx.budget_max); assert!( !ctx.skills.is_empty(), @@ -198,7 +198,7 @@ mod tests { /// Base skills alone (~6000 tokens) left only ~2000 tokens of /// headroom under the old 8000-token phase default (landing-page.md /// was cut to 275 of its 1203 tokens; design-principles was dropped - /// from all three prompts entirely). The phase default is now 12000 + /// from all three prompts entirely). The phase default is now 13200 /// (see `Phase::default_budget`'s doc comment); this locks in that no /// resolved skill in these representative prompts gets truncated by /// either mechanism (own-budget cap or total-budget tail cut). @@ -224,6 +224,68 @@ mod tests { } } + /// The budget report and the trimmer's internal accounting are the same + /// number, and a `BudgetExhausted` drop always means the skill genuinely + /// did not fit — never that headroom was left on the table. + /// + /// This was asked as "the report says 652 tokens free yet a 438-token + /// Knowledge skill was dropped, so `used` and `budget_used` must have + /// drifted apart" (2026-08-09 deck audit). They have not: `budget_used` + /// is summed from the very `token_count`s the knapsack accumulated, and + /// both trim steps recompute `remaining` from that same running total, so + /// a dropped skill is always strictly larger than the leftover (Step 3 + /// only skips a candidate when `token_count > remaining`, and `used` only + /// grows afterwards). Free headroom next to an evicted skill therefore + /// cannot be produced here — it can only come from a consumer that + /// removes skills AFTER resolution (the orchestrator's tier filters do + /// exactly that), which leaves a hole in a budget that was already spent. + /// This test is the standing answer, so the question does not have to be + /// re-litigated by hand next time. + #[test] + fn budget_report_matches_the_knapsack_and_drops_only_what_cannot_fit() { + for prompt in [ + "帮我做一个 8 页的融资路演 PPT,深色科技感", + "做一份季度数据汇报 PPT,要有仪表盘和数据表格", + "generate a 10-slide deck for our SaaS admin console product with analytics data tables", + "design an admin dashboard with a data table, charts, and analytics for a SaaS product", + ] { + let ctx = resolve_skills(Phase::Generation, prompt, &ResolveOptions::default()); + + // The report is the knapsack's own accounting, not a re-estimate. + let summed: u32 = ctx.skills.iter().map(|s| s.token_count).sum(); + assert_eq!( + ctx.budget_used, summed, + "{prompt:?}: report {} != sum of kept skills {summed}", + ctx.budget_used + ); + assert_eq!(ctx.report.budget_used, ctx.budget_used); + assert!(ctx.budget_used <= ctx.budget_max); + + let leftover = ctx.budget_max - ctx.budget_used; + for drop in ctx + .report + .dropped + .iter() + .filter(|d| d.reason == DropReason::BudgetExhausted) + { + let entry = crate::loader::get_skill_by_name(&drop.name) + .unwrap_or_else(|| panic!("dropped skill {:?} must exist", drop.name)); + // Skills carrying a `{{placeholder}}` are sized after + // injection, which the registry copy cannot reproduce. + if entry.content.contains("{{") { + continue; + } + let size = crate::budget::estimate_tokens(&entry.content).min(entry.meta.budget); + assert!( + size > leftover, + "{prompt:?}: {} ({size} tokens) was dropped for budget while \ + {leftover} tokens were still free", + drop.name + ); + } + } + } + #[test] fn generation_resolve_leaves_no_unresolved_placeholder() { // With history present, the `{{recentHistory}}` placeholder in diff --git a/crates/op-ai-skills/src/types.rs b/crates/op-ai-skills/src/types.rs index 1ac724da9..03470dcd4 100644 --- a/crates/op-ai-skills/src/types.rs +++ b/crates/op-ai-skills/src/types.rs @@ -58,6 +58,17 @@ impl Phase { /// callers (Basic/Standard mobile/desktop) are unaffected: they pass an /// explicit `budget_override` and never fall through to this default. /// + /// Generation moved again 12000 → 13200 (2026-08-09) when `deck-contract` + /// joined the deck corpus. A deck prompt now resolves four Domain skills + /// totalling ~6200 tokens (`cjk-typography` 602, `deck-patterns` 1821, + /// `deck-contract` 1599, `slides` 2176) on top of ~6700 of always-kept + /// Base skills. At 12000 that overflowed by ~900 and the Step 3 knapsack + /// cut `slides` down to 1274 tokens, so the tier tables and type floors + /// silently vanished from every deck prompt. The three deck skills are + /// deliberately orthogonal (tier selection, structural skeletons, + /// cross-tier contract), so the fix is headroom rather than merging them + /// back together. + /// /// Planning moved 4000 → 6000 for a related reason (2026-07-28). Its /// three `Base` skills are budget-EXEMPT but still counted against the /// total, and they need ~4500 tokens on their own once @@ -69,7 +80,7 @@ impl Phase { pub fn default_budget(self) -> u32 { match self { Phase::Planning => 6000, - Phase::Generation => 12000, + Phase::Generation => 13200, Phase::Validation => 3000, Phase::Maintenance => 5000, } @@ -79,7 +90,7 @@ impl Phase { /// Per-phase default token budgets — the TS `DEFAULT_BUDGETS` record. pub const DEFAULT_BUDGETS: [(Phase, u32); 4] = [ (Phase::Planning, 6000), - (Phase::Generation, 12000), + (Phase::Generation, 13200), (Phase::Validation, 3000), (Phase::Maintenance, 5000), ]; @@ -332,7 +343,7 @@ mod tests { #[test] fn default_budget_table() { assert_eq!(Phase::Planning.default_budget(), 6000); - assert_eq!(Phase::Generation.default_budget(), 12000); + assert_eq!(Phase::Generation.default_budget(), 13200); assert_eq!(Phase::Validation.default_budget(), 3000); assert_eq!(Phase::Maintenance.default_budget(), 5000); // The const table agrees with the per-variant method. diff --git a/crates/op-editor-core/assets/scene_templates/minimal-keynote.op b/crates/op-editor-core/assets/scene_templates/minimal-keynote.op index 8d03d0383..699d8670f 100644 --- a/crates/op-editor-core/assets/scene_templates/minimal-keynote.op +++ b/crates/op-editor-core/assets/scene_templates/minimal-keynote.op @@ -84,7 +84,7 @@ } ], "lineHeight": 1.02, - "letterSpacing": -6, + "letterSpacing": -3, "textGrowth": "fixed-width", "width": "fill_container", "textAlign": "center" @@ -643,7 +643,7 @@ } ], "lineHeight": 1.12, - "letterSpacing": -3, + "letterSpacing": -2, "textGrowth": "fixed-width", "width": "fill_container", "textAlign": "center" @@ -973,7 +973,7 @@ } ], "lineHeight": 1.0, - "letterSpacing": -8, + "letterSpacing": -5, "textGrowth": "fixed-width", "width": "fill_container", "textAlign": "center" @@ -1052,7 +1052,7 @@ } ], "lineHeight": 1.08, - "letterSpacing": -4, + "letterSpacing": -3, "textGrowth": "fixed-width", "width": "fill_container", "textAlign": "center"