callouts: restyle boxes, revise type vocabulary, normalize warn
commit 04acf71067b695d4b8b073103377d9bfaf26f105 · parent 40c7f54f5b
· browse this tree
Ximianth, 2026-08-09 11:09:37 UTC
Restyle: each type drives one --callout hue, and the box derives a tinted fill and matching border from it via color-mix, plus a soft drop shadow and vertical margin. Titles keep the caps/letter-spacing look and gain an emoji icon via --callout-icon (following the heading chainlink's emoji-as-icon idiom); only known types set an icon, so an unknown type stays a clean icon-less neutral box. Vocabulary is now info, note, tip, todo, warning, danger; info and note ride the per-host accent. warn is normalized to warning in the parser via a small alias table, kept as a table so the mapping is cheap to grow, rather than duplicating spellings across the CSS.
diff --git a/README.md b/README.md index f5c1a9c..ea82de0 100644 --- a/README.md +++ b/README.md @@ -84,8 +84,9 @@ body content, which is **full markdown** The rendered box is `<div class="callout callout-{type}">` (matching the markdown-it-container / VuePress convention, so docs authored for those tools render as intended). -The types `note`, `info`, `tip`, `important`, `warning`, `caution`, and `danger` -get distinct accent colors; note and info ride the per-host accent. +The styled types are `info`, `note`, `tip`, `todo`, `warning`, and `danger`, +each with its own accent color and icon; `info` and `note` ride the per-host accent. +`warn` is accepted as an alias for `warning`. An unknown type still renders as a plain neutral box. Callouts do not nest, by design, diff --git a/callout.go b/callout.go index 323deee..9d65fba 100644 --- a/callout.go +++ b/callout.go @@ -36,6 +36,22 @@ type calloutNode struct { var kindCallout = ast.NewNodeKind("Callout") +// calloutTypeAliases folds alternate type spellings onto the canonical set that +// style.css styles. Kept as a table because it is cheap to grow; for now it +// just spells warn out in full. +var calloutTypeAliases = map[string]string{ + "warn": "warning", +} + +// canonicalCalloutType maps an author's type onto its canonical spelling, +// leaving unknown types untouched (they render as a neutral box). +func canonicalCalloutType(typ string) string { + if c, ok := calloutTypeAliases[typ]; ok { + return c + } + return typ +} + func (n *calloutNode) Kind() ast.NodeKind { return kindCallout } func (n *calloutNode) Dump(src []byte, level int) { ast.DumpHelper(n, src, level, nil, nil) } @@ -89,7 +105,7 @@ func (calloutParser) Open(parent ast.Node, reader text.Reader, pc parser.Context typ, title = rest[:sp], bytes.TrimSpace(rest[sp+1:]) } reader.Advance(len(line)) - return &calloutNode{Kind_: string(typ), Title: string(title)}, parser.HasChildren + return &calloutNode{Kind_: canonicalCalloutType(string(typ)), Title: string(title)}, parser.HasChildren } func (calloutParser) Continue(node ast.Node, reader text.Reader, pc parser.Context) parser.State { diff --git a/callout_test.go b/callout_test.go index ec5ea0e..69f3efd 100644 --- a/callout_test.go +++ b/callout_test.go @@ -113,6 +113,22 @@ func TestCalloutTypeIsEscaped(t *testing.T) { } } +func TestCalloutTypeNormalized(t *testing.T) { + // warn folds onto the canonical warning type that style.css styles. + got := renderMD(t, ":::warn Heads up\nx\n:::\n") + if !strings.Contains(got, "callout-warning") { + t.Errorf("warn not normalized to warning; got:\n%s", got) + } +} + +func TestCalloutUnknownTypePassesThrough(t *testing.T) { + // An unknown type is left as-is on the class, for the neutral fallback box. + got := renderMD(t, ":::obscure\nx\n:::\n") + if !strings.Contains(got, "callout callout-obscure") { + t.Errorf("unknown type not preserved; got:\n%s", got) + } +} + func TestCalloutNoNesting(t *testing.T) { // An opener inside a callout is left as plain content, not a nested box. got := renderMD(t, ":::note\n:::warning\ninner\n:::\n") diff --git a/style.css b/style.css index 76b305f..aaa65ab 100644 --- a/style.css +++ b/style.css @@ -109,35 +109,52 @@ article th, article td { } article th { background: var(--well); } -/* Callouts: :::type admonition boxes. Inset like other article content, - in a well with a bold left border, consistent with pre and blockquote. - The per-type accent hue rides a single --callout variable so the base box - below stays the one place that positions and paints the container. */ +/* Callouts: :::type admonition boxes. + Each type carries one accent hue in --callout, and the base box derives its + tinted fill and border from that hue with color-mix, so a single value drives + the whole look (info and note simply inherit the per-host --accent). + --callout-icon is the emoji shown before a title, following this repo's + emoji-as-icon idiom (see the heading chainlink); it rides the title, so a + title-less callout shows no icon. */ article .callout { --callout: var(--accent); - background: var(--well); - border: 1px solid var(--well-edge); - border-left: 4px solid var(--callout); - border-radius: .25rem; - padding: .6rem .8rem; + margin-top: 1.25rem; + margin-bottom: 1.25rem; + padding: .7rem 1rem; + border: 1px solid color-mix(in srgb, var(--callout) 40%, var(--well-edge)); + border-radius: .5rem; + background: color-mix(in srgb, var(--callout) 9%, var(--paper)); + box-shadow: 0 1px 3px rgba(0, 0, 0, .08); } /* The children are already inset by "article > *"; inside the box they should sit flush to its padding instead. */ article .callout > * { margin-left: 0; } article .callout-title { - margin-top: 0; + margin: 0 0 .4rem; font-weight: bold; + font-size: .95rem; text-transform: uppercase; letter-spacing: .03em; color: var(--callout); } +/* The icon rides the title via --callout-icon. Only the known types below set + it, so an unknown type shows a clean icon-less box rather than a misleading + glyph; a title-less callout shows no icon at all. */ +article .callout-title::before { + content: var(--callout-icon, ""); + margin-right: .45rem; +} -/* Per-type accent hues. note/info stay in the blue --accent family; - the rest pick one hue each. An unknown type matches none of these and - falls back to the neutral base box above, still readable. */ -article .callout-tip { --callout: #1a7f37; } -article .callout-warning, -article .callout-caution { --callout: #bf8700; } -article .callout-danger { --callout: #cf222e; } -article .callout-important { --callout: #8250df; } +/* Per-type accent hues and icons, for the canonical type set only; + alternate spellings (warn, success, caution, important) are folded onto + these by the normalizer before they reach the class, so the styling stays + to one row per look. info and note keep the per-host --accent. + An unknown type matches none of these and falls back to the neutral base + box above, still readable. */ +article .callout-info, +article .callout-note { --callout-icon: "\2139\FE0F"; } /* information source, theme color */ +article .callout-tip { --callout: #1a7f37; --callout-icon: "\1F4A1"; } /* light bulb, green */ +article .callout-todo { --callout: #8250df; --callout-icon: "\1F4DD"; } /* memo, purple */ +article .callout-warning { --callout: #e8590c; --callout-icon: "\26A0\FE0F"; } /* warning sign, orange */ +article .callout-danger { --callout: #cf222e; --callout-icon: "\1F6D1"; } /* stop sign, red */