@markii/html 0.7.2 → 0.8.0
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.
|
@@ -2,4 +2,4 @@
|
|
|
2
2
|
// Regenerate with: node scripts/generate-doc-css.ts
|
|
3
3
|
// Source of truth: packages/platforms/markii-react/src/doc.css
|
|
4
4
|
/** The shared document stylesheet (@markii/react's doc.css), embedded as a string for exportHtmlDocument's <style> block. */
|
|
5
|
-
export const DOC_CSS = "/*\n * Document rhythm: components own their insides only, never outer margins.\n * This single rule spaces every block-level child of `.doc` identically —\n * paragraphs, headings, and components alike — so new components always\n * sit correctly in the flow with zero per-component tuning.\n */\n.doc > * + * {\n margin-block-start: 1rem;\n}\n\n.doc {\n color: #1a1a1a;\n font-family:\n system-ui,\n -apple-system,\n 'Segoe UI',\n sans-serif;\n line-height: 1.6;\n}\n\n.doc pre {\n overflow-x: auto;\n background: #f4f4f5;\n padding: 0.75rem 1rem;\n border-radius: 6px;\n}\n\n.doc code {\n background: #f0f0f2;\n border-radius: 3px;\n padding: 0.1em 0.35em;\n font-size: 0.9em;\n}\n\n.doc pre code {\n background: none;\n padding: 0;\n}\n\n/* ---------- GFM table ---------- */\n\n/*\n * `display: block` on the table itself (rather than wrapping it in an\n * extra element the renderer doesn't otherwise inject) is what makes a wide\n * table scroll horizontally instead of overflowing the page or the doc\n * column — the table box becomes independently scrollable content, the\n * same trick used by GitHub's own Markdown rendering.\n */\n.doc table {\n display: block;\n overflow-x: auto;\n border-collapse: collapse;\n font-size: 0.95em;\n}\n\n.doc th,\n.doc td {\n border: 1px solid #e4e4e7;\n padding: 0.4rem 0.75rem;\n text-align: left;\n}\n\n.doc th {\n font-weight: 600;\n background: #f8fafc;\n}\n\n.doc tr:nth-child(even) {\n background: #fafafa;\n}\n\n/* ---------- GFM task list ---------- */\n\n/*\n * `li:has(> input[type=\"checkbox\"])` scopes bullet removal + checkbox\n * alignment to task-list items only — an ordinary `<ul>`/`<ol>` item keeps\n * its normal bullet/number, since GFM only adds a leading `<input>` to\n * items that used `- [ ]`/`- [x]` syntax.\n */\n.doc li:has(> input[type='checkbox']) {\n list-style: none;\n margin-inline-start: -1.5em;\n}\n\n.doc li > input[type='checkbox'] {\n margin-inline-end: 0.5em;\n vertical-align: middle;\n}\n\n/* ---------- callout ---------- */\n\n.mk-callout {\n border: 1px solid var(--mk-callout-border, #94a3b8);\n border-left-width: 4px;\n border-radius: 6px;\n padding: 0.75rem 1rem;\n background: var(--mk-callout-bg, #f8fafc);\n}\n\n.mk-callout--info {\n --mk-callout-border: #3b82f6;\n --mk-callout-bg: #eff6ff;\n}\n\n.mk-callout--warning {\n --mk-callout-border: #d97706;\n --mk-callout-bg: #fffbeb;\n}\n\n.mk-callout--danger {\n --mk-callout-border: #dc2626;\n --mk-callout-bg: #fef2f2;\n}\n\n.mk-callout__header {\n display: flex;\n align-items: center;\n gap: 0.5rem;\n font-weight: 600;\n}\n\n.mk-callout__icon {\n line-height: 1;\n}\n\n/*\n * `display: flex; flex-direction: column` is load-bearing here, not just\n * `> * + *` margin: several block-holding components (`stat`, `badge`)\n * declare their OWN outer display as `inline-flex`/`inline-block` (correct\n * for sitting inline in a sentence), so without a flex/grid parent they\n * flow side-by-side on the same line instead of stacking — a\n * `margin-block-start` on a same-line inline-level sibling creates no\n * visible gap. Making the body a column flex container forces every\n * child's *used* display to blockify (CSS Flexbox: a flex item's outer\n * display is always block-level), so they stack regardless of the\n * component's own declared display, and the margin rule below then has\n * something to actually separate.\n */\n.mk-callout__body {\n display: flex;\n flex-direction: column;\n}\n\n.mk-callout__body > :first-child {\n margin-block-start: 0.5rem;\n}\n\n.mk-callout__body > * + * {\n margin-block-start: 0.5rem;\n}\n\n/* ---------- kbd ---------- */\n\n.mk-kbd {\n display: inline-block;\n vertical-align: baseline;\n line-height: 1.4;\n height: 1.4em;\n padding: 0 0.4em;\n font-family: ui-monospace, 'SFMono-Regular', Menlo, monospace;\n font-size: 0.85em;\n border: 1px solid #c9ccd1;\n border-bottom-width: 2px;\n border-radius: 4px;\n background: #f6f7f9;\n box-shadow: inset 0 -1px 0 rgba(0, 0, 0, 0.05);\n}\n\n/* ---------- rating ---------- */\n\n.mk-rating {\n display: inline-flex;\n gap: 0.15em;\n font-size: 1.1em;\n color: #cbd5e1;\n}\n\n.mk-rating__star--filled {\n color: #f59e0b;\n}\n\n/* ---------- value interpolation ---------- */\n\n.mk-value {\n display: inline;\n vertical-align: baseline;\n}\n\n.mk-value--stale {\n color: #92400e;\n border-bottom: 1px dashed #d97706;\n}\n\n.mk-value--missing {\n font-family: ui-monospace, 'SFMono-Regular', Menlo, monospace;\n font-size: 0.9em;\n color: #94a3b8;\n font-style: italic;\n}\n\n/*\n * Failure-kind modifiers (`@markii/runtime`'s `FailureKind`, docs/scripting.md) —\n * layered on top of `.mk-value--missing`, only ever added when the\n * resolution's root entry carried a `failureKind` on a genuine error (see\n * `ValueDirective`). Each just tints the dashed-underline treatment a\n * distinct hue so a reader can tell \"script bug\" apart from \"needs\n * permission\" / \"needs a manual run\" / \"hit a resource limit\" at a glance,\n * with the full message still available via the `title` tooltip.\n */\n.mk-value--script-error {\n border-bottom: 1px dashed #ef4444;\n}\n\n.mk-value--capability-denied {\n border-bottom: 1px dashed #d97706;\n}\n\n.mk-value--tier-blocked {\n border-bottom: 1px dashed #2563eb;\n}\n\n.mk-value--limit {\n border-bottom: 1px dashed #7c3aed;\n}\n\n/* ---------- script marker ---------- */\n\n.mk-script {\n border: 1px solid #d4d4d8;\n border-radius: 6px;\n background: #fafafa;\n}\n\n.mk-script__summary {\n cursor: pointer;\n padding: 0.5rem 0.75rem;\n font-family: ui-monospace, 'SFMono-Regular', Menlo, monospace;\n font-size: 0.85em;\n color: #52525b;\n}\n\n.mk-script[open] > .mk-script__summary {\n border-bottom: 1px solid #e4e4e7;\n}\n\n.mk-script__code {\n margin: 0.75rem;\n margin-block-start: 0;\n}\n\n.mk-script__empty {\n margin: 0.75rem;\n margin-block-start: 0;\n font-size: 0.85em;\n font-style: italic;\n color: #94a3b8;\n}\n\n/* ---------- details ---------- */\n\n.mk-details {\n border: 1px solid #d4d4d8;\n border-radius: 6px;\n padding: 0.75rem 1rem;\n background: #fafafa;\n}\n\n.mk-details__summary {\n cursor: pointer;\n font-weight: 600;\n}\n\n.mk-details[open] > .mk-details__summary {\n margin-block-end: 0.5rem;\n}\n\n/* Blockifies inline-flex/inline-block children (`stat`, `badge`, ...) so they stack — see `.mk-callout__body`'s comment above for why this is load-bearing. */\n.mk-details__body {\n display: flex;\n flex-direction: column;\n}\n\n.mk-details__body > :first-child {\n margin-block-start: 0.5rem;\n}\n\n.mk-details__body > * + * {\n margin-block-start: 0.5rem;\n}\n\n/* ---------- card ---------- */\n\n.mk-card {\n border: 1px solid #e4e4e7;\n border-radius: 8px;\n padding: 1rem;\n background: #fff;\n}\n\n.mk-card__title {\n font-weight: 600;\n margin-block-end: 0.5rem;\n}\n\n/* Blockifies inline-flex/inline-block children (`stat`, `badge`, ...) so they stack — see `.mk-callout__body`'s comment above for why this is load-bearing. */\n.mk-card__body {\n display: flex;\n flex-direction: column;\n}\n\n.mk-card__body > :first-child {\n margin-block-start: 0;\n}\n\n.mk-card__body > * + * {\n margin-block-start: 0.5rem;\n}\n\n/* ---------- badge ---------- */\n\n.mk-badge {\n display: inline-block;\n vertical-align: baseline;\n line-height: 1.4;\n height: 1.4em;\n padding: 0 0.6em;\n font-size: 0.8em;\n font-weight: 600;\n border-radius: 999px;\n color: var(--mk-badge-fg, #334155);\n background: var(--mk-badge-bg, #e2e8f0);\n}\n\n.mk-badge--info {\n --mk-badge-fg: #1d4ed8;\n --mk-badge-bg: #dbeafe;\n}\n\n.mk-badge--success {\n --mk-badge-fg: #15803d;\n --mk-badge-bg: #dcfce7;\n}\n\n.mk-badge--warning {\n --mk-badge-fg: #b45309;\n --mk-badge-bg: #fef3c7;\n}\n\n.mk-badge--danger {\n --mk-badge-fg: #b91c1c;\n --mk-badge-bg: #fee2e2;\n}\n\n/* ---------- figure ---------- */\n\n.mk-figure {\n margin: 0;\n}\n\n.mk-figure__img {\n display: block;\n max-width: 100%;\n height: auto;\n border-radius: 6px;\n}\n\n.mk-figure__caption {\n margin-block-start: 0.5rem;\n font-size: 0.9em;\n color: #52525b;\n}\n\n.mk-figure__caption > :first-child {\n margin-block-start: 0;\n}\n\n.mk-figure__caption > * + * {\n margin-block-start: 0.5rem;\n}\n\n/* ---------- tabs ---------- */\n\n.mk-tabs__list {\n display: flex;\n gap: 0.25rem;\n border-bottom: 1px solid #e4e4e7;\n}\n\n.mk-tabs__button {\n cursor: pointer;\n border: none;\n background: none;\n padding: 0.5rem 0.9rem;\n font: inherit;\n font-weight: 600;\n color: #71717a;\n border-bottom: 2px solid transparent;\n margin-block-end: -1px;\n}\n\n.mk-tabs__button--active {\n color: #1d4ed8;\n border-bottom-color: #1d4ed8;\n}\n\n/* Blockifies inline-flex/inline-block children (`stat`, `badge`, ...) so they stack — see `.mk-callout__body`'s comment above for why this is load-bearing. */\n.mk-tab {\n display: flex;\n flex-direction: column;\n padding-block-start: 0.75rem;\n}\n\n.mk-tab > :first-child {\n margin-block-start: 0;\n}\n\n.mk-tab > * + * {\n margin-block-start: 0.5rem;\n}\n\n/* ---------- stat ---------- */\n\n.mk-stat {\n display: inline-flex;\n flex-direction: column;\n gap: 0.15rem;\n}\n\n.mk-stat__value {\n font-size: 1.8em;\n font-weight: 700;\n line-height: 1.2;\n}\n\n.mk-stat__label {\n font-size: 0.85em;\n color: #71717a;\n}\n\n.mk-stat__delta {\n font-size: 0.85em;\n font-weight: 600;\n color: #71717a;\n}\n\n.mk-stat__delta--up {\n color: #15803d;\n}\n\n.mk-stat__delta--down {\n color: #b91c1c;\n}\n\n.mk-stat__delta--flat {\n color: #71717a;\n}\n\n/* ---------- progress ---------- */\n\n.mk-progress {\n display: flex;\n align-items: center;\n gap: 0.6rem;\n}\n\n.mk-progress__label {\n font-size: 0.85em;\n color: #52525b;\n flex: 0 0 auto;\n}\n\n.mk-progress__track {\n flex: 1 1 auto;\n height: 0.6rem;\n border-radius: 999px;\n background: #e4e4e7;\n overflow: hidden;\n}\n\n.mk-progress__bar {\n height: 100%;\n background: #3b82f6;\n border-radius: inherit;\n}\n\n.mk-progress__percent {\n flex: 0 0 auto;\n font-size: 0.85em;\n font-variant-numeric: tabular-nums;\n color: #52525b;\n}\n\n/* ---------- chart ---------- */\n\n.mk-chart {\n display: block;\n max-width: 100%;\n}\n\n.mk-chart__line {\n fill: none;\n stroke: #3b82f6;\n stroke-width: 2;\n stroke-linejoin: round;\n stroke-linecap: round;\n}\n\n.mk-chart__bar {\n fill: #3b82f6;\n}\n\n.mk-chart--empty {\n display: flex;\n align-items: center;\n justify-content: center;\n font-size: 0.8em;\n font-style: italic;\n color: #94a3b8;\n border: 1px dashed #d4d4d8;\n border-radius: 6px;\n}\n\n/* ---------- data-binding state (docs/scripting.md) ---------- */\n\n/*\n * The block twins of the `.mk-value--*` markers above: a data-bound\n * component (`stat`, `progress`, `chart`) whose `data=` binding is stale or\n * failed keeps its ordinary QUIET body — `—`, a `0%` bar, `no data` — and\n * says so only through these hooks plus its `title` tooltip. No rule here\n * may add body text (`content:` is deliberately absent) or an outer margin;\n * the failure taxonomy's wording lives in one place only, `components/\n * failure-presentation.ts`.\n *\n * The hues match `.mk-value--*` exactly, so the same failing name reads the\n * same whether it surfaced inline via `:value[...]` or as a component.\n */\n.mk-stat--stale,\n.mk-progress--stale,\n.mk-chart--stale {\n opacity: 0.8;\n}\n\n.mk-stat--script-error,\n.mk-progress--script-error,\n.mk-chart--script-error {\n border-bottom: 2px solid #ef4444;\n}\n\n.mk-stat--capability-denied,\n.mk-progress--capability-denied,\n.mk-chart--capability-denied {\n border-bottom: 2px solid #d97706;\n}\n\n.mk-stat--tier-blocked,\n.mk-progress--tier-blocked,\n.mk-chart--tier-blocked {\n border-bottom: 2px solid #2563eb;\n}\n\n.mk-stat--limit,\n.mk-progress--limit,\n.mk-chart--limit {\n border-bottom: 2px solid #7c3aed;\n}\n\n/* ---------- layout presets (docs/format.md) ---------- */\n\n/*\n * `render.tsx` only wraps a directive in a `mk-width-*`/`mk-align-*` `<div>`\n * when at least one of these classes actually applies, so the wrapper below\n * IS the element sitting directly in `.doc`'s rhythm flow — never\n * `margin-block` here, that's `.doc > * + *`'s job alone; setting it on the\n * wrapper too would double up spacing. `max-width: min(<size>, 100%)` keeps\n * every preset from ever overflowing the document column, even on a narrow\n * viewport where the size below is wider than the column itself.\n */\n.mk-width-narrow {\n max-width: min(30rem, 100%);\n}\n\n.mk-width-wide {\n max-width: min(64rem, 100%);\n}\n\n/* \"full\" is the full available column width — not a viewport-bleed hack with negative margins. */\n.mk-width-full {\n max-width: 100%;\n}\n\n.mk-align-left {\n margin-inline-end: auto;\n}\n\n.mk-align-center {\n margin-inline: auto;\n}\n\n.mk-align-right {\n margin-inline-start: auto;\n}\n\n/*\n * The five `:::center`/`:::right`/`:::wide`/`:::narrow`/`:::full` layout\n * wrappers (docs/format.md, `layout-wrapper.tsx`) reuse the `mk-width-*`/\n * `mk-align-*` classes above and add `mk-layout` on top for the rules below,\n * which only make sense on a container that has its OWN plain-markdown\n * children (a table, an image, a paragraph) rather than on the bare\n * attribute-interception wrapper `render.tsx` emits for `width=`/`align=`.\n *\n * No outer margin on `.mk-layout` itself, same rule as every component\n * (Architecture rule 4) — `.doc > * + *` spaces the wrapper against its\n * siblings. This rule instead restores RHYTHM *inside* the wrapper's own\n * scope, mirroring `.mk-card__body > * + *`: without it, the wrapper's\n * children would have no spacing between them at all, since `.doc > * + *`\n * only ever sees the wrapper `<div>` as a whole, never reaches inside it.\n */\n.mk-layout > * + * {\n margin-block-start: 1rem;\n}\n\n/* `center`/`right` additionally set text alignment for everything in scope — not just the shrink-to-fit block alignment below. */\n.mk-layout.mk-align-center {\n text-align: center;\n}\n\n.mk-layout.mk-align-right {\n text-align: right;\n}\n\n/*\n * `.mk-align-center`/`.mk-align-right` above (shared with the `align=`\n * attribute wrapper) only center/right-align the wrapper `<div>` ITSELF\n * within ITS container — they say nothing about the wrapper's own children.\n * These two rules do that: they shrink-to-fit and align every direct child\n * of the wrapper's scope, which is what actually centers/right-aligns a\n * narrower-than-column table or image sitting inside `:::center`/`:::right`.\n */\n.mk-layout.mk-align-center > * {\n margin-inline: auto;\n}\n\n.mk-layout.mk-align-right > * {\n margin-inline-start: auto;\n margin-inline-end: 0;\n}\n\n/*\n * `.doc table` (above) sets `display: block` for horizontal-scroll\n * overflow, which also makes the table fill its column — defeating\n * shrink-to-fit alignment before it can even apply. These two rules size a\n * table down to its content instead, so the `margin-inline` rules above\n * have a narrower box to actually move. `.doc th`/`.doc td` set\n * `text-align: left` directly on the cells, so this scope's `text-align`\n * never flips table cell text — only the table's own position in the\n * scope, and any non-table text alongside it.\n */\n.mk-layout.mk-align-center > table,\n.mk-layout.mk-align-right > table {\n width: fit-content;\n max-width: 100%;\n}\n\n/* ---------- row ---------- */\n\n.mk-row {\n display: grid;\n gap: 1rem;\n grid-template-columns: repeat(auto-fit, minmax(min(16rem, 100%), 1fr));\n}\n\n/*\n * `minmax(0, 1fr)` — a bare `1fr` track can still grow past an equal share\n * to fit a wide intrinsic-content cell (e.g. a `chart` SVG, a long code\n * span); pinning the minimum to `0` is what keeps such a cell, and\n * therefore the whole row, from blowing out past its column.\n */\n.mk-row--cols-2 {\n grid-template-columns: repeat(2, minmax(0, 1fr));\n}\n\n.mk-row--cols-3 {\n grid-template-columns: repeat(3, minmax(0, 1fr));\n}\n\n.mk-row--cols-4 {\n grid-template-columns: repeat(4, minmax(0, 1fr));\n}\n\n/*\n * No separate blockification rule is needed here, unlike `.mk-card__body`'s:\n * a grid container (which `.mk-row` is) already promotes every direct\n * child's outer display to block-level as a grid item, same as a flex\n * container does — an inline-flex/inline-block component (`stat`, `badge`,\n * ...) used directly as a row cell already stacks/sizes as a proper grid\n * cell with no extra rule required.\n */\n\n@media (max-width: 40rem) {\n .mk-row,\n .mk-row--cols-2,\n .mk-row--cols-3,\n .mk-row--cols-4 {\n grid-template-columns: 1fr;\n }\n}\n\n/*\n * `:::row{align=...}` (docs/format.md): `align` is intercepted like on any\n * other block directive (`render.tsx`/`layout.ts`), wrapping `.mk-row` in\n * the same generic `mk-align-*` `<div>` every other directive gets — but\n * `margin-inline: auto` (the rule above, under \"layout presets\") has\n * nothing to move: a `.mk-row` grid already fills the document column, so\n * shrink-to-fit placement of the row itself is meaningless. These three\n * rules give `align` on a row its own, more useful meaning instead: they\n * set `text-align` on the row, which every cell's content INHERITS (plain\n * CSS inheritance — `.mk-cell` sets no `text-align` of its own, so nothing\n * blocks it). Scoped to `> .mk-row` specifically, so a non-row directive's\n * `align` wrapper is completely unaffected.\n *\n * Locality wins for free: a more local `:::center`/`:::left`/`:::right`\n * wrapper written inside one cell (`.mk-layout.mk-align-*` above) sets\n * `text-align` directly on itself, and an element's own declared value\n * always wins over one it only inherited — no specificity fight needed.\n */\n.mk-align-left > .mk-row {\n text-align: left;\n}\n\n.mk-align-center > .mk-row {\n text-align: center;\n}\n\n.mk-align-right > .mk-row {\n text-align: right;\n}\n\n/* ---------- cell ---------- */\n\n/*\n * `:::cell` (`cell.tsx`) is a TRANSPARENT grouping container: it has no\n * border, background, padding, or outer margin of its own — its whole job is\n * making several blocks count as ONE `.mk-row` grid cell, so anything visual\n * here would betray that. `.mk-cell` therefore has no rule at all; only the\n * rhythm-restoring rule below exists, mirroring `.mk-layout > * + *`:\n * `.doc > * + *` sees the cell as a single box and never reaches inside it,\n * so without this its children would sit flush against each other.\n */\n.mk-cell > * + * {\n margin-block-start: 1rem;\n}\n\n/* ---------- unknown directive fallback ---------- */\n\n.mk-unknown {\n border: 1px dashed #94a3b8;\n border-radius: 6px;\n color: #475569;\n}\n\n.mk-unknown--block {\n padding: 0.75rem 1rem;\n}\n\n.mk-unknown--inline {\n display: inline-flex;\n align-items: baseline;\n gap: 0.4em;\n padding: 0 0.4em;\n vertical-align: baseline;\n}\n\n.mk-unknown__label {\n font-size: 0.85em;\n font-style: italic;\n margin: 0;\n}\n\n.mk-unknown__content > :first-child {\n margin-block-start: 0.5rem;\n}\n\n.mk-unknown__content > * + * {\n margin-block-start: 0.5rem;\n}\n";
|
|
5
|
+
export const DOC_CSS = "/*\n * Document rhythm: components own their insides only, never outer margins.\n * This single rule spaces every block-level child of `.doc` identically —\n * paragraphs, headings, and components alike — so new components always\n * sit correctly in the flow with zero per-component tuning.\n */\n.doc > * + * {\n margin-block-start: 1rem;\n}\n\n/*\n * ============================================================================\n * TIER 1 TOKENS — the theming contract\n * ============================================================================\n * Every host that wants a themed (in particular, a dark) `.doc` remaps ONLY\n * these custom properties, deliberately scoped to `.doc` and not `:root`:\n * `doc.css` is a library stylesheet embedded into other people's pages\n * (Obsidian injects it globally), so it must stay polite and never claim\n * `:root`. A host theme layer loaded after this one, at the same\n * specificity, simply redeclares the ones below on `.doc` (or a more\n * specific selector) to win the cascade — see `apps/vscode/src/webview/\n * theme.css` and `apps/obsidian/src/obsidian-theme.css`.\n *\n * Nine neutrals + five semantic hues. This list is deliberately small: a\n * host maps ~14 tokens instead of ~60 individual selectors, and a\n * derivation layer below (Tier 2) builds every finer shade FROM these, so\n * remapping these 14 makes every callout/badge/chart/etc. variant\n * theme-correct for free.\n */\n.doc {\n /* ---------- neutrals ---------- */\n --mk-bg: #fff; /* page ground */\n --mk-raised: #fff; /* raised surface for cards — distinct from ground; dark themes need real separation here where light themes get it from the border alone */\n --mk-fg: #1a1a1a; /* body text */\n --mk-surface: #f4f4f5; /* subtle fill one step off the ground: pre, kbd, script marker, details, default callout, zebra rows */\n --mk-surface-strong: #f0f0f2; /* second step off the ground: inline code, th, progress track, default badge background */\n --mk-border: #e4e4e7; /* hairlines */\n --mk-muted: #52525b; /* secondary text: captions, labels, script summary */\n --mk-faint: #94a3b8; /* tertiary/empty: missing values, empty-chart text, unfilled stars */\n --mk-accent: #3b82f6; /* the single interactive/brand color: active tab, progress bar, chart stroke/fill */\n --mk-on-accent: #fff; /* ink that sits ON a solid --mk-accent fill; a host must supply this because it cannot be derived: it depends on the accent's own lightness, not on the page palette */\n\n /* ---------- semantic hues ---------- */\n --mk-info: #3b82f6;\n --mk-success: #15803d;\n --mk-warning: #d97706;\n --mk-danger: #dc2626;\n --mk-limit: #7c3aed; /* the purple, used for the \"hit a resource limit\" failure kind */\n}\n\n/*\n * ============================================================================\n * TIER 2 DERIVATION FORMULAS\n * ============================================================================\n * Every finer shade `doc.css` needs (a callout's tinted background, a\n * badge's tinted text) is a MIX of a Tier 1 hue against `--mk-bg`/`--mk-fg`,\n * never a literal of its own. Because the mix targets those two tokens\n * specifically, a dark host palette flips the derived shade's polarity\n * correctly with no extra work on the host's part — the whole point of\n * this refactor.\n *\n * Exactly three named percentages are used anywhere in this file. A future\n * component reuses one of these three; it does not invent a fourth.\n *\n * --mk-mix-variant-fill: color-mix(in srgb, <hue> 14%, var(--mk-bg))\n * A quiet tinted background — callout body fill.\n * --mk-mix-strong-fill: color-mix(in srgb, <hue> 18%, var(--mk-bg))\n * A slightly stronger tinted background — badge background.\n * --mk-mix-ink: color-mix(in srgb, <hue> 85%, var(--mk-fg))\n * A hue nudged toward body text — usable as ink (badge text, star\n * color) or as a border (callout border).\n *\n * `color-mix()` is unsupported in most email clients, and a custom property\n * whose value fails to parse does not fall back — the declaration goes\n * invalid-at-computed-value and effectively vanishes. `doc.css` is embedded\n * verbatim into `@markii/html`'s `exportHtmlDocument`, whose documented\n * targets include email and archive output, so every derived token below is\n * defined TWICE: first as a literal hex (today's existing light-mode\n * value), then, guarded by `@supports (color: color-mix(in srgb, red,\n * red))`, redefined via the real mix. A modern browser or either Electron\n * host gets live derivation that tracks a remapped Tier 1 palette; an old\n * email client silently keeps exactly today's light palette.\n */\n.doc {\n /* ---- literal light-mode fallback (used verbatim where color-mix is unsupported) ---- */\n --mk-info-fill: #eff6ff;\n --mk-info-strong-fill: #dbeafe;\n --mk-info-ink: #3b82f6;\n --mk-success-strong-fill: #dcfce7;\n --mk-success-ink: #15803d;\n --mk-warning-fill: #fffbeb;\n --mk-warning-strong-fill: #fef3c7;\n --mk-warning-ink: #d97706;\n --mk-danger-fill: #fef2f2;\n --mk-danger-strong-fill: #fee2e2;\n --mk-danger-ink: #dc2626;\n --mk-limit-ink: #7c3aed;\n /*\n * The keycap's inset depth line. Not a themed hue, but not theme-neutral\n * either: a low-alpha BLACK line is invisible on a dark surface, so the\n * literal below is only the no-`color-mix` fallback (where the palette is\n * the light one anyway). The derivation in the `@supports` block below\n * expresses it against `--mk-fg` instead, so it flips to a light line\n * when a host supplies a dark palette, which is the correct depth cue\n * there. Kept in this block, not the Tier 1 block above, so its literal\n * stays inside a block the no-raw-color-literal test allows.\n */\n --mk-shadow-sm: rgba(0, 0, 0, 0.05);\n}\n\n@supports (color: color-mix(in srgb, red, red)) {\n .doc {\n --mk-shadow-sm: color-mix(in srgb, var(--mk-fg) 8%, transparent);\n\n --mk-info-fill: color-mix(in srgb, var(--mk-info) 14%, var(--mk-bg));\n --mk-info-strong-fill: color-mix(in srgb, var(--mk-info) 18%, var(--mk-bg));\n --mk-info-ink: color-mix(in srgb, var(--mk-info) 85%, var(--mk-fg));\n\n --mk-success-strong-fill: color-mix(\n in srgb,\n var(--mk-success) 18%,\n var(--mk-bg)\n );\n --mk-success-ink: color-mix(in srgb, var(--mk-success) 85%, var(--mk-fg));\n\n --mk-warning-fill: color-mix(in srgb, var(--mk-warning) 14%, var(--mk-bg));\n --mk-warning-strong-fill: color-mix(\n in srgb,\n var(--mk-warning) 18%,\n var(--mk-bg)\n );\n --mk-warning-ink: color-mix(in srgb, var(--mk-warning) 85%, var(--mk-fg));\n\n --mk-danger-fill: color-mix(in srgb, var(--mk-danger) 14%, var(--mk-bg));\n --mk-danger-strong-fill: color-mix(\n in srgb,\n var(--mk-danger) 18%,\n var(--mk-bg)\n );\n --mk-danger-ink: color-mix(in srgb, var(--mk-danger) 85%, var(--mk-fg));\n\n --mk-limit-ink: color-mix(in srgb, var(--mk-limit) 85%, var(--mk-fg));\n }\n}\n\n.doc {\n color: var(--mk-fg);\n font-family:\n system-ui,\n -apple-system,\n 'Segoe UI',\n sans-serif;\n line-height: 1.6;\n}\n\n.doc pre {\n overflow-x: auto;\n background: var(--mk-surface);\n padding: 0.75rem 1rem;\n border-radius: 6px;\n}\n\n.doc code {\n background: var(--mk-surface-strong);\n border-radius: 3px;\n padding: 0.1em 0.35em;\n font-size: 0.9em;\n}\n\n.doc pre code {\n background: none;\n padding: 0;\n}\n\n/* ---------- GFM table ---------- */\n\n/*\n * `display: block` on the table itself (rather than wrapping it in an\n * extra element the renderer doesn't otherwise inject) is what makes a wide\n * table scroll horizontally instead of overflowing the page or the doc\n * column — the table box becomes independently scrollable content, the\n * same trick used by GitHub's own Markdown rendering.\n */\n.doc table {\n display: block;\n overflow-x: auto;\n border-collapse: collapse;\n font-size: 0.95em;\n}\n\n.doc th,\n.doc td {\n border: 1px solid var(--mk-border);\n padding: 0.4rem 0.75rem;\n text-align: left;\n}\n\n.doc th {\n font-weight: 600;\n background: var(--mk-surface-strong);\n}\n\n.doc tr:nth-child(even) {\n background: var(--mk-surface);\n}\n\n/* ---------- GFM task list ---------- */\n\n/*\n * `li:has(> input[type=\"checkbox\"])` scopes bullet removal + checkbox\n * alignment to task-list items only — an ordinary `<ul>`/`<ol>` item keeps\n * its normal bullet/number, since GFM only adds a leading `<input>` to\n * items that used `- [ ]`/`- [x]` syntax.\n */\n.doc li:has(> input[type='checkbox']) {\n list-style: none;\n margin-inline-start: -1.5em;\n}\n\n.doc li > input[type='checkbox'] {\n margin-inline-end: 0.5em;\n vertical-align: middle;\n}\n\n/* ---------- callout ---------- */\n\n.mk-callout {\n border: 1px solid var(--mk-callout-border, var(--mk-border));\n border-left-width: 4px;\n border-radius: 6px;\n padding: 0.75rem 1rem;\n background: var(--mk-callout-bg, var(--mk-surface));\n}\n\n.mk-callout--info {\n --mk-callout-border: var(--mk-info-ink);\n --mk-callout-bg: var(--mk-info-fill);\n}\n\n.mk-callout--warning {\n --mk-callout-border: var(--mk-warning-ink);\n --mk-callout-bg: var(--mk-warning-fill);\n}\n\n.mk-callout--danger {\n --mk-callout-border: var(--mk-danger-ink);\n --mk-callout-bg: var(--mk-danger-fill);\n}\n\n.mk-callout__header {\n display: flex;\n align-items: center;\n gap: 0.5rem;\n font-weight: 600;\n}\n\n.mk-callout__icon {\n line-height: 1;\n}\n\n/*\n * `display: flex; flex-direction: column` is load-bearing here, not just\n * `> * + *` margin: several block-holding components (`stat`, `badge`)\n * declare their OWN outer display as `inline-flex`/`inline-block` (correct\n * for sitting inline in a sentence), so without a flex/grid parent they\n * flow side-by-side on the same line instead of stacking — a\n * `margin-block-start` on a same-line inline-level sibling creates no\n * visible gap. Making the body a column flex container forces every\n * child's *used* display to blockify (CSS Flexbox: a flex item's outer\n * display is always block-level), so they stack regardless of the\n * component's own declared display, and the margin rule below then has\n * something to actually separate.\n */\n.mk-callout__body {\n display: flex;\n flex-direction: column;\n}\n\n.mk-callout__body > :first-child {\n margin-block-start: 0.5rem;\n}\n\n.mk-callout__body > * + * {\n margin-block-start: 0.5rem;\n}\n\n/* ---------- kbd ---------- */\n\n.mk-kbd {\n display: inline-block;\n vertical-align: baseline;\n line-height: 1.4;\n height: 1.4em;\n padding: 0 0.4em;\n font-family: ui-monospace, 'SFMono-Regular', Menlo, monospace;\n font-size: 0.85em;\n border: 1px solid var(--mk-border);\n border-bottom-width: 2px;\n border-radius: 4px;\n background: var(--mk-surface);\n box-shadow: inset 0 -1px 0 var(--mk-shadow-sm);\n}\n\n/* ---------- rating ---------- */\n\n.mk-rating {\n display: inline-flex;\n gap: 0.15em;\n font-size: 1.1em;\n color: var(--mk-faint);\n}\n\n.mk-rating__star--filled {\n color: var(--mk-warning);\n}\n\n/* ---------- value interpolation ---------- */\n\n.mk-value {\n display: inline;\n vertical-align: baseline;\n}\n\n.mk-value--stale {\n color: var(--mk-warning-ink);\n border-bottom: 1px dashed var(--mk-warning-ink);\n}\n\n.mk-value--missing {\n font-family: ui-monospace, 'SFMono-Regular', Menlo, monospace;\n font-size: 0.9em;\n color: var(--mk-faint);\n font-style: italic;\n}\n\n/*\n * Failure-kind modifiers (`@markii/runtime`'s `FailureKind`, docs/scripting.md) —\n * layered on top of `.mk-value--missing`, only ever added when the\n * resolution's root entry carried a `failureKind` on a genuine error (see\n * `ValueDirective`). Each just tints the dashed-underline treatment a\n * distinct hue so a reader can tell \"script bug\" apart from \"needs\n * permission\" / \"needs a manual run\" / \"hit a resource limit\" at a glance,\n * with the full message still available via the `title` tooltip.\n *\n * Failure kinds map onto the Tier 1 semantic hues: script-error -> danger,\n * capability-denied -> warning, tier-blocked -> info, limit -> limit.\n */\n.mk-value--script-error {\n border-bottom: 1px dashed var(--mk-danger-ink);\n}\n\n.mk-value--capability-denied {\n border-bottom: 1px dashed var(--mk-warning-ink);\n}\n\n.mk-value--tier-blocked {\n border-bottom: 1px dashed var(--mk-info-ink);\n}\n\n.mk-value--limit {\n border-bottom: 1px dashed var(--mk-limit-ink);\n}\n\n/* ---------- script marker ---------- */\n\n.mk-script {\n border: 1px solid var(--mk-border);\n border-radius: 6px;\n background: var(--mk-surface);\n}\n\n.mk-script__summary {\n cursor: pointer;\n padding: 0.5rem 0.75rem;\n font-family: ui-monospace, 'SFMono-Regular', Menlo, monospace;\n font-size: 0.85em;\n color: var(--mk-muted);\n}\n\n.mk-script[open] > .mk-script__summary {\n border-bottom: 1px solid var(--mk-border);\n}\n\n.mk-script__code {\n margin: 0.75rem;\n margin-block-start: 0;\n}\n\n.mk-script__empty {\n margin: 0.75rem;\n margin-block-start: 0;\n font-size: 0.85em;\n font-style: italic;\n color: var(--mk-faint);\n}\n\n/* ---------- details ---------- */\n\n.mk-details {\n border: 1px solid var(--mk-border);\n border-radius: 6px;\n padding: 0.75rem 1rem;\n background: var(--mk-surface);\n}\n\n.mk-details__summary {\n cursor: pointer;\n font-weight: 600;\n}\n\n.mk-details[open] > .mk-details__summary {\n margin-block-end: 0.5rem;\n}\n\n/* Blockifies inline-flex/inline-block children (`stat`, `badge`, ...) so they stack — see `.mk-callout__body`'s comment above for why this is load-bearing. */\n.mk-details__body {\n display: flex;\n flex-direction: column;\n}\n\n.mk-details__body > :first-child {\n margin-block-start: 0.5rem;\n}\n\n.mk-details__body > * + * {\n margin-block-start: 0.5rem;\n}\n\n/* ---------- card ---------- */\n\n.mk-card {\n border: 1px solid var(--mk-border);\n border-radius: 8px;\n padding: 1rem;\n background: var(--mk-raised);\n}\n\n.mk-card__title {\n font-weight: 600;\n margin-block-end: 0.5rem;\n}\n\n/* Blockifies inline-flex/inline-block children (`stat`, `badge`, ...) so they stack — see `.mk-callout__body`'s comment above for why this is load-bearing. */\n.mk-card__body {\n display: flex;\n flex-direction: column;\n}\n\n.mk-card__body > :first-child {\n margin-block-start: 0;\n}\n\n.mk-card__body > * + * {\n margin-block-start: 0.5rem;\n}\n\n/* ---------- badge ---------- */\n\n.mk-badge {\n display: inline-block;\n vertical-align: baseline;\n line-height: 1.4;\n height: 1.4em;\n padding: 0 0.6em;\n font-size: 0.8em;\n font-weight: 600;\n border-radius: 999px;\n color: var(--mk-badge-fg, var(--mk-fg));\n background: var(--mk-badge-bg, var(--mk-surface-strong));\n}\n\n.mk-badge--info {\n --mk-badge-fg: var(--mk-info-ink);\n --mk-badge-bg: var(--mk-info-strong-fill);\n}\n\n.mk-badge--success {\n --mk-badge-fg: var(--mk-success-ink);\n --mk-badge-bg: var(--mk-success-strong-fill);\n}\n\n.mk-badge--warning {\n --mk-badge-fg: var(--mk-warning-ink);\n --mk-badge-bg: var(--mk-warning-strong-fill);\n}\n\n.mk-badge--danger {\n --mk-badge-fg: var(--mk-danger-ink);\n --mk-badge-bg: var(--mk-danger-strong-fill);\n}\n\n/* ---------- figure ---------- */\n\n.mk-figure {\n margin: 0;\n}\n\n.mk-figure__img {\n display: block;\n max-width: 100%;\n height: auto;\n border-radius: 6px;\n}\n\n.mk-figure__caption {\n margin-block-start: 0.5rem;\n font-size: 0.9em;\n color: var(--mk-muted);\n}\n\n.mk-figure__caption > :first-child {\n margin-block-start: 0;\n}\n\n.mk-figure__caption > * + * {\n margin-block-start: 0.5rem;\n}\n\n/* ---------- tabs ---------- */\n\n.mk-tabs__list {\n display: flex;\n gap: 0.25rem;\n border-bottom: 1px solid var(--mk-border);\n}\n\n.mk-tabs__button {\n cursor: pointer;\n border: none;\n background: none;\n padding: 0.5rem 0.9rem;\n font: inherit;\n font-weight: 600;\n color: var(--mk-muted);\n border-bottom: 2px solid transparent;\n margin-block-end: -1px;\n}\n\n.mk-tabs__button--active {\n color: var(--mk-accent);\n border-bottom-color: var(--mk-accent);\n}\n\n/* Blockifies inline-flex/inline-block children (`stat`, `badge`, ...) so they stack — see `.mk-callout__body`'s comment above for why this is load-bearing. */\n.mk-tab {\n display: flex;\n flex-direction: column;\n padding-block-start: 0.75rem;\n}\n\n.mk-tab > :first-child {\n margin-block-start: 0;\n}\n\n.mk-tab > * + * {\n margin-block-start: 0.5rem;\n}\n\n/* ---------- stat ---------- */\n\n.mk-stat {\n display: inline-flex;\n flex-direction: column;\n gap: 0.15rem;\n}\n\n.mk-stat__value {\n font-size: 1.8em;\n font-weight: 700;\n line-height: 1.2;\n}\n\n.mk-stat__label {\n font-size: 0.85em;\n color: var(--mk-muted);\n}\n\n.mk-stat__delta {\n font-size: 0.85em;\n font-weight: 600;\n color: var(--mk-muted);\n}\n\n.mk-stat__delta--up {\n color: var(--mk-success-ink);\n}\n\n.mk-stat__delta--down {\n color: var(--mk-danger-ink);\n}\n\n.mk-stat__delta--flat {\n color: var(--mk-muted);\n}\n\n/* ---------- progress ---------- */\n\n.mk-progress {\n display: flex;\n align-items: center;\n gap: 0.6rem;\n}\n\n.mk-progress__label {\n font-size: 0.85em;\n color: var(--mk-muted);\n flex: 0 0 auto;\n}\n\n.mk-progress__track {\n flex: 1 1 auto;\n height: 0.6rem;\n border-radius: 999px;\n background: var(--mk-border);\n overflow: hidden;\n}\n\n.mk-progress__bar {\n height: 100%;\n background: var(--mk-accent);\n border-radius: inherit;\n}\n\n.mk-progress__percent {\n flex: 0 0 auto;\n font-size: 0.85em;\n font-variant-numeric: tabular-nums;\n color: var(--mk-muted);\n}\n\n/* ---------- chart ---------- */\n\n.mk-chart {\n display: block;\n max-width: 100%;\n}\n\n.mk-chart__line {\n fill: none;\n stroke: var(--mk-accent);\n stroke-width: 2;\n stroke-linejoin: round;\n stroke-linecap: round;\n}\n\n.mk-chart__bar {\n fill: var(--mk-accent);\n}\n\n.mk-chart--empty {\n display: flex;\n align-items: center;\n justify-content: center;\n font-size: 0.8em;\n font-style: italic;\n color: var(--mk-faint);\n border: 1px dashed var(--mk-border);\n border-radius: 6px;\n}\n\n/* ---------- data-binding state (docs/scripting.md) ---------- */\n\n/*\n * The block twins of the `.mk-value--*` markers above: a data-bound\n * component (`stat`, `progress`, `chart`) whose `data=` binding is stale or\n * failed keeps its ordinary QUIET body — `—`, a `0%` bar, `no data` — and\n * says so only through these hooks plus its `title` tooltip. No rule here\n * may add body text (`content:` is deliberately absent) or an outer margin;\n * the failure taxonomy's wording lives in one place only, `components/\n * failure-presentation.ts`.\n *\n * The hues match `.mk-value--*` exactly, so the same failing name reads the\n * same whether it surfaced inline via `:value[...]` or as a component.\n */\n.mk-stat--stale,\n.mk-progress--stale,\n.mk-chart--stale {\n opacity: 0.8;\n}\n\n.mk-stat--script-error,\n.mk-progress--script-error,\n.mk-chart--script-error {\n border-bottom: 2px solid var(--mk-danger-ink);\n}\n\n.mk-stat--capability-denied,\n.mk-progress--capability-denied,\n.mk-chart--capability-denied {\n border-bottom: 2px solid var(--mk-warning-ink);\n}\n\n.mk-stat--tier-blocked,\n.mk-progress--tier-blocked,\n.mk-chart--tier-blocked {\n border-bottom: 2px solid var(--mk-info-ink);\n}\n\n.mk-stat--limit,\n.mk-progress--limit,\n.mk-chart--limit {\n border-bottom: 2px solid var(--mk-limit-ink);\n}\n\n/* ---------- layout presets (docs/format.md) ---------- */\n\n/*\n * `render.tsx` only wraps a directive in a `mk-width-*`/`mk-align-*` `<div>`\n * when at least one of these classes actually applies, so the wrapper below\n * IS the element sitting directly in `.doc`'s rhythm flow — never\n * `margin-block` here, that's `.doc > * + *`'s job alone; setting it on the\n * wrapper too would double up spacing. `max-width: min(<size>, 100%)` keeps\n * every preset from ever overflowing the document column, even on a narrow\n * viewport where the size below is wider than the column itself.\n */\n.mk-width-narrow {\n max-width: min(30rem, 100%);\n}\n\n.mk-width-wide {\n max-width: min(64rem, 100%);\n}\n\n/* \"full\" is the full available column width — not a viewport-bleed hack with negative margins. */\n.mk-width-full {\n max-width: 100%;\n}\n\n.mk-align-left {\n margin-inline-end: auto;\n}\n\n.mk-align-center {\n margin-inline: auto;\n}\n\n.mk-align-right {\n margin-inline-start: auto;\n}\n\n/*\n * The five `:::center`/`:::right`/`:::wide`/`:::narrow`/`:::full` layout\n * wrappers (docs/format.md, `layout-wrapper.tsx`) reuse the `mk-width-*`/\n * `mk-align-*` classes above and add `mk-layout` on top for the rules below,\n * which only make sense on a container that has its OWN plain-markdown\n * children (a table, an image, a paragraph) rather than on the bare\n * attribute-interception wrapper `render.tsx` emits for `width=`/`align=`.\n *\n * No outer margin on `.mk-layout` itself, same rule as every component\n * (Architecture rule 4) — `.doc > * + *` spaces the wrapper against its\n * siblings. This rule instead restores RHYTHM *inside* the wrapper's own\n * scope, mirroring `.mk-card__body > * + *`: without it, the wrapper's\n * children would have no spacing between them at all, since `.doc > * + *`\n * only ever sees the wrapper `<div>` as a whole, never reaches inside it.\n */\n.mk-layout > * + * {\n margin-block-start: 1rem;\n}\n\n/* `center`/`right` additionally set text alignment for everything in scope — not just the shrink-to-fit block alignment below. */\n.mk-layout.mk-align-center {\n text-align: center;\n}\n\n.mk-layout.mk-align-right {\n text-align: right;\n}\n\n/*\n * `.mk-align-center`/`.mk-align-right` above (shared with the `align=`\n * attribute wrapper) only center/right-align the wrapper `<div>` ITSELF\n * within ITS container — they say nothing about the wrapper's own children.\n * These two rules do that: they shrink-to-fit and align every direct child\n * of the wrapper's scope, which is what actually centers/right-aligns a\n * narrower-than-column table or image sitting inside `:::center`/`:::right`.\n */\n.mk-layout.mk-align-center > * {\n margin-inline: auto;\n}\n\n.mk-layout.mk-align-right > * {\n margin-inline-start: auto;\n margin-inline-end: 0;\n}\n\n/*\n * `.doc table` (above) sets `display: block` for horizontal-scroll\n * overflow, which also makes the table fill its column — defeating\n * shrink-to-fit alignment before it can even apply. These two rules size a\n * table down to its content instead, so the `margin-inline` rules above\n * have a narrower box to actually move. `.doc th`/`.doc td` set\n * `text-align: left` directly on the cells, so this scope's `text-align`\n * never flips table cell text — only the table's own position in the\n * scope, and any non-table text alongside it.\n */\n.mk-layout.mk-align-center > table,\n.mk-layout.mk-align-right > table {\n width: fit-content;\n max-width: 100%;\n}\n\n/* ---------- row ---------- */\n\n.mk-row {\n display: grid;\n gap: 1rem;\n grid-template-columns: repeat(auto-fit, minmax(min(16rem, 100%), 1fr));\n}\n\n/*\n * `minmax(0, 1fr)` — a bare `1fr` track can still grow past an equal share\n * to fit a wide intrinsic-content cell (e.g. a `chart` SVG, a long code\n * span); pinning the minimum to `0` is what keeps such a cell, and\n * therefore the whole row, from blowing out past its column.\n */\n.mk-row--cols-2 {\n grid-template-columns: repeat(2, minmax(0, 1fr));\n}\n\n.mk-row--cols-3 {\n grid-template-columns: repeat(3, minmax(0, 1fr));\n}\n\n.mk-row--cols-4 {\n grid-template-columns: repeat(4, minmax(0, 1fr));\n}\n\n/*\n * No separate blockification rule is needed here, unlike `.mk-card__body`'s:\n * a grid container (which `.mk-row` is) already promotes every direct\n * child's outer display to block-level as a grid item, same as a flex\n * container does — an inline-flex/inline-block component (`stat`, `badge`,\n * ...) used directly as a row cell already stacks/sizes as a proper grid\n * cell with no extra rule required.\n */\n\n@media (max-width: 40rem) {\n .mk-row,\n .mk-row--cols-2,\n .mk-row--cols-3,\n .mk-row--cols-4 {\n grid-template-columns: 1fr;\n }\n}\n\n/*\n * `:::row{align=...}` (docs/format.md): `align` is intercepted like on any\n * other block directive (`render.tsx`/`layout.ts`), wrapping `.mk-row` in\n * the same generic `mk-align-*` `<div>` every other directive gets — but\n * `margin-inline: auto` (the rule above, under \"layout presets\") has\n * nothing to move: a `.mk-row` grid already fills the document column, so\n * shrink-to-fit placement of the row itself is meaningless. These three\n * rules give `align` on a row its own, more useful meaning instead: they\n * set `text-align` on the row, which every cell's content INHERITS (plain\n * CSS inheritance — `.mk-cell` sets no `text-align` of its own, so nothing\n * blocks it). Scoped to `> .mk-row` specifically, so a non-row directive's\n * `align` wrapper is completely unaffected.\n *\n * Locality wins for free: a more local `:::center`/`:::left`/`:::right`\n * wrapper written inside one cell (`.mk-layout.mk-align-*` above) sets\n * `text-align` directly on itself, and an element's own declared value\n * always wins over one it only inherited — no specificity fight needed.\n */\n.mk-align-left > .mk-row {\n text-align: left;\n}\n\n.mk-align-center > .mk-row {\n text-align: center;\n}\n\n.mk-align-right > .mk-row {\n text-align: right;\n}\n\n/* ---------- cell ---------- */\n\n/*\n * `:::cell` (`cell.tsx`) is a TRANSPARENT grouping container: it has no\n * border, background, padding, or outer margin of its own — its whole job is\n * making several blocks count as ONE `.mk-row` grid cell, so anything visual\n * here would betray that. `.mk-cell` therefore has no rule at all; only the\n * rhythm-restoring rule below exists, mirroring `.mk-layout > * + *`:\n * `.doc > * + *` sees the cell as a single box and never reaches inside it,\n * so without this its children would sit flush against each other.\n */\n.mk-cell > * + * {\n margin-block-start: 1rem;\n}\n\n/* ---------- empty inline-component marker ---------- */\n\n/*\n * Wraps an `inline: true` component that received no content\n * (`::badge{label=\"x\"}` instead of `:badge[x]`) — see `render.tsx`'s\n * `isRegisteredInline`/`isEmptyContent` and `render.ts`'s HTML-engine\n * mirror. The component underneath renders unchanged; this is a quiet\n * perceptual hook only (a faint dashed underline, matching the treatment\n * `.mk-value--stale` already gives a quiet-but-present state), with the\n * reason carried in the `title` tooltip rather than in the page.\n */\n.mk-inline-empty {\n border-bottom: 1px dashed var(--mk-faint);\n}\n\n/* ---------- unknown directive fallback ---------- */\n\n.mk-unknown {\n border: 1px dashed var(--mk-faint);\n border-radius: 6px;\n color: var(--mk-muted);\n}\n\n.mk-unknown--block {\n padding: 0.75rem 1rem;\n}\n\n.mk-unknown--inline {\n display: inline-flex;\n align-items: baseline;\n gap: 0.4em;\n padding: 0 0.4em;\n vertical-align: baseline;\n}\n\n.mk-unknown__label {\n font-size: 0.85em;\n font-style: italic;\n margin: 0;\n}\n\n.mk-unknown__content > :first-child {\n margin-block-start: 0.5rem;\n}\n\n.mk-unknown__content > * + * {\n margin-block-start: 0.5rem;\n}\n";
|
|
@@ -12,3 +12,13 @@ export declare function failureKindClass(base: string, kind: FailureKind | undef
|
|
|
12
12
|
* state modifiers, e.g. `mk-chart--empty`) is kept adjacent to the base.
|
|
13
13
|
*/
|
|
14
14
|
export declare function dataStateClassName(base: string, status: ValueStatus | undefined, kind: FailureKind | undefined, extra?: readonly string[]): string;
|
|
15
|
+
/**
|
|
16
|
+
* The class `render.ts` wraps an INLINE-registered component in when it
|
|
17
|
+
* receives no content (e.g. `::badge{label="x"}`, text put in an attribute
|
|
18
|
+
* instead of the directive body). Ported from `@markii/react`'s copy of this
|
|
19
|
+
* module so both engines say and style the same thing; see that copy's doc
|
|
20
|
+
* comment for the full rationale.
|
|
21
|
+
*/
|
|
22
|
+
export declare const EMPTY_INLINE_MARKER_CLASS = "mk-inline-empty";
|
|
23
|
+
/** The `title` tooltip for `EMPTY_INLINE_MARKER_CLASS`. Ported verbatim from `@markii/react`. */
|
|
24
|
+
export declare function emptyInlineTitle(name: string): string;
|
|
@@ -54,3 +54,15 @@ export function dataStateClassName(base, status, kind, extra) {
|
|
|
54
54
|
classes.push(kindClass);
|
|
55
55
|
return classes.join(' ');
|
|
56
56
|
}
|
|
57
|
+
/**
|
|
58
|
+
* The class `render.ts` wraps an INLINE-registered component in when it
|
|
59
|
+
* receives no content (e.g. `::badge{label="x"}`, text put in an attribute
|
|
60
|
+
* instead of the directive body). Ported from `@markii/react`'s copy of this
|
|
61
|
+
* module so both engines say and style the same thing; see that copy's doc
|
|
62
|
+
* comment for the full rationale.
|
|
63
|
+
*/
|
|
64
|
+
export const EMPTY_INLINE_MARKER_CLASS = 'mk-inline-empty';
|
|
65
|
+
/** The `title` tooltip for `EMPTY_INLINE_MARKER_CLASS`. Ported verbatim from `@markii/react`. */
|
|
66
|
+
export function emptyInlineTitle(name) {
|
|
67
|
+
return `${name}: no content (an attribute may have been used where directive text was expected)`;
|
|
68
|
+
}
|
package/dist/render.js
CHANGED
|
@@ -4,7 +4,7 @@ import { readRegistryComponent, resolveDirectiveAlias } from './registry.js';
|
|
|
4
4
|
import { resolveLayoutAttributes } from './layout.js';
|
|
5
5
|
import { escapeHtml } from './escape.js';
|
|
6
6
|
import { resolveScopedPath } from './resolve.js';
|
|
7
|
-
import { failureKindClass, failureTitle } from './failure-presentation.js';
|
|
7
|
+
import { failureKindClass, failureTitle, EMPTY_INLINE_MARKER_CLASS, emptyInlineTitle, } from './failure-presentation.js';
|
|
8
8
|
import { stringifyStoredValue } from './value-format.js';
|
|
9
9
|
/** The hast tag name `@markii/core`'s `toHast` marks every directive with (`to-hast.ts`'s `DIRECTIVE_TAG`). */
|
|
10
10
|
const DIRECTIVE_TAG = 'mk-directive';
|
|
@@ -163,6 +163,19 @@ function isFormMismatch(entry, kind) {
|
|
|
163
163
|
return false;
|
|
164
164
|
}
|
|
165
165
|
}
|
|
166
|
+
/**
|
|
167
|
+
* Whether `entry` is registered `inline: true`, read the same
|
|
168
|
+
* hostile-configuration-safe way `isFormMismatch` reads `entry.inline`.
|
|
169
|
+
* Mirrors `@markii/react`'s `isRegisteredInline`.
|
|
170
|
+
*/
|
|
171
|
+
function isRegisteredInline(entry) {
|
|
172
|
+
try {
|
|
173
|
+
return entry.inline === true;
|
|
174
|
+
}
|
|
175
|
+
catch {
|
|
176
|
+
return false;
|
|
177
|
+
}
|
|
178
|
+
}
|
|
166
179
|
/** The fallback label line, worded for the reason and the form the directive was written in. Returns HTML. */
|
|
167
180
|
function fallbackLabel(name, inline, reason) {
|
|
168
181
|
const code = `<code>${escapeHtml(name)}</code>`;
|
|
@@ -309,12 +322,23 @@ function renderDirectiveContent(name, kind, attributes, childrenHtml, plainLabel
|
|
|
309
322
|
return unknownDirective(name || '(unnamed)', inline, childrenHtml, 'form-mismatch');
|
|
310
323
|
}
|
|
311
324
|
const binding = resolveDataAttribute(attributes, scope);
|
|
325
|
+
let rendered;
|
|
312
326
|
try {
|
|
313
|
-
|
|
327
|
+
rendered = component(binding.attributes, childrenHtml, withDataBinding(ctx, binding));
|
|
314
328
|
}
|
|
315
329
|
catch {
|
|
316
330
|
return componentError(name || '(unnamed)', inline, childrenHtml);
|
|
317
331
|
}
|
|
332
|
+
// ITEM 1 (AGENTS.md "clean is not silent"): mirrors `@markii/react`'s
|
|
333
|
+
// identical check in `renderDirectiveContent` — an `inline: true`
|
|
334
|
+
// component with no content still renders exactly as registered, wrapped
|
|
335
|
+
// in the same quiet marker/title, so the two engines produce identical
|
|
336
|
+
// markup for this case.
|
|
337
|
+
if (isRegisteredInline(entry) && childrenHtml.trim() === '') {
|
|
338
|
+
const title = escapeHtml(emptyInlineTitle(name));
|
|
339
|
+
return `<span class="${EMPTY_INLINE_MARKER_CLASS}" title="${title}">${rendered}</span>`;
|
|
340
|
+
}
|
|
341
|
+
return rendered;
|
|
318
342
|
}
|
|
319
343
|
/** Turns one `<mk-directive>` element (children already transformed) into its HTML string, including the layout wrapper for block directives. */
|
|
320
344
|
function renderDirective(element, registry, ctx, scope) {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@markii/html",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.8.0",
|
|
4
4
|
"description": "A framework-free static HTML renderer for Markii (.mk.md): a registry-driven hast-to-HTML string engine. Zero React; for stopped-changing documents (publish, CI, email, archive).",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"markdown",
|
|
@@ -13,11 +13,11 @@
|
|
|
13
13
|
],
|
|
14
14
|
"license": "MIT",
|
|
15
15
|
"author": "sadigaxund",
|
|
16
|
-
"homepage": "https://github.com/
|
|
17
|
-
"bugs": "https://github.com/
|
|
16
|
+
"homepage": "https://github.com/markii-org/markii#readme",
|
|
17
|
+
"bugs": "https://github.com/markii-org/markii/issues",
|
|
18
18
|
"repository": {
|
|
19
19
|
"type": "git",
|
|
20
|
-
"url": "git+https://github.com/
|
|
20
|
+
"url": "git+https://github.com/markii-org/markii.git",
|
|
21
21
|
"directory": "packages/platforms/markii-html"
|
|
22
22
|
},
|
|
23
23
|
"type": "module",
|
|
@@ -54,9 +54,9 @@
|
|
|
54
54
|
},
|
|
55
55
|
"dependencies": {
|
|
56
56
|
"hast-util-to-html": "^9.0.0",
|
|
57
|
-
"@markii/core": "0.
|
|
58
|
-
"@markii/runtime": "0.
|
|
59
|
-
"@markii/stdlib": "0.
|
|
57
|
+
"@markii/core": "0.8.0",
|
|
58
|
+
"@markii/runtime": "0.8.0",
|
|
59
|
+
"@markii/stdlib": "0.8.0"
|
|
60
60
|
},
|
|
61
61
|
"devDependencies": {
|
|
62
62
|
"@types/hast": "^3.0.4"
|