name: slide-craft
title: "页面设计"
description: The design law of a slide page — canvas geometry, the text height table, the type scale, contrast pairs, spacing rhythm, and which element type carries which content. Load it when patching slide elements one at a time, when a fix is about colour or contrast, when text has to grow or shrink inside a box, when a page feels crowded or empty, or when replacement content needs real rich-text structure. It is the standard page-clone and pro-editing edit against, not a procedure of its own — those two decide which pages to touch and in what order, this one decides what a good page looks like when you put it back.
Every slide in this runtime was drawn under a long set of rules — the canvas, the height table, the type scale, the spacing standards. The generator that drew the page had all of them in front of it. When you patch one element, you have the page's numbers and nothing else, and the rules are the only way to tell a repair from a dent.
This is that rule set, restated for editing. It is about the page, not the process: which numbers a change has to stay consistent with, and which fields actually reach the screen.
The canvas is 1000 × 562.5. All elements respect a 50px margin, so the
live area is left ∈ [50, 950], top ∈ [50, 512.5], and an element's right
edge (left + width) stays ≤ 950, its bottom edge ≤ 512.5.
The page's own alignment grid, which existing elements are already on:
left = 60 or left = 80.left = (1000 - width) / 2 — recomputed, never guessed.left = 1000 - width - 60.Changing width on centred content changes left too, and they are two separate
writes. Read the neighbours' boxes first and land on the column they are already
using; a lone element 8px off the shared left edge reads as a mistake even though
nothing overflows.
There is no alignment operation. Every position is an explicit number you
write to that element's own left / top / width / height, one path per call,
computed from the neighbours you read. Aligning a row means giving each element the
number the row already uses — not asking the page to tidy itself.
A text element has 10px padding on all four sides, so its usable area is
(width - 20) × (height - 20), and heights come from one table
(line-height 1.5, padding included):
| Font size | 1 line | 2 lines | 3 lines | 4 lines | 5 lines |
|---|---|---|---|---|---|
| 14px | 43 | 64 | 85 | 106 | 127 |
| 16px | 46 | 70 | 94 | 118 | 142 |
| 18px | 49 | 76 | 103 | 130 | 157 |
| 20px | 52 | 82 | 112 | 142 | 172 |
| 24px | 58 | 94 | 130 | 166 | 202 |
| 28px | 64 | 106 | 148 | 190 | 232 |
| 32px | 70 | 118 | 166 | 214 | 262 |
| 36px | 76 | 130 | 184 | 238 | 292 |
When you replace an element's words, re-derive its height instead of keeping the old one:
characters_per_line = (width - 20) / font_size. Keep the longest line at
≤ 75% of that; past 100% the text wraps and takes a row you did not
budget.<p>, plus a wrap for
every paragraph over characters_per_line — then add ~0.8 of a line of
slack and round up.Height is a container, not a clamp: overflowing text spills past the box rather than shrinking, and the page shows the spill. The fix order is shorten the words, then step to the next table row, then widen the box — in that order, because a slide that needs a bigger box usually needs fewer words.
| Content | Size |
|---|---|
| Main title | 32-36px |
| Subtitle | 24-28px |
| Key points | 18-20px |
| Body | 16-18px |
| Caption | 14-16px |
Levels stay 2-4px apart and everything at one level uses one size. When you touch an element, take its size from the siblings that share its role on that page rather than from this table fresh — the page's own scale wins, and this table is how you recognise which level an element belongs to.
Size lives in the content HTML, as inline font-size on the <p> — there is
no font-size field to write. So changing a size means writing the whole content
string back, which means you have to know the markup you are replacing.
The content of a text element is an HTML string, and this page's CSS resets
most tag semantics. Four consequences decide how you write rich text:
<ul> / <ol> render without markers. The stylesheet's reset strips
list-style and the indent, and the rule that puts them back is scoped to
the browser editor — not to classroom playback. A list sent as
<ul><li>…</li></ul> comes out as bare unindented lines. Write bullets the
way the deck itself does: one <p> per item with the marker in the text,
<p style="font-size:18px;">• First point</p>.<h1>–<h6> are inert. The reset sets font-size: inherit and
font-weight: inherit on them, so a heading tag is a <p> with extra
characters. Size and weight come from inline font-size and
<strong>, which do work.<p> tags have no gap between them in a text element — only
line-height separates them. If two lines need air between them, raise the
element's lineHeight or split them into two elements; do not expect an
empty <p>, whose height still costs you a table row.<p> and the newline collapses to a space. Multi-line content must
arrive as real <p> tags, one per line.Anything that looks like a tag is passed through as markup and not escaped, so
prose containing < followed by a letter (if x<y> then) is read as HTML and
mangled. Send that as an escaped entity.
Supported inline styles are font-size, color, text-align, line-height,
font-weight and font-family; supported tags are <p>, <span>,
<strong>/<b>, <em>/<i>, <u>. Nothing else is a contract.
Before you change any colour, name the two things that will sit on top of each
other. Text is legible or not against its immediate backdrop, which is the
shape's fill if it sits on a shape, the text element's own fill if it has
one, and the page background otherwise.
Which field carries which colour:
| Ink | Field |
|---|---|
| Text element glyphs | defaultColor, or inline color in the content |
| Text element backdrop | fill (unset = the page shows through) |
| Shape label glyphs | text.defaultColor |
| Shape body | fill |
| Formula | color on the latex element |
| Rule / arrow | color on the line element |
A colour change is one write to the field the renderer actually reads —
defaultColor or the inline color inside the content for text,
text.defaultColor for a shape label, color for a latex or line element. Read
the source JSON first and preserve its inline spans. opacity on a text element fades the glyphs
and the fill together because it applies to the whole box — it is not a way
to soften a background behind live text.
Aim for ≥ 4.5:1 on body text and ≥ 3:1 on 24px-and-up titles. Two
calibration points from this deck's own palette: #333333 on white is about
12:1 and safe anywhere; the accent #5b9bd5 on white is about 3:1, which
passes for a 32px title and fails for 16px body. The pattern that always
works here is a pale tint fill with dark same-hue text — #1e40af on
#dbeafe, #166534 on #dcfce7, #92400e on #fef3c7, each around 6-7:1.
Recolouring one half of such a pair breaks it; recolour both or neither.
A label inside a card is not an independent element — its box is derived from the shape's:
text.width = shape.width - 40 (20px padding each side)
text.height = a table value ≤ shape.height - 40
text.left = shape.left + (shape.width - text.width) / 2
text.top = shape.top + (shape.height - text.height) / 2
Centre points should agree within 2px. So moving the shape is always two edits —
move the shape, then re-derive the label — and a text height that steps up to the
next table row may force the card to grow with it. The classic
wrong repair is giving the label the shape's own left/top, which pins it
to the top-left corner instead of centring it.
Parallel things use identical values, not close ones: three cards in a row
share one width, one height, one top, and one gap. The eye resolves 5px,
so approximation is visible. When you delete one card of a row or duplicate one
into it, respace all of them from the row's own arithmetic rather than
dropping the newcomer next to its neighbour.
The spacing standards the page was built to:
Diagnosing density from the inventory. The boxes tell you the truth
without a render: sort the content elements by top and read the gaps.
A crowded page shows gaps under ~20px and a bottom edge near 512; an empty one
shows one cluster and 150px of dead space below it. Fix crowding by cutting
words and merging elements, not by shrinking font sizes below the scale — a
page whose body text has dropped to 12px is a page with too much content on
it. Fix emptiness by growing the type one level and re-centring the block
vertically, not by adding filler.
The page has ten element types and they are not interchangeable. Getting this wrong is the failure that renders as literal source code on a slide.
\frac{a}{b}.
This holds for the small ones too (x^2, a/b).latex and the server re-renders the cached html the renderer
actually paints. Verify the result with a preview.width is stroke thickness, not length. Length comes
from start/end. Keep stroke at 2-4 (never above 6): the arrowhead is
width × 3 across, so width: 60 draws a 180px arrowhead.width and height together;
writing a new src does not touch the box, so an image of a different shape
needs the box re-derived.Underlines, dividers and highlight bars are shapes whose geometry was computed from the text they decorate. Move or resize the text and they do not follow — recompute them:
left = text.left + 10, width = text.width - 20,
top = text.top + text.height + 8..12, height = 2..4.width 700-900, height 1-2, with 25-35px of clear
space above and below.left = text.left - 15, top = text.top + 0.1 ×
text.height, height = 0.8 × text.height, width 3-6.An orphaned rule floating beside moved text is one of the most visible signs of a careless patch.
Elements paint in array order; later ones sit on top. Background shapes come before the text they hold. When something is hidden behind something else, the fix is to rewrite the element array in the order you want — never move a box to escape the overlap, and never delete the backdrop.
Check only the elements you touched, and check these:
left/top ≥ 50, right and bottom edges ≤ 950 /
512.5.characters_per_line.Where a preview is available, spend it on the changes this list cannot settle: a formula, a density judgement, or text you suspect overflows.
slide-dsl is the field reference under this one — every element type's
fields, which of them holds the words, what values are legal, and what the
renderer actually does with each of them. It says what you may write; this skill says
what is worth writing. Load it when you need a field name or a legal value.page-clone is the procedure for building a page out of an existing
one. It decides which elements are content and which are skeleton; this skill
decides whether the content you put in them is well made.pro-editing is the procedure for revising a page that already exists —
which operation to reach for, how small to keep it, how to verify. This skill
is what "well made" means once that operation lands.stage-design still governs the build of the stage as a whole.<p> per line, bullet characters instead
of <ul>, inline font-size instead of heading tags.