@ammduncan/easel 0.2.27 → 0.2.29
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +13 -0
- package/dist/client/viewer.js +63 -7
- package/dist/mcp.js +12 -7
- package/package.json +1 -1
- package/skills/using-easel/SKILL.md +19 -1
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,19 @@
|
|
|
2
2
|
|
|
3
3
|
All notable changes to easel. This project adheres to [Semantic Versioning](https://semver.org/).
|
|
4
4
|
|
|
5
|
+
## 0.2.29 — 2026-05-25
|
|
6
|
+
|
|
7
|
+
### Fixed
|
|
8
|
+
- **`.window` mockups no longer wash out in a dark-mode viewer — pin a stable surface like `.code`/`.terminal` already do.** `.window` set `background: light-dark(#ffffff, #161616)`, so its surface *flipped* with the host theme, and it never pinned text color or re-scoped `color: inherit` to children. In a dark-mode viewer the window resolved to a dark `#161616` panel, and a light-dashboard mockup's subtle gray-on-white labels (pills, captions, KPI sublabels) vanished against it — only explicitly-coloured figures survived. This is the identical surface-vs-ink mismatch the 0.2.28 `.code`/`.terminal` primitive locks against; it was fixed for code blocks but the `.window` chrome was never given the same treatment, and its theme-flipping background made it worse (the same mockup rendered differently per viewer). A mockup renders an app's own UI and should look the same to everyone, so `.window` is now a **stable light canvas**: white bg, pinned `#1a1a1a` ink, `color: inherit` re-scoped to every child. Added an opt-in **`.window.dark`** variant (locked dark surface + light ink + dark chrome + stronger shadow) for genuinely dark-UI mockups, and a `@media print` override so a dark mockup prints legibly (browsers drop background colors by default, which would otherwise strand its light ink on white paper). Skill + `push` tool description updated to document the stable surface and `.window.dark`.
|
|
9
|
+
|
|
10
|
+
## 0.2.28 — 2026-05-25
|
|
11
|
+
|
|
12
|
+
### Added
|
|
13
|
+
- **Built-in `.code` / `.terminal` code-block primitive — kills the invisible-code-block bug at the source.** The single most recurring failure was a hand-rolled dark code container: an agent sets `background:#0f172a` on a custom div but leaves base text inheriting `.wrap`'s `light-dark(#111,…)`, which resolves to near-black in light host mode and vanishes against the dark panel (only the explicitly-coloured syntax spans survive). The guidance to lock bg+ink as a pair was correct but purely advisory — it relied on the agent remembering it every push. Now the wrapper ships a baked-in primitive: `<div class="code">` (alias `.terminal`) locks both background and ink, re-scopes `color: inherit` to every child, and provides the verified github-dark token classes (`.kw .string .fn .prop .num .comment .muted .accent`) so syntax highlighting reads against `#0f172a` with no per-token tuning. Same lever as the existing `.window` chrome — a safe primitive agents reach for instead of hand-rolling. Prints on white paper with dark text like `pre`/`code`.
|
|
14
|
+
|
|
15
|
+
### Docs
|
|
16
|
+
- **Steered the `push` tool description and skill toward the new primitive.** The locked-mode section now leads with "use the built-in `.code`/`.terminal`, don't hand-roll", demoting the hand-rolled CSS to a fallback for other locked containers (brand heroes, custom callouts). Added a "Code & terminal blocks" entry to the skill's Built-in helpers. Plain `<pre>`/`<code>` remain safe (bg+ink token pair) as before.
|
|
17
|
+
|
|
5
18
|
## 0.2.27 — 2026-05-23
|
|
6
19
|
|
|
7
20
|
### Fixed
|
package/dist/client/viewer.js
CHANGED
|
@@ -728,16 +728,26 @@ body > *:last-child { margin-bottom: 0 !important; }
|
|
|
728
728
|
Add the desktop class for a full desktop-screen canvas — min-height 900px,
|
|
729
729
|
i.e. the 1440x900 (16:10) standard design canvas — so a screen mockup looks
|
|
730
730
|
like a real window with viewport breathing room rather than a short strip.
|
|
731
|
-
Omit desktop for dialogs / small components so they stay content-sized.
|
|
731
|
+
Omit desktop for dialogs / small components so they stay content-sized.
|
|
732
|
+
|
|
733
|
+
A mockup renders an app's own UI, so it owns a STABLE surface that does NOT
|
|
734
|
+
flip with the host theme — otherwise a light dashboard mockup renders on a dark
|
|
735
|
+
window in a dark-mode viewer and every subtle gray label washes out (same
|
|
736
|
+
surface-vs-ink mismatch the .code primitive locks against). Default is a light
|
|
737
|
+
canvas with pinned dark ink and color:inherit re-scoped to every child so the
|
|
738
|
+
host's light-dark() ink can never leak in. Add the dark class
|
|
739
|
+
(class="window dark") for a genuinely dark-UI mockup. */
|
|
732
740
|
.window {
|
|
733
741
|
position: relative;
|
|
734
742
|
padding-top: 40px;
|
|
735
743
|
border-radius: 12px;
|
|
736
|
-
border: 1px solid
|
|
744
|
+
border: 1px solid #e2e2e2;
|
|
737
745
|
box-shadow: 0 14px 48px rgba(0, 0, 0, 0.16);
|
|
738
746
|
overflow: hidden;
|
|
739
|
-
background:
|
|
747
|
+
background: #ffffff;
|
|
748
|
+
color: #1a1a1a;
|
|
740
749
|
}
|
|
750
|
+
.window * { color: inherit; }
|
|
741
751
|
.window::before {
|
|
742
752
|
content: "";
|
|
743
753
|
position: absolute;
|
|
@@ -745,8 +755,8 @@ body > *:last-child { margin-bottom: 0 !important; }
|
|
|
745
755
|
left: 0;
|
|
746
756
|
right: 0;
|
|
747
757
|
height: 40px;
|
|
748
|
-
background-color:
|
|
749
|
-
border-bottom: 1px solid
|
|
758
|
+
background-color: #f1f1f1;
|
|
759
|
+
border-bottom: 1px solid #e2e2e2;
|
|
750
760
|
background-image:
|
|
751
761
|
radial-gradient(circle at 19px 20px, #ff5f57 6px, transparent 6.5px),
|
|
752
762
|
radial-gradient(circle at 39px 20px, #febc2e 6px, transparent 6.5px),
|
|
@@ -765,9 +775,20 @@ body > *:last-child { margin-bottom: 0 !important; }
|
|
|
765
775
|
justify-content: center;
|
|
766
776
|
font-size: 13px;
|
|
767
777
|
font-weight: 500;
|
|
768
|
-
color:
|
|
778
|
+
color: #6b6b6b;
|
|
769
779
|
pointer-events: none;
|
|
770
780
|
}
|
|
781
|
+
.window.dark {
|
|
782
|
+
border-color: #2a2a2a;
|
|
783
|
+
background: #161616;
|
|
784
|
+
color: #e6edf3;
|
|
785
|
+
box-shadow: 0 14px 48px rgba(0, 0, 0, 0.4);
|
|
786
|
+
}
|
|
787
|
+
.window.dark::before {
|
|
788
|
+
background-color: #1f1f1f;
|
|
789
|
+
border-bottom-color: #2a2a2a;
|
|
790
|
+
}
|
|
791
|
+
.window.dark::after { color: #9b9b9b; }
|
|
771
792
|
.window.desktop {
|
|
772
793
|
min-height: 900px;
|
|
773
794
|
}
|
|
@@ -837,6 +858,35 @@ pre {
|
|
|
837
858
|
margin: 16px 0 24px;
|
|
838
859
|
}
|
|
839
860
|
pre code { background: transparent; padding: 0; color: inherit; font-size: inherit; }
|
|
861
|
+
/* Locked-dark code / terminal primitive. Reach for this instead of hand-rolling
|
|
862
|
+
a dark code container — the recurring failure is a custom dark <div> that sets
|
|
863
|
+
its own background but lets base text inherit .wrap's light-dark() ink, which
|
|
864
|
+
resolves to near-black in light host mode and vanishes against the dark panel.
|
|
865
|
+
This class locks BOTH background and ink, and re-scopes color:inherit to every
|
|
866
|
+
child so the host theme can never leak in. Ships the verified github-dark token
|
|
867
|
+
palette so syntax highlighting reads against #0f172a without per-token tuning.
|
|
868
|
+
Usage: <div class="code"><span class="kw">gcloud</span> services enable …</div>
|
|
869
|
+
.terminal is an alias; add .terminal for a prompt feel (same colors). */
|
|
870
|
+
.code, .terminal {
|
|
871
|
+
background: #0f172a;
|
|
872
|
+
color: #e6edf3;
|
|
873
|
+
border-radius: 12px;
|
|
874
|
+
padding: 18px 22px;
|
|
875
|
+
font-family: ui-monospace, "SF Mono", Menlo, monospace;
|
|
876
|
+
font-size: 13.5px;
|
|
877
|
+
line-height: 1.7;
|
|
878
|
+
overflow: auto;
|
|
879
|
+
margin: 16px 0 24px;
|
|
880
|
+
}
|
|
881
|
+
.code *, .terminal * { color: inherit; }
|
|
882
|
+
.code .kw, .terminal .kw { color: #ff7b72; } /* keywords, control flow */
|
|
883
|
+
.code .string, .terminal .string { color: #a5d6ff; } /* strings, attr values */
|
|
884
|
+
.code .fn, .terminal .fn { color: #d2a8ff; } /* function names */
|
|
885
|
+
.code .prop, .terminal .prop { color: #79c0ff; } /* identifiers, properties */
|
|
886
|
+
.code .num, .terminal .num { color: #ffa657; } /* numbers, constants */
|
|
887
|
+
.code .comment, .terminal .comment { color: #8b949e; } /* comments */
|
|
888
|
+
.code .muted, .terminal .muted { color: #94a3b8; } /* dim / secondary */
|
|
889
|
+
.code .accent, .terminal .accent { color: #6ee7b7; } /* highlight / success */
|
|
840
890
|
blockquote {
|
|
841
891
|
border-left: 3px solid var(--ds-accent);
|
|
842
892
|
margin: 18px 0;
|
|
@@ -883,7 +933,13 @@ img { max-width: 100%; height: auto; border-radius: 10px; }
|
|
|
883
933
|
body { padding: 24px !important; max-width: none !important; }
|
|
884
934
|
body > p, body > .deck, body > .lede, body > ul, body > ol, body > blockquote,
|
|
885
935
|
body > h1, body > h2, body > h3, body > h4 { max-width: none !important; }
|
|
886
|
-
pre, code { background: #f4f3ed !important; color: #111 !important; border: 1px solid #ddd; }
|
|
936
|
+
pre, code, .code, .terminal { background: #f4f3ed !important; color: #111 !important; border: 1px solid #ddd; }
|
|
937
|
+
.code *, .terminal * { color: #111 !important; }
|
|
938
|
+
/* Force the dark window variant light for print — browsers drop background
|
|
939
|
+
colors by default, which would leave its light ink invisible on white paper
|
|
940
|
+
(same reason .code/.terminal are forced light above). */
|
|
941
|
+
.window.dark { background: #ffffff !important; color: #111 !important; }
|
|
942
|
+
.window.dark * { color: #111 !important; }
|
|
887
943
|
.card, .panel { background: #fff !important; border: 1px solid #ddd !important; box-shadow: none !important; }
|
|
888
944
|
a { color: #111 !important; text-decoration: underline; border-bottom: 0 !important; }
|
|
889
945
|
}
|
package/dist/mcp.js
CHANGED
|
@@ -112,12 +112,17 @@ export async function main() {
|
|
|
112
112
|
" .wrap { color: light-dark(#111, #e8e8e8); padding: 56px 48px; font-family: -apple-system, 'Inter', system-ui, sans-serif; max-width: 820px; }\n" +
|
|
113
113
|
" .wrap *, .wrap h1, .wrap h2, .wrap h3, .wrap p, .wrap li, .wrap span { color: inherit; }\n" +
|
|
114
114
|
" .card { background: light-dark(#fff, #161616); border: 1px solid light-dark(#e0d9c3, #2a2a2a); border-radius: 12px; padding: 24px; }\n\n" +
|
|
115
|
-
"═══
|
|
116
|
-
"
|
|
117
|
-
"
|
|
118
|
-
"
|
|
119
|
-
" .
|
|
120
|
-
" .
|
|
115
|
+
"═══ CODE / TERMINAL BLOCKS — USE THE BUILT-IN PRIMITIVE, DON'T HAND-ROLL ═══\n" +
|
|
116
|
+
"The #1 recurring bug is a hand-rolled dark code container: you set `background:#0f172a` on a custom div but leave base text inheriting `.wrap`'s `light-dark(#111,…)`, which resolves to near-black in light host mode and VANISHES against the dark panel (only the explicitly-coloured syntax spans survive). Don't hand-roll it. The wrapper ships a baked-in, always-safe primitive:\n" +
|
|
117
|
+
" <div class=\"code\"> … </div> (alias: class=\"terminal\")\n" +
|
|
118
|
+
"It locks BOTH background (#0f172a) and ink (#e6edf3), re-scopes `color:inherit` to every child, and provides verified github-dark syntax token classes you can drop onto spans — NO per-token tuning needed:\n" +
|
|
119
|
+
" .kw (keywords #ff7b72) · .string (#a5d6ff) · .fn (function #d2a8ff) · .prop (identifiers #79c0ff) · .num (#ffa657) · .comment (#8b949e) · .muted (#94a3b8) · .accent (#6ee7b7)\n" +
|
|
120
|
+
" e.g. <div class=\"code\"><span class=\"kw\">gcloud</span> services enable run.googleapis.com</div>\n" +
|
|
121
|
+
"Plain <pre>/<code> are also already safe (bg+ink token pair). Only reach for a fully custom container when .code/.terminal genuinely don't fit — and then obey the locked-mode rule below.\n\n" +
|
|
122
|
+
"═══ COPY-PASTE STARTER (any OTHER LOCKED-MODE container — brand hero, custom panel) ═══\n" +
|
|
123
|
+
"If a container has a FIXED background (not `light-dark()`), you MUST set its own text color AND re-scope `color: inherit` to its children. Otherwise the children inherit `light-dark(...)` from `.wrap` and the text flips to the wrong shade in one mode (e.g. dark text on a locked-dark panel in light host mode → invisible).\n" +
|
|
124
|
+
" .hero { background: #0f172a; color: #e6edf3; border-radius: 12px; padding: 20px 24px; }\n" +
|
|
125
|
+
" .hero * { color: inherit; }\n" +
|
|
121
126
|
"• Same pairing applies in the OPPOSITE direction — locked-LIGHT containers (e.g. a white card on the host canvas). A `.card { background: #fff }` with no `color:` inherits `.wrap`'s light-dark() text, which in dark host mode resolves to a light cream/gray → invisible titles on a white card. Commit text too AND re-scope inherit on children. This bites just as often as the dark case.\n" +
|
|
122
127
|
" .card { background: #ffffff; color: #111111; border: 1px solid #e5e5e5; border-radius: 12px; padding: 24px 32px; }\n" +
|
|
123
128
|
" .card * { color: inherit; }\n" +
|
|
@@ -140,7 +145,7 @@ export async function main() {
|
|
|
140
145
|
"• Stack desktop mockups VERTICALLY with labels ('Now', 'Proposed') — don't squeeze them side-by-side. The iframe is ~900px wide; two desktop screens at half-width crush columns, wrap headings to 3 lines, and turn tables unreadable.\n" +
|
|
141
146
|
"• Side-by-side is fine only for narrow mobile mockups, small cards, or short text columns that genuinely fit in half-width.\n" +
|
|
142
147
|
"• Mockup embedded mid-explanation? Prose is left-aligned and capped ~880px; wrap JUST the mockup in <div class=\"full-bleed\">…</div> and it fills the content column from the SAME left edge (wider than the prose, sharing one left margin; the body padding stays as a gutter so nothing touches the card edge). (If the WHOLE push is a UI recreation, use kind:'mockup'/'app' instead.)\n" +
|
|
143
|
-
"• Window chrome: wrap a mockup in <div class=\"window\" data-title=\"App name\">…</div> for a macOS window frame (title bar + red/yellow/green traffic-light dots + centred title). Add the `desktop` class (class=\"window desktop\") for the 1440x900 (16:10) desktop canvas via min-height:900px; omit it so dialogs/components size to content. Combine with .full-bleed to fill the column. NOTE: .window sets overflow:hidden (to clip its rounded corners) — so NEVER put a fixed `height` on .window or any inner stage, or content past that height is silently guillotined. It's built to grow via min-height.\n" +
|
|
148
|
+
"• Window chrome: wrap a mockup in <div class=\"window\" data-title=\"App name\">…</div> for a macOS window frame (title bar + red/yellow/green traffic-light dots + centred title). Add the `desktop` class (class=\"window desktop\") for the 1440x900 (16:10) desktop canvas via min-height:900px; omit it so dialogs/components size to content. Combine with .full-bleed to fill the column. .window is a STABLE LIGHT canvas — it pins white bg + dark ink + re-scopes color:inherit to children (like .code/.terminal), so it does NOT flip with the host theme and subtle gray-on-white labels stay legible even in a dark-mode viewer; a mockup is a screenshot, it should look the same to everyone. For a genuinely dark-UI mockup add the `dark` class (class=\"window dark\") — don't hand-roll a dark .window. NOTE: .window sets overflow:hidden (to clip its rounded corners) — so NEVER put a fixed `height` on .window or any inner stage, or content past that height is silently guillotined. It's built to grow via min-height.\n" +
|
|
144
149
|
"• BUILD MOCKUPS FLUID, not fixed-width. Lay the inside out with flex / % / fr widths, not hardcoded width:1440px columns. 1440 is a MAX, not a target. A fluid mockup reflows to fit when the viewer's window is squeezed — no horizontal scroll, nothing clipped, exports stay complete. A fixed-pixel-width mockup gets cut off or needs an awkward horizontal scrollbar when narrowed.\n" +
|
|
145
150
|
"• NEVER CLIP CONTENT — no fixed `height` + `overflow:hidden` on any container that holds content (cards, panels, device/browser/phone frames, stages, slideovers, toasts). That combo guillotines anything taller than your guessed height — buttons sliced through text, lists cut mid-item. Containers size to their CONTENT: use `min-height` for a floor, NEVER a fixed `height`. `overflow:hidden` is allowed ONLY for genuine cosmetic crops where clipping IS the intent (rounded-corner image masks, decorative bleed) — never on a content region. Decorative frames must grow with their content. When unsure, leave height unset. Mentally render the tallest card: if any text/button could exceed the box, the box is wrong.\n" +
|
|
146
151
|
"• MATCH THE SOURCE'S REAL FRAME — faithful height, not minimal, in both directions. Mocking a COMPONENT (card, modal, row, toolbar)? Size to content — do NOT pad with min-height:560px to feel 'desktop-y'; that floats content in dead whitespace. Mocking a FULL DESKTOP SCREEN (login page, dashboard)? Give it realistic viewport proportions via MIN-HEIGHT (e.g. min-height:760px or a 16:10 floor — never a fixed `height`, which clips) and lay content out inside as the real screen does (centred form, top nav). Either way copy the source's exact dimensions if it has them, as a min-height. Test: cropped the same way, would your mock look like a screenshot of the real thing? Empty bands = over-padded; a screen squashed to a strip = under-sized.\n" +
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@ammduncan/easel",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.29",
|
|
4
4
|
"description": "A live browser tab for every Claude Code (and MCP) session. The push MCP tool appends HTML cards to a scrolling feed you keep open in split-screen.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "MIT",
|
|
@@ -205,8 +205,10 @@ So the rule is **faithful height, not minimal height** — but always expressed
|
|
|
205
205
|
|
|
206
206
|
The test: cropped the same way, would your mock look like a screenshot of the real thing? Empty bands the real screen doesn't have = over-padded. A desktop screen squashed to a short strip = under-sized.
|
|
207
207
|
|
|
208
|
+
**For code / terminal blocks, don't hand-roll — use the built-in `.code` / `.terminal` primitive** (see Built-in helpers below). It already locks bg + ink, re-scopes `color: inherit` to children, and ships the verified github-dark token palette. The hand-rolled patterns below are the fallback for *other* locked containers (brand heroes, custom dark callouts) where no primitive fits.
|
|
209
|
+
|
|
208
210
|
```css
|
|
209
|
-
/* Locked-dark container (
|
|
211
|
+
/* Locked-dark container (custom dark callout, brand hero). */
|
|
210
212
|
.terminal {
|
|
211
213
|
background: #0f172a; /* locked dark, ignores host mode */
|
|
212
214
|
color: #e6edf3; /* MUST set text too */
|
|
@@ -315,6 +317,8 @@ Wrap a mockup in `.window` to give it a macOS window frame — a title bar with
|
|
|
315
317
|
- `data-title` sets the centred title text (omit for a blank bar).
|
|
316
318
|
- Add the **`desktop`** class for a full desktop-screen canvas — `min-height: 900px`, the standard 1440×900 (16:10) design canvas — so a screen mockup looks like a real window with viewport breathing room. **Omit `desktop`** for a dialog or small component so the chrome sizes to its content (don't pad a small thing to screen height).
|
|
317
319
|
|
|
320
|
+
**`.window` is a stable LIGHT canvas — it does not flip with the host theme.** A mockup is a screenshot of an app; it should look the same to every viewer regardless of their OS/viewer mode. So `.window` pins a white surface with dark ink and re-scopes `color: inherit` to children (same locked-surface treatment as `.code`/`.terminal`) — you can use subtle gray-on-white labels inside and they stay legible even when the viewer is in dark mode. For a genuinely **dark-UI** mockup, add the **`dark`** class (`class="window dark"`) — it locks a dark surface + light ink + dark chrome instead. Don't hand-roll a dark `.window` with inline backgrounds.
|
|
321
|
+
|
|
318
322
|
**Build the mockup fluid, not fixed-width.** Lay the inside out with flex / `%` / `fr` widths, not hardcoded `width: 1440px` columns. The content column caps at a desktop-realistic width, but when the viewer's window is narrower (a "squeezed" screen) a fluid mockup simply **reflows to fit** — no horizontal scroll, nothing clipped, and PNG/PDF export still captures the whole thing. A fixed-pixel-width mockup gets cut off or needs an awkward horizontal scrollbar when squeezed; a fluid one never does. The 1440 is a *max*, not a target.
|
|
319
323
|
|
|
320
324
|
**`.window` sets `overflow: hidden`** (to clip its own rounded corners) and grows via `min-height`, never a fixed height. So **never put a fixed `height` on `.window` itself, or on an inner "stage" element** — content past that height gets silently guillotined, and because the overflow is on the frame the crop is invisible until you export or scroll. Let it grow.
|
|
@@ -328,6 +332,20 @@ The width rule above has a vertical twin, and it's the more common footgun: **ne
|
|
|
328
332
|
- **Decorative frames** (browser chrome, phone bezel, device frame) must grow with their content — give the frame `min-height` and let it expand, or don't constrain height at all.
|
|
329
333
|
- **The mental test:** render the tallest card in your head. If any text or button could exceed the container, the container is wrong. When unsure, leave height unset. A mockup exists to show the design *fully* — uniform-looking rectangles are never worth clipped content; let frames be different heights.
|
|
330
334
|
|
|
335
|
+
### Code & terminal blocks
|
|
336
|
+
|
|
337
|
+
Reach for the built-in **`.code`** (alias **`.terminal`**) class instead of hand-rolling a dark code container — that hand-roll is the single most recurring failure (custom `background:#0f172a` div + base text inheriting `.wrap`'s `light-dark(#111,…)` → invisible in light host mode). The primitive locks bg + ink, re-scopes `color: inherit` to children, and ships the verified github-dark token palette so syntax highlighting reads against `#0f172a` with no per-token tuning:
|
|
338
|
+
|
|
339
|
+
```html
|
|
340
|
+
<div class="code">
|
|
341
|
+
<span class="kw">gcloud</span> services enable run.googleapis.com
|
|
342
|
+
<span class="comment"># dvla artifact registry</span>
|
|
343
|
+
<span class="prop">--location=</span><span class="string">europe-west1</span>
|
|
344
|
+
</div>
|
|
345
|
+
```
|
|
346
|
+
|
|
347
|
+
Token classes: `.kw` (keywords) · `.string` · `.fn` (functions) · `.prop` (identifiers/properties) · `.num` · `.comment` · `.muted` · `.accent`. Plain `<pre>`/`<code>` are already safe too (bg + ink token pair). Only hand-roll a custom dark container when neither fits — and then obey the locked-mode pairing rule above.
|
|
348
|
+
|
|
331
349
|
### Semantic chips
|
|
332
350
|
|
|
333
351
|
```html
|