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

717 lines
24 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;
/* nudge so the grip centres on the block's first line */
margin-block-start: 0.12em;
/* 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 {
from {
opacity: 0;
transform: translateX(-4px);
}
to {
opacity: 1;
transform: 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-4);
padding: var(--space-4);
}
[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);
}
[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);
}
[data-words-inspector-value] {
font-size: var(--words-font-size-sm);
color: var(--words-content-color);
font-variant-numeric: tabular-nums;
}
[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;
}
/* ── 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);
}
figure[data-words-block='image'] img {
max-inline-size: 100%;
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;
}
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.