You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
svelte-kit-vice/src/uix/eidos/components/words/words.css

1071 lines
37 KiB

/**
* words.css — eidos visual for the Words editor (content layer).
*
* Targets the `data-words-*` attributes the engine's render tree emits
* (see soma `engine/render.ts`) plus the editor chrome that layers over
* the content: the left block gutter and the floating selection bubble
* (their Svelte lives in `words-block-gutter.svelte` /
* `words-bubble.svelte`; the visuals live here). The settings drawer
* keeps its own recipe.
*
* Tokens are the `--words-*` recipe surface (eidos/lib/recipes/base.ts).
* The reading typography is deliberately document-grade (generous size +
* line-height + block rhythm), not the tight UI scale.
*/
/* ── Frame ────────────────────────────────────────────────────────────── */
[data-words] {
/* size cascade — md is the default; sm / lg override below.
content-px is gutter-aware (leaves room for the block handle in the
left margin + the settings button in the right margin) with enough
clearance that the dashed block outline never crowds the grip. */
--_words-content-px: 3rem;
--_words-content-py: var(--words-content-py-md);
--_words-content-min: var(--words-content-min-block-size-md);
--_words-content-max: var(--words-content-max-block-size-md);
--_words-content-font-size: var(--words-font-size-lg);
--_words-radius: var(--words-radius-md);
position: relative;
display: flex;
flex-direction: column;
inline-size: 100%;
font-family: var(--words-font-family);
color: var(--words-color);
background: var(--words-bg);
border: var(--words-border-width) solid var(--words-border);
border-radius: var(--_words-radius);
box-shadow: var(--words-shadow);
transition:
border-color var(--words-transition-duration) var(--words-transition-ease),
box-shadow var(--words-transition-duration) var(--words-transition-ease);
}
[data-words][data-size='sm'] {
--_words-content-px: 2.5rem;
--_words-content-py: var(--words-content-py-sm);
--_words-content-min: var(--words-content-min-block-size-sm);
--_words-content-max: var(--words-content-max-block-size-sm);
--_words-content-font-size: var(--words-font-size-md);
--_words-radius: var(--words-radius-sm);
}
[data-words][data-size='lg'] {
--_words-content-px: 3.5rem;
--_words-content-py: var(--words-content-py-lg);
--_words-content-min: var(--words-content-min-block-size-lg);
--_words-content-max: var(--words-content-max-block-size-lg);
--_words-content-font-size: calc(var(--words-font-size-lg) + 2px);
--_words-radius: var(--words-radius-lg);
}
[data-words]:focus-within {
border-color: var(--words-border-hover);
box-shadow: var(--words-focus-shadow);
}
[data-words][data-invalid] {
border-color: var(--words-invalid-border);
}
[data-words][data-disabled] {
opacity: var(--words-disabled-opacity);
pointer-events: none;
}
/* Pre-mount frame (client-only editor — see words.svelte). Holds the
min height so swapping in the live editor doesn't shift layout. */
[data-words][aria-busy='true'] {
min-block-size: calc(var(--_words-content-min) + 2 * var(--_words-content-py));
}
/* ── Left gutter — block handle ───────────────────────────────────────── */
[data-words-block-gutter] {
position: absolute;
inset-inline-start: 0.35rem;
z-index: 2;
/* The script sets `top` to the block's vertical midpoint; this
transform pulls the grip up by half its own height so the icon
lands ON the midpoint rather than starting from it. Works
regardless of block height (paragraph, code, table, image). */
transform: translateY(-50%);
/* slide smoothly between blocks as the cursor moves */
transition: top 120ms var(--words-transition-ease);
animation: words-gutter-in 140ms var(--words-transition-ease);
}
@keyframes words-gutter-in {
/* `translateY(-50%)` keeps the grip vertically centered against the
block midpoint — the keyframe MUST preserve it on both frames, or
the end state snaps the grip back to its `top` anchor and the
centering is lost the moment the animation completes. */
from {
opacity: 0;
transform: translateY(-50%) translateX(-4px);
}
to {
opacity: 1;
transform: translateY(-50%) translateX(0);
}
}
[data-words-block-gutter] [data-words-block-handle] {
color: var(--words-placeholder-color);
cursor: grab;
transition:
transform var(--words-transition-duration) var(--words-transition-ease),
color var(--words-transition-duration) var(--words-transition-ease);
}
/* subtle displacement when the cursor lands on the handle */
[data-words-block-gutter] [data-words-block-handle]:hover {
color: var(--words-content-color);
transform: translateX(1px) scale(1.18);
}
/* dashed outline emphasising the block the handle acts on */
[data-words-block-outline] {
position: absolute;
z-index: 1;
pointer-events: none;
border: 1px dashed var(--words-border-hover);
border-radius: var(--words-radius-sm);
animation: words-outline-in var(--words-transition-duration) var(--words-transition-ease);
}
@keyframes words-outline-in {
from {
opacity: 0;
}
to {
opacity: 1;
}
}
/* ── Selection bubble toolbar ─────────────────────────────────────────── */
/* Floating card, viewport-anchored (the soma BubbleMenu writes
--_words-bubble-x / -y from the selection's client rect, so position
is fixed). Centred horizontally on the selection; sits above it for
side='top', below for side='bottom'. */
[data-words-bubble-menu] {
position: fixed;
inset-block-start: var(--_words-bubble-y, 0);
inset-inline-start: var(--_words-bubble-x, 0);
z-index: 30;
display: flex;
align-items: center;
gap: var(--words-command-gap);
padding: var(--space-1);
background: var(--words-bg);
border: var(--words-border-width) solid var(--words-border);
border-radius: var(--words-radius-md);
box-shadow: var(--shadow-overlay);
animation: words-bubble-in 120ms var(--words-transition-ease);
}
[data-words-bubble-menu][data-side='top'] {
transform: translate(-50%, calc(-100% - 0.5rem));
}
[data-words-bubble-menu][data-side='bottom'] {
transform: translate(-50%, 0.5rem);
}
[data-words-bubble-menu][hidden] {
display: none;
}
/* Opacity-only so the keyframe doesn't fight the data-side transform. */
@keyframes words-bubble-in {
from {
opacity: 0;
}
to {
opacity: 1;
}
}
/* Active mark — the soma CommandButton stamps data-active / aria-pressed
on the composed eidos Button; tint it to read as "on". (The Button
recipe owns every other state: hover, focus, disabled, sizing.) */
[data-words-bubble-menu] [data-button][data-active] {
background: var(--words-primary-solid);
color: var(--words-command-solid-color);
}
/* Thin vertical rule between groups. */
[data-words-bubble-menu] [data-words-bubble-divider] {
flex: 0 0 auto;
inline-size: 1px;
block-size: 1.15rem;
margin-inline: var(--space-0-5);
background: var(--words-border);
}
/* Turn-into trigger (an eidos Button) — keep it slim in the bubble. */
[data-words-bubble-menu] [data-words-bubble-trigger] {
white-space: nowrap;
}
/* Inline link editor row (replaces the toolbar while editing a link). */
[data-words-bubble-menu] [data-words-bubble-link] {
display: flex;
align-items: center;
gap: var(--space-1);
}
[data-words-bubble-menu] [data-words-bubble-link] input {
inline-size: 15rem;
padding: var(--space-1) var(--space-2);
border: var(--words-border-width) solid var(--words-border);
border-radius: var(--words-command-radius);
background: var(--words-bg);
color: var(--words-content-color);
font-family: inherit;
font-size: var(--words-font-size-md);
outline: none;
}
[data-words-bubble-menu] [data-words-bubble-link] input:focus {
border-color: var(--words-border-hover);
}
/* Turn-into menu item — label + trailing active tick. */
[data-words-bubble-into-item] {
display: flex;
align-items: center;
justify-content: space-between;
gap: var(--space-4);
}
/* trailing active tick (eidos Icon) */
[data-words-bubble-into-item] [data-icon] {
color: var(--words-primary-text);
}
/* ── Block inspector ──────────────────────────────────────────────────── */
/* Shared body — a vertical stack of labelled property rows. Reused by
every inspector presentation (sidebar / drawer / popover). The vertical
layout gives each control a full-width row, so nothing crowds. */
/* The inspector body lives inside a soma `Words.Drawer` (for live provider
reactivity); that host adds no layout — only the inner panel does. */
[data-words-inspector-host] {
display: contents;
}
[data-words-inspector] {
display: flex;
flex-direction: column;
gap: var(--space-3);
/* Tightened from space-4 → space-2 (16px → 8px) — the sidebar is narrow
and the previous padding ate ~20% of the usable inline width. */
padding: var(--space-2);
/* Force the system sans stack on EVERY descendant of the inspector
(labels, values, subsection titles, NumberField inputs, ToggleGroup
chips, accordion triggers, table cell counters, etc.). The editor's
content font can be serif when the surface is themed for reading
(`Words` body inherits `--words-font-family`); the inspector chrome
must stay sans regardless or the controls render with mixed
typography.
The container declaration alone is NOT enough — eidos primitives
(Accordion.Trigger, Button, NumberField.Input, ToggleGroup.Item,
…) set their own `font-family` in their recipe CSS, breaking the
cascade. The `* { font-family: inherit }` rule forces every
descendant to inherit from the container regardless of recipe
overrides. This is the standard pattern when you want a subtree
to share one typeface against shipped components that ship their
own font choices. */
font-family:
system-ui,
-apple-system,
'Segoe UI',
Roboto,
Helvetica,
Arial,
sans-serif;
}
[data-words-inspector] *,
[data-words-inspector] *::before,
[data-words-inspector] *::after {
font-family: inherit;
}
[data-words-inspector-title] {
font-size: var(--words-font-size-md);
font-weight: var(--words-strong-font-weight);
color: var(--words-content-color);
}
[data-words-inspector-section] {
display: flex;
flex-direction: column;
gap: var(--space-2-5);
}
[data-words-inspector-section-title] {
font-size: var(--words-font-size-sm);
font-weight: var(--words-strong-font-weight);
color: var(--words-placeholder-color);
text-transform: uppercase;
letter-spacing: 0.05em;
}
[data-words-inspector-row] {
display: flex;
flex-direction: column;
gap: var(--space-1-5);
}
/* Inspector row gated by a sibling toggle (e.g. image Width / Height /
Align gated by Fullwidth). Reduce contrast + block pointer events so
the user sees the field is dimmed but not removed (gives context for
what Fullwidth controls). The actual controls inside still set
`disabled`/`aria-disabled`; this is the visual sugar. */
[data-words-inspector-row][data-disabled] {
opacity: 0.45;
pointer-events: none;
}
/* Image src field shares its row with an inline `Upload` button. The
wrapping group is the row's "value" slot — `data-inline` gives it
`justify-content: space-between` against the label, and the inner
flex distributes input (stretch) + button (auto) along the row. */
[data-words-inspector-src-group] {
display: flex;
flex-direction: row;
align-items: center;
gap: var(--space-1);
flex: 1;
min-inline-size: 0;
}
[data-words-inspector-src-group] input[data-words-inspector-text] {
flex: 1;
min-inline-size: 0;
}
/* Single-line row variant — label on the left, control on the right.
Used by `WordsColorRow` (each color = `Label [Picker]` per the
inspector's compact spec). Sliders / toggle groups keep the stacked
layout (label above, control below) because the control is wider. */
[data-words-inspector-row][data-inline] {
flex-direction: row;
align-items: center;
justify-content: space-between;
gap: var(--space-2);
}
/* The eidos ColorPicker root declares `inline-size: 100%` (so it fills
form rows by default). Inside an inline inspector row that override
collapses the label-to-picker distribution because the picker stretches
to the full remaining width. Pin to auto here so `justify-content:
space-between` actually pushes the picker's trigger to the right edge. */
[data-words-inspector-row][data-inline] [data-color-picker] {
inline-size: auto;
}
/* Pin the trigger to a fixed inline size so every color row's picker has
the same visual width regardless of the hex string length (6 chars vs
8 chars with alpha). `tabular-nums` keeps the hex characters at a
consistent advance width so the text doesn't jitter while the picker
value oscillates during drag. */
[data-words-inspector-row][data-inline] [data-color-picker-trigger] {
inline-size: 7.5rem;
justify-content: flex-start;
font-variant-numeric: tabular-nums;
}
[data-words-inspector-rowhead] {
display: flex;
align-items: baseline;
justify-content: space-between;
}
[data-words-inspector-label] {
font-size: var(--words-font-size-sm);
color: var(--words-command-color);
/* Font-family is inherited from `[data-words-inspector]` (forced sans
at the container level). */
}
[data-words-inspector-value] {
font-size: var(--words-font-size-sm);
color: var(--words-content-color);
font-variant-numeric: tabular-nums;
}
/* Compact numeric input in `WordsNumRow` rowhead — pins width so the
label / slider layout doesn't shift as the user types longer numbers.
`tabular-nums` keeps the digits at uniform advance width. */
[data-words-inspector-rowhead] [data-number-field] {
inline-size: 4.5rem;
}
[data-words-inspector-rowhead] [data-number-field-input] {
inline-size: 100%;
text-align: end;
font-variant-numeric: tabular-nums;
font-size: var(--words-font-size-sm);
}
/* WordsSpacingRow — 4-side editor with PAIR chain.
Layout: a vertical stack of TWO pair rows. Each pair holds two cells
(label-above-input) plus its OWN link toggle. The two pairs are
independent — chaining one does not chain the other. This makes the
pair semantics unmistakable visually. */
[data-words-inspector-spacing] {
display: flex;
flex-direction: column;
gap: var(--space-2);
}
[data-words-inspector-spacing-head] {
display: flex;
align-items: center;
justify-content: space-between;
gap: var(--space-2);
}
[data-words-inspector-spacing-grid] {
display: flex;
flex-direction: column;
gap: var(--space-2);
}
[data-words-inspector-spacing-pair] {
display: grid;
/* Two equal-width inputs + a small fixed slot for the link toggle. */
grid-template-columns: 1fr 1fr auto;
align-items: end;
gap: var(--space-1);
/* Bump icon size in this scope so the link/unlink chip is visible at
the Toggle xs size (default `--icon-size` is too small here). */
--icon-size: 14px;
}
[data-words-inspector-spacing-cell] {
display: flex;
flex-direction: column;
gap: 2px;
min-inline-size: 0;
}
[data-words-inspector-spacing-cell-label] {
font-size: var(--words-font-size-xs, 0.75rem);
color: var(--words-content-color);
opacity: 0.7;
letter-spacing: 0.02em;
}
[data-words-inspector-spacing-cell] [data-number-field] {
inline-size: 100%;
min-inline-size: 0;
}
[data-words-inspector-spacing-cell] [data-number-field-input] {
inline-size: 100%;
text-align: end;
font-variant-numeric: tabular-nums;
font-size: var(--words-font-size-sm);
}
/* Block panel — bare text inputs (code language, image alt + caption).
Styled to match the inspector chrome (sans, sm size, ghost
background) so they sit among the ToggleGroups without visual noise.
`<datalist>` suggestions render natively on focus. The shared
`[data-words-inspector-text]` selector is for plain text edits;
`[data-words-inspector-code-language]` keeps the original selector
for backward compatibility — both inherit the same base rules. */
[data-words-inspector-text],
[data-words-inspector-code-language] {
flex: 1 1 0;
min-inline-size: 0;
font: inherit;
font-size: var(--words-font-size-sm);
padding: 4px 8px;
border: 1px solid var(--words-border);
border-radius: var(--radius-2, 4px);
background: transparent;
color: inherit;
}
[data-words-inspector-text]:focus-visible,
[data-words-inspector-code-language]:focus-visible {
outline: 2px solid var(--color-primary, currentColor);
outline-offset: -1px;
}
/* Callout intent swatch — small color dot rendered INSIDE the
ToggleGroup chip alongside its localized label. Color comes from
the doctrinal palette token for each intent (so a `risk` swatch
matches the actual `risk` callout's accent). Lives in the chip
button's flex flow; sits at the inline-start. */
[data-words-intent-swatch] {
display: inline-block;
inline-size: 0.625rem;
block-size: 0.625rem;
border-radius: 50%;
margin-inline-end: 4px;
vertical-align: -1px;
background: var(--color-neutral-solid);
}
[data-words-intent-swatch][data-intent='neutral'] {
background: var(--color-neutral-solid);
}
[data-words-intent-swatch][data-intent='affirm'] {
background: var(--color-affirm-solid);
}
[data-words-intent-swatch][data-intent='fulfill'] {
background: var(--color-fulfill-solid);
}
[data-words-intent-swatch][data-intent='risk'] {
background: var(--color-risk-solid);
}
[data-words-intent-swatch][data-intent='threat'] {
background: var(--color-threat-solid);
}
[data-words-intent-swatch][data-intent='loss'] {
background: var(--color-loss-solid);
}
/* Border-style swatch — the chip IS the line preview. No lucide icon
for solid/dashed/dotted is fitting, so we render a short top border
in the current ink. The swatch line scales with the chip width so
bigger sizes show a longer sample. */
[data-words-border-style-swatch] {
display: inline-block;
inline-size: 1.25rem;
block-size: 0;
border-block-start-width: 2px;
border-block-start-color: currentColor;
}
[data-words-border-style-swatch='solid'] {
border-block-start-style: solid;
}
[data-words-border-style-swatch='dashed'] {
border-block-start-style: dashed;
}
[data-words-border-style-swatch='dotted'] {
border-block-start-style: dotted;
}
/* WordsBlockPanel hierarchical subsections (table cell + table).
Sits inside `data-words-inspector-section` and groups related rows
under a small label. Keeps the visual separation between scopes
(cell-level vs table-level) without the heaviness of a second
accordion item. */
[data-words-inspector-subsection] {
display: flex;
flex-direction: column;
gap: var(--space-2);
padding-block: var(--space-1);
}
[data-words-inspector-subsection] + [data-words-inspector-subsection] {
border-block-start: 1px dashed var(--words-border);
padding-block-start: var(--space-2);
}
[data-words-inspector-subsection-title] {
font-size: var(--words-font-size-xs, 0.75rem);
color: var(--words-content-color);
opacity: 0.75;
text-transform: uppercase;
letter-spacing: 0.04em;
font-weight: 600;
}
/* Table insert/delete ops row — 3 small buttons in a horizontal flex
group (above/below/delete for rows; left/right/delete for columns).
Sits below the count display so the user reads "Rows: 3" then sees
the actions, mirroring Notion/Word table panels. */
[data-words-inspector-table-ops] {
display: flex;
gap: var(--space-1);
flex-wrap: wrap;
}
[data-words-inspector-empty] {
padding: var(--space-2) 0;
font-size: var(--words-font-size-sm);
color: var(--words-placeholder-color);
}
/* Sidebar — docked, always visible (builder-style). The rail reserves a
right gutter; the sidebar itself is absolutely positioned so the frame's
height follows the CONTENT, not the (usually taller) inspector. A flex
row would stretch the frame to the inspector's height and leave dead
space below the text — the inspector instead scrolls within the content
height. */
[data-words][data-inspector='sidebar'] {
padding-inline-end: 16rem;
}
[data-words-sidebar] {
position: absolute;
inset-block: 0;
inset-inline-end: 0;
inline-size: 16rem;
overflow-y: auto;
border-inline-start: var(--words-border-width) solid var(--words-border);
border-start-end-radius: var(--_words-radius);
border-end-end-radius: var(--_words-radius);
background: var(--words-bg);
}
/* Drawer — slide-in panel from the right edge + a fixed toggle. */
[data-words-drawer-toggle] {
position: absolute;
inset-block-start: var(--space-3);
inset-inline-end: var(--space-3);
z-index: 21;
}
[data-words-drawer-panel] {
position: absolute;
inset-block: 0;
inset-inline-end: 0;
z-index: 20;
inline-size: 16rem;
max-inline-size: 85%;
display: flex;
flex-direction: column;
overflow-y: auto;
background: var(--words-bg);
border-inline-start: var(--words-border-width) solid var(--words-border);
border-start-end-radius: var(--_words-radius);
border-end-end-radius: var(--_words-radius);
box-shadow: var(--shadow-overlay);
transform: translateX(100%);
transition: transform var(--words-transition-duration) var(--words-transition-ease);
}
[data-words-drawer-panel][data-open] {
transform: translateX(0);
}
[data-words-drawer-panel-head] {
display: flex;
align-items: center;
justify-content: space-between;
padding: var(--space-2) var(--space-2) var(--space-2) var(--space-4);
border-block-end: var(--words-border-width) solid var(--words-border);
font-size: var(--words-font-size-md);
font-weight: var(--words-strong-font-weight);
color: var(--words-content-color);
}
/* Popover — a corner toggle floats the inspector. */
[data-words-popover-anchor] {
position: absolute;
inset-block-start: var(--space-3);
inset-inline-end: var(--space-3);
z-index: 21;
}
[data-words-inspector-popover] {
inline-size: 16rem;
padding: 0;
}
/* ── Content surface (the contenteditable) ───────────────────────────── */
[data-words-content] {
position: relative;
flex: 1 1 auto;
min-block-size: var(--_words-content-min);
max-block-size: var(--_words-content-max);
overflow-y: auto;
padding: var(--_words-content-py) var(--_words-content-px);
font-size: var(--_words-content-font-size);
line-height: 1.7;
color: var(--words-content-color);
outline: none;
-webkit-font-smoothing: antialiased;
text-rendering: optimizeLegibility;
}
[data-words-content] :where([data-words-node='block']):first-child {
margin-block-start: 0;
}
[data-words-content] :where([data-words-node='block']):last-child {
margin-block-end: 0;
}
/* ── Placeholder ──────────────────────────────────────────────────────── */
[data-words-placeholder] {
position: absolute;
inset-block-start: var(--_words-content-py);
inset-inline-start: var(--_words-content-px);
font-size: var(--_words-content-font-size);
line-height: 1.7;
color: var(--words-placeholder-color);
pointer-events: none;
user-select: none;
}
[data-words-placeholder][data-hidden] {
display: none;
}
/* ── Block rhythm ─────────────────────────────────────────────────────── */
[data-words-content] :where(p, h1, h2, h3, blockquote, pre, ul, ol, table, figure, hr, [data-words-block='callout']) {
margin-block: var(--words-block-gap) 0;
}
/* Columns block — flex layout for side-by-side content. Each child
`[data-words-column]` is a flex item; the engine sets `flex: <user-
chosen value or 1>` inline so equal-width is the default and the
user can resize via the inspector. Gap reuses the block-gap token
so columns breathe the same vertical rhythm as the surrounding
content. Each column is also a flex container itself (column
direction) so its nested blocks stack vertically. */
[data-words-content] [data-words-block='columns'] {
display: flex;
flex-direction: row;
gap: var(--words-block-gap);
margin-block: var(--words-block-gap) 0;
align-items: stretch;
}
[data-words-content] [data-words-block='columns'] [data-words-column] {
display: flex;
flex-direction: column;
min-inline-size: 0;
/* Visual cue while editing — without this, an empty columns block
collapses to 0 height and the user can't see where the layout is.
The dashed outline + breathing min-height make the structure
discoverable; the soft tone fades into the background once the
user fills the columns with real content. No padding — child
blocks own their own padding via the inspector's Spacing section
so the column dashed border traces exactly the content edge. */
min-block-size: 3em;
padding: 0;
border: 1px dashed var(--words-border);
border-radius: var(--radius-2, 4px);
transition: border-color var(--words-transition-duration) var(--words-transition-ease);
}
[data-words-content] [data-words-block='columns'] [data-words-column]:hover,
[data-words-content] [data-words-block='columns'] [data-words-column]:focus-within {
border-color: var(--words-content-color);
}
/* Reset the universal block-rhythm `margin-block` for the FIRST nested
block of each column — otherwise that extra gap pushes the column
contents down relative to the column's frame, breaking vertical
alignment with siblings. */
[data-words-content] [data-words-block='columns'] [data-words-column] > *:first-child {
margin-block-start: 0;
}
/* ── Paragraph ────────────────────────────────────────────────────────── */
[data-words-block='paragraph'] {
margin-block: var(--words-block-gap) 0;
}
/* ── Headings ─────────────────────────────────────────────────────────── */
[data-words-block='heading'] {
margin-block: calc(var(--words-block-gap) * 1.5) 0;
color: var(--words-heading-color);
font-weight: var(--words-heading-font-weight);
line-height: var(--words-heading-line-height);
letter-spacing: -0.01em;
}
h1[data-words-block='heading'] {
font-size: var(--words-heading-h1-font-size);
}
h2[data-words-block='heading'] {
font-size: var(--words-heading-h2-font-size);
}
h3[data-words-block='heading'] {
font-size: var(--words-heading-h3-font-size);
}
/* ── Quote ────────────────────────────────────────────────────────────── */
blockquote[data-words-block='quote'] {
padding-inline-start: var(--words-quote-px);
border-inline-start: var(--words-quote-border-width) solid var(--words-primary-border);
color: var(--words-quote-color);
font-style: italic;
}
/* ── Code block ───────────────────────────────────────────────────────── */
pre[data-words-block='code'] {
padding: calc(var(--words-code-py) * 3) calc(var(--words-code-px) * 3);
border-radius: var(--words-radius-sm);
background: var(--words-code-bg);
color: var(--words-code-color);
font-family: var(--words-code-font-family);
font-size: 0.9em;
line-height: 1.6;
overflow-x: auto;
tab-size: 2;
}
pre[data-words-block='code'] code {
font-family: inherit;
white-space: pre;
}
/* syntax tokens (highlightWordsCode) */
[data-words-code-token='keyword'] {
color: var(--words-primary-text);
font-weight: var(--words-strong-font-weight);
}
[data-words-code-token='string'] {
color: var(--words-affirm-text);
}
[data-words-code-token='number'] {
color: var(--words-fulfill-text);
}
[data-words-code-token='comment'] {
color: var(--words-content-color);
opacity: 0.6;
font-style: italic;
}
[data-words-code-token='function'] {
color: var(--words-secondary-text);
}
/* ── Lists ────────────────────────────────────────────────────────────── */
[data-words-block='list'] {
padding-inline-start: var(--words-list-padding-inline-start);
}
[data-words-block='list'][data-words-list-kind='check'] {
padding-inline-start: 0;
list-style: none;
}
li[data-words-node='list-item'] {
margin-block: 0.25em;
}
li[data-words-node='list-item'][data-words-indent='1'] {
margin-inline-start: var(--words-list-padding-inline-start);
}
li[data-words-node='list-item'][data-words-indent='2'] {
margin-inline-start: calc(var(--words-list-padding-inline-start) * 2);
}
/* check toggle */
[data-words-list-kind='check'] li[data-words-node='list-item'] {
display: flex;
align-items: flex-start;
gap: var(--space-2);
}
[data-words-check-toggle] {
flex: 0 0 auto;
inline-size: 1.1em;
block-size: 1.1em;
margin-block-start: 0.3em;
border: 1.5px solid var(--words-border-hover);
border-radius: var(--words-radius-sm);
background: var(--words-bg);
cursor: pointer;
transition:
background var(--words-transition-duration) var(--words-transition-ease),
border-color var(--words-transition-duration) var(--words-transition-ease);
}
[data-words-check-toggle][data-words-checked='true'] {
border-color: var(--words-primary-solid);
background: var(--words-primary-solid)
url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16' fill='none' stroke='white' stroke-width='2.5' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M3.5 8.5l3 3 6-7'/%3E%3C/svg%3E")
center / 70% no-repeat;
}
[data-words-checked='true'] :where(span[data-words-node='text']) {
color: var(--words-content-color);
opacity: 0.55;
text-decoration: line-through;
}
/* ── Table ────────────────────────────────────────────────────────────── */
table[data-words-block='table'] {
inline-size: 100%;
border-collapse: collapse;
font-size: 0.95em;
}
table[data-words-block='table'] :where(td, th) {
padding: var(--space-1-5) var(--space-2-5);
border: var(--words-border-width) solid var(--words-border);
text-align: start;
vertical-align: top;
}
table[data-words-block='table'] th {
background: var(--words-code-bg);
font-weight: var(--words-strong-font-weight);
}
[data-words-node='table-cell'][data-words-cell-align='center'] {
text-align: center;
}
[data-words-node='table-cell'][data-words-cell-align='right'] {
text-align: end;
}
[data-words-node='table-cell'][data-words-cell-vertical='middle'] {
vertical-align: middle;
}
[data-words-node='table-cell'][data-words-cell-vertical='bottom'] {
vertical-align: bottom;
}
/* ── Image ────────────────────────────────────────────────────────────── */
figure[data-words-block='image'] {
display: flex;
flex-direction: column;
align-items: center;
gap: var(--space-2);
/* Browser default for <figure> is `margin-inline: 40px` — it eats
~80px from every column / container the figure sits in. Reset to 0
so the image can use the full content area of its parent (the
block-gap-aware margin-block reset above only touched the vertical
axis). */
margin-inline: 0;
}
/* When no explicit width attr is set, cap at the container so the image
doesn't break layout. When the user typed an explicit width in the
inspector (renders as `<img width="X">`), let that win — including
values LARGER than the natural / container size (intentional upscale
for hero use-cases). */
figure[data-words-block='image'] img:not([width]) {
max-inline-size: 100%;
block-size: auto;
border-radius: var(--words-radius-sm);
}
figure[data-words-block='image'] img[width] {
block-size: auto;
border-radius: var(--words-radius-sm);
}
figure[data-words-block='image'][data-words-image-align='left'] {
align-items: flex-start;
}
figure[data-words-block='image'][data-words-image-align='right'] {
align-items: flex-end;
}
/* Fullwidth: drop the figure's flex layout (which sized it to its
content + caused overflow on `inline-size: 100%` chains with the
column's flex parent) and force the figure to fill its container.
The column is `display: flex; align-items: normal` and its child
flex items default to `flex: 0 1 auto` — that auto basis collapses
block-level children to their intrinsic content (= the img's natural
width). Setting `inline-size: 100%` explicitly + `box-sizing:
border-box` is what makes the figure fill the column's content area
without overflowing past its padding. The img inside then stretches
inside the figure. */
figure[data-words-block='image'][data-words-image-full-width] {
display: block;
inline-size: 100%;
max-inline-size: 100%;
box-sizing: border-box;
}
figure[data-words-block='image'][data-words-image-full-width] img {
display: block;
inline-size: 100%;
max-inline-size: 100%;
block-size: auto;
box-sizing: border-box;
}
figure[data-words-block='image'] figcaption {
font-size: 0.85em;
color: var(--words-quote-color);
text-align: center;
}
figure[data-words-block='image'][data-words-block-selected] {
outline: 2px solid var(--words-primary-solid);
outline-offset: 3px;
border-radius: var(--words-radius-sm);
}
figure[data-words-block='image'][data-words-image-status='uploading'] {
opacity: 0.6;
}
figure[data-words-block='image'][data-words-image-status='error'] {
outline: 2px solid var(--words-threat-solid);
outline-offset: 3px;
}
/* ── Divider ──────────────────────────────────────────────────────────── */
hr[data-words-block='divider'] {
block-size: 0;
border: none;
border-block-start: var(--words-border-width) solid var(--words-border);
margin-block: calc(var(--words-block-gap) * 1.25) 0;
}
hr[data-words-block='divider'][data-words-block-selected] {
border-block-start-color: var(--words-primary-solid);
border-block-start-width: 2px;
}
/* ── Callout ──────────────────────────────────────────────────────────── */
[data-words-block='callout'] {
padding: var(--space-3) var(--space-4);
border: var(--words-border-width) solid var(--words-neutral-border);
border-inline-start-width: 3px;
border-radius: var(--words-radius-sm);
background: var(--words-neutral-track);
}
[data-words-callout-title] {
margin-block-end: var(--space-1);
font-weight: var(--words-strong-font-weight);
}
[data-words-block='callout'] :where([data-words-node='block']):first-child {
margin-block-start: 0;
}
[data-words-callout-intent='affirm'] {
border-color: var(--words-affirm-border);
background: var(--words-affirm-track);
}
[data-words-callout-intent='affirm'] [data-words-callout-title] {
color: var(--words-affirm-text);
}
[data-words-callout-intent='fulfill'] {
border-color: var(--words-fulfill-border);
background: var(--words-fulfill-track);
}
[data-words-callout-intent='fulfill'] [data-words-callout-title] {
color: var(--words-fulfill-text);
}
[data-words-callout-intent='risk'] {
border-color: var(--words-risk-border);
background: var(--words-risk-track);
}
[data-words-callout-intent='risk'] [data-words-callout-title] {
color: var(--words-risk-text);
}
[data-words-callout-intent='threat'] {
border-color: var(--words-threat-border);
background: var(--words-threat-track);
}
[data-words-callout-intent='threat'] [data-words-callout-title] {
color: var(--words-threat-text);
}
[data-words-callout-intent='loss'] {
border-color: var(--words-loss-border);
background: var(--words-loss-track);
}
[data-words-callout-intent='loss'] [data-words-callout-title] {
color: var(--words-loss-text);
}
/* ── Inline marks ─────────────────────────────────────────────────────── */
[data-words-marks~='bold'] {
font-weight: var(--words-strong-font-weight);
}
[data-words-marks~='italic'] {
font-style: italic;
}
[data-words-marks~='underline'] {
text-decoration-line: underline;
text-underline-offset: 0.18em;
}
[data-words-marks~='strike'] {
text-decoration-line: line-through;
}
[data-words-marks~='underline'][data-words-marks~='strike'] {
text-decoration-line: underline line-through;
}
[data-words-marks~='code'] {
padding: var(--words-code-py) var(--words-code-px);
border-radius: var(--words-code-radius);
background: var(--words-code-bg);
font-family: var(--words-code-font-family);
font-size: 0.9em;
}
/* ── Link ─────────────────────────────────────────────────────────────── */
a[data-words-node='link'] {
color: var(--words-primary-text);
text-decoration-line: underline;
text-underline-offset: 0.15em;
text-decoration-thickness: 1px;
cursor: pointer;
}
a[data-words-node='link']:hover {
text-decoration-thickness: 2px;
}
/* ── Find/replace matches ─────────────────────────────────────────────── */
mark[data-words-find-match] {
border-radius: 2px;
background: var(--words-fulfill-track);
color: inherit;
}
mark[data-words-find-match][data-words-find-active] {
background: var(--words-fulfill-solid);
color: var(--words-fulfill-text);
}
/* ── Selection ────────────────────────────────────────────────────────── */
[data-words-content] ::selection {
background: color-mix(in srgb, var(--words-primary-solid) 22%, transparent);
}

Powered by TurnKey Linux.