/** * Plan editor styling. * * MDXEditor's structural CSS is imported by the app; this retheme layer maps it * onto Ace's tokens so the editor reads as part of the product rather than as a * third-party widget. */ @import "./callout.css"; /* Evidence can extend beyond the clipped document column; only its panel takes hits. */ .plan-evidence-popover { pointer-events: none; position: fixed; inset: 0; z-index: 40; } .plan-evidence-panel { pointer-events: auto; position: fixed; width: min(20rem, calc(100vw - 24px)); max-height: calc(100dvh - 24px); overflow-y: auto; border-radius: var(--radius-md); background: var(--color-page); box-shadow: var(--shadow-overlay); outline: var(--edge-width) solid var(--color-edge); outline-offset: calc(-1 * var(--edge-width)); } .plan-content [data-research-draft-anchor] { margin-block-end: var(--research-draft-space, 0); } .plan-research-draft { width: min(28.125rem, var(--plan-content-width, 48rem), calc(100vw - 2rem)); padding: 0.875rem; border: var(--edge-width) solid var(--color-edge); border-radius: var(--radius-lg); background: var(--color-page); box-shadow: var(--shadow-resting); } [data-research-ready] { box-shadow: var(--shadow-resting-strong); transition: box-shadow var(--duration-fast) var(--ease-out); } [data-research-ready]:hover, [data-research-ready]:focus-within { box-shadow: var(--shadow-raised); } .plan-research-open { position: absolute; z-index: 1; inset: 0; cursor: pointer; border: 0; border-radius: inherit; background: transparent; } /* Research widgets never exceed 450px and shrink with their container. */ .plan-research-composer, .plan-research-tracked { width: 100%; max-width: 28.125rem; } .plan-research-composer { display: grid; gap: 0.625rem; } .plan-research-field { position: relative; } .plan-research-field textarea { display: block; width: 100%; min-height: 5.5rem; resize: none; padding: 0.625rem 2.5rem 3rem 0.75rem; font: inherit; font-size: var(--text-sm); } .plan-research-dismiss { position: absolute; inset: 0.3125rem 0.3125rem auto auto; } .plan-research-send { position: absolute; inset: auto 0.625rem 0.625rem auto; } .plan-research-actions { display: flex; flex-wrap: wrap; justify-content: flex-end; gap: 0.5rem; } .plan-research-tracked { display: grid; } .plan-research-status { display: flex; align-items: center; gap: 0.5rem; min-height: 1.5rem; font-size: var(--text-sm); color: var(--color-text-tertiary); } /* Stages that lead with a badge give their header more room. */ .plan-research-status[data-lead] { margin-block-end: 0.25rem; } .plan-research-remove { display: flex; align-items: center; margin-inline-start: auto; } .plan-research-meta { color: var(--color-text-quaternary); } .plan-research-title { font-size: var(--text-sm); font-weight: 600; line-height: var(--text-sm--line-height); color: var(--color-text-primary); } .plan-research-card[data-stage="cancelled"] .plan-research-title { color: var(--color-text-tertiary); } .plan-research-summary { margin: 0; font-size: var(--text-sm); color: var(--color-text-secondary); } .plan-research-footer { display: flex; justify-content: flex-end; } .plan-research-dot { width: 0.5rem; height: 0.5rem; margin-inline: 0.1875rem; border-radius: 50%; background: var(--color-brand); animation: plan-research-pulse 1.4s var(--ease-out) infinite; } .plan-research-dot[data-idle] { background: var(--color-text-quaternary); animation: none; } @keyframes plan-research-pulse { 0% { box-shadow: 0 0 0 0 color-mix(in oklch, var(--color-brand) 45%, transparent); } 100% { box-shadow: 0 0 0 0.375rem transparent; } } .plan-research-badge { display: grid; flex-shrink: 0; place-items: center; width: 1.5rem; height: 1.5rem; border-radius: 50%; background: var(--color-danger-surface); color: var(--color-destructive); } .plan-research-badge[data-tone="brand"] { background: var(--color-brand-wash); color: var(--color-brand); } .plan-research-badge [data-nucleo-icon] { color: inherit; } .plan-research-callout { padding: 0.375rem 0.75rem; border-radius: var(--radius-md); background: var(--color-ruby-2); } .plan-research-callout p { margin: 0; padding-block: 0.125rem; font-size: var(--text-sm); line-height: var(--text-sm--line-height); color: var(--color-destructive-ink); } .plan-research-track { display: grid; grid-template-columns: repeat(5, 1fr); gap: 0.25rem; padding-inline: 0.25rem; } .plan-research-track span { position: relative; overflow: hidden; height: 0.3125rem; border-radius: var(--radius-full); background: var(--color-selected); transition: background-color 500ms var(--ease-out); } .plan-research-track span[data-state="now"], .plan-research-track span[data-state="done"] { background: var(--color-brand); } /* The sheen rides its own layer so it can fade without touching the fill. */ .plan-research-track span::after { content: ""; position: absolute; inset: 0; background: linear-gradient( 90deg, transparent 0%, color-mix(in oklch, white 70%, transparent) 50%, transparent 100% ); background-size: 50% 100%; background-repeat: no-repeat; opacity: 0; animation: plan-research-sheen 1.6s linear infinite; transition: opacity 400ms var(--ease-out); } .plan-research-track span[data-state="now"]::after { opacity: 1; transition-delay: var(--duration-base); } @keyframes plan-research-sheen { from { background-position: -100% 0; } to { background-position: 200% 0; } } .plan-research-found { padding-inline: 0.25rem; font-size: var(--text-xs); color: var(--color-text-tertiary); } /* Swap: layers share one grid cell and crossfade by opacity alone. */ .plan-research-swap { display: inline-grid; } .plan-research-swap > span { grid-area: 1 / 1; animation: plan-research-swap-in 360ms var(--ease-out) var(--duration-fast) both; } .plan-research-swap > span[data-initial] { animation: none; } .plan-research-swap > span[data-leaving] { animation: plan-research-swap-out 240ms var(--ease-out) both; } @keyframes plan-research-swap-in { from { opacity: 0; } } @keyframes plan-research-swap-out { to { opacity: 0; } } .plan-research-indicator { place-items: center; } .plan-research-indicator > span { display: grid; place-items: center; min-width: 0.875rem; min-height: 0.875rem; } .plan-research-indicator .plan-research-dot { margin: 0; } /* Fold: animates grid rows so height changes are never a jump. */ .plan-research-fold { display: grid; grid-template-rows: 0fr; opacity: 0; transition: grid-template-rows 420ms var(--ease-out), opacity 300ms var(--ease-out); } .plan-research-fold[data-open] { grid-template-rows: 1fr; opacity: 1; transition-delay: var(--duration-fast); } .plan-research-fold > div { min-height: 0; overflow: hidden; } .plan-research-tracked > .plan-research-fold > div { display: grid; gap: 0.5rem; padding-block-start: 0.625rem; } /* Inside the card a fold owns its gap, so a closed one takes no space. */ .plan-research-card .plan-research-fold { margin-block-start: -0.5rem; } .plan-research-card .plan-research-fold > div > * { margin-block-start: 0.5rem; } @media (prefers-reduced-motion: reduce) { .plan-research-dot, .plan-research-swap > span, .plan-research-track span::after { animation: none; } .plan-research-fold, .plan-research-track span, .plan-research-track span::after { transition: none; } } .plan, .plan-decisions { --plan-gutter: 4rem; /* * A comfortable measure. The panel is resizable and a plan is read far more * often than it is written, so the text stops widening well before the pane * does. At the body size below this is roughly 85 characters a line, near * the top of the range a reader can track back from without losing the row. */ --plan-measure: 43.75rem; /* * Document body text, one step down from the old 17px so prose sits * closer to chrome and chat sizes. Resolved out here because MDXEditor * declares `--text-base`, `--text-sm` * and `--text-xs` on its own root, which sits between `:root` and the * document — so reading the theme token inside the editor gets its 1rem * rather than ours. `.plan` is outside that root, so this catches the * theme's value on the way past. */ --plan-text-xs: var(--text-xs); --plan-text-sm: var(--text-sm); --plan-body: var(--text-base); } /* Document views -------------------------------------------------------- */ .plan-workspace { position: relative; display: flex; flex: 1; min-height: 0; overflow: hidden; } .plan-document { position: relative; container-type: inline-size; flex: 1; min-width: 0; min-height: 0; background: var(--color-page); box-shadow: var(--shadow-raised); } /* A narrow document needs breathing room, but never a 32px tax on each side. */ @container (max-width: 40rem) { .plan-content, .plan-decisions-content { --plan-gutter: 1rem; } /* Anchor state keeps this lane stable when its marker leaves the viewport. */ .plan-document[data-plan-decision-lane] .plan-content { --plan-leading-gutter: 2rem; } /* Wide authored surfaces keep their own visible, keyboard-operable lane. */ .plan-content :is(pre, table, .plan-code-view, [role="tablist"]) { overscroll-behavior-inline: contain; } .plan-content :is(pre, table, .plan-code-view, [role="tablist"])::-webkit-scrollbar { block-size: 8px; } .plan-content :is(pre, table, .plan-code-view, [role="tablist"])::-webkit-scrollbar-track { background: var(--color-inset); } .plan-content :is(pre, table, .plan-code-view, [role="tablist"])::-webkit-scrollbar-thumb { border: 2px solid var(--color-inset); border-radius: var(--radius-lg); background: var(--color-text-quaternary); } } /* Reader-local comment chrome must not move collaborative line boxes. */ .plan-comment-layer { pointer-events: none; position: absolute; inset: 0; z-index: 2; } .plan-comment-button, .plan-comment-orphan-button { pointer-events: auto; border: var(--edge-width) solid var(--color-control-edge); background: var(--color-page); color: var(--color-text-secondary); box-shadow: var(--shadow-resting); } .plan-comment-button { position: absolute; display: grid; width: 24px; height: 24px; place-items: center; border-radius: var(--radius-md); font-size: var(--text-xs); } :root[data-plan-coarse-pointer] .plan-comment-button { width: 44px; height: 44px; } /* The host receives hits so prose keeps native selection. */ .plan-comment-hit { pointer-events: none; position: absolute; } .plan-comment-button:hover, .plan-comment-button:focus-visible, .plan-comment-button[aria-expanded="true"], .plan-comment-orphan-button:hover, .plan-comment-orphan-button:focus-visible { background: var(--color-brand-wash); color: var(--color-brand-ink); } .plan-comment-preview, .plan-comment-card { pointer-events: auto; position: absolute; width: min(20rem, calc(100% - 1.5rem)); } .plan-comment-preview { /* A narrow preview can overlap prose, but never intercept it. */ pointer-events: none; max-width: 18rem; padding: 0.625rem; border-radius: var(--radius-md); background: var(--color-page); box-shadow: var(--shadow-overlay); outline: var(--edge-width) solid var(--color-edge); outline-offset: calc(-1 * var(--edge-width)); font-size: var(--text-sm); color: var(--color-text-primary); } .plan-comment-preview p { margin: 0; } .plan-comment-preview .plan-comment-preview-author { font-weight: 600; color: var(--color-brand-ink); } .plan-comment-preview .plan-comment-preview-note { margin-top: 0.125rem; } .plan-comment-preview span { display: block; margin-top: 0.25rem; color: var(--color-text-tertiary); } .plan-comment-card { max-height: calc(100% - 1rem); overflow: auto; border-radius: var(--radius-lg); box-shadow: var(--shadow-overlay); animation: plan-comment-enter var(--duration-base) var(--ease-out); } [data-plan-comment-card] { background: var(--color-page); box-shadow: none; } .plan-comment-close { margin: -0.25rem -0.25rem -0.25rem 0; color: var(--color-text-tertiary); } .plan-comment-context { padding: 0.5rem 0.625rem; border-radius: var(--radius-md); background: var(--color-inset); } .plan-comment-context-copy { display: -webkit-box; overflow: hidden; -webkit-box-orient: vertical; -webkit-line-clamp: 3; line-height: var(--text-sm--line-height); } .plan-comment-composer[data-inset-send] { border-radius: var(--radius-md); background: var(--color-page); } .plan-comment-composer[data-inset-send] .plan-comment-composer-field { padding-right: 3rem; padding-bottom: 2.75rem; } .plan-comment-confirm { padding: 0.625rem; border-radius: var(--radius-md); background: var(--color-inset); } @keyframes plan-comment-enter { from { opacity: 0.92; transform: translateX(0.375rem); } to { opacity: 1; transform: translateX(0); } } .motion-comment-preview { opacity: 0; transform: scale(var(--dropdown-pre-scale)); transition: opacity var(--dropdown-open-dur) var(--dropdown-ease), transform var(--dropdown-open-dur) var(--dropdown-ease); will-change: opacity, transform; } .motion-comment-preview.is-open { opacity: 1; transform: none; } @starting-style { .motion-comment-preview.is-open { opacity: 0; transform: scale(var(--dropdown-pre-scale)); } } .motion-comment-preview.is-closing { opacity: 0; transform: scale(var(--dropdown-closing-scale)); transition-duration: var(--dropdown-close-dur); } .motion-comment-preview[data-motion-immediate] { transition: none; } .motion-comment-surface { opacity: 0; transform: scale(var(--dropdown-pre-scale)); transform-origin: top right; transition: opacity var(--dropdown-open-dur) var(--dropdown-ease), transform var(--dropdown-open-dur) var(--dropdown-ease); will-change: opacity, transform; } .motion-comment-surface[data-motion-presentation="sheet"] { transform: translateY(var(--drawer-distance)); transform-origin: bottom center; transition-duration: var(--drawer-open-dur); } .motion-comment-surface.is-open { opacity: 1; transform: none; } .motion-comment-surface.is-closing { pointer-events: none; opacity: 0; transform: scale(var(--dropdown-closing-scale)); transition-duration: var(--dropdown-close-dur); } .motion-comment-surface[data-motion-presentation="sheet"].is-closing { transform: translateY(var(--drawer-distance)); transition-duration: var(--drawer-close-dur); } .plan-comment-orphans { pointer-events: auto; position: absolute; top: 0.5rem; right: 0.5rem; } .plan-comment-orphan-button { border-radius: var(--radius-md); padding: 0.375rem 0.5rem; font-size: var(--text-sm); } .plan-comment-orphan-card { top: 2.5rem; right: 0; } .plan-comment-sheet-backdrop { --backdrop-opacity: 0.24; position: fixed; inset: 0; z-index: 70; background: rgb(var(--shadow-color)); opacity: calc(var(--backdrop-opacity) * (1 - var(--drawer-swipe-progress, 0))); transition: opacity var(--duration-base) var(--ease-out); } .plan-comment-sheet-viewport { pointer-events: none; position: fixed; inset: 0; z-index: 71; display: flex; align-items: flex-end; } .plan-comment-sheet-popup { pointer-events: auto; width: 100%; height: calc(100dvh - 0.5rem); overflow: hidden; border-radius: var(--radius-xl) var(--radius-xl) 0 0; background: var(--color-page); box-shadow: 0 -1rem 3rem rgb(var(--shadow-color) / 22%); transform: translateY( calc(var(--drawer-snap-point-offset, 100%) + var(--drawer-swipe-movement-y, 0px)) ); transition: transform calc(var(--drawer-swipe-strength, 1) * var(--duration-base)) var(--ease-out); } .plan-comment-sheet-grabber { display: grid; width: 100%; height: 2rem; place-items: center; border: 0; background: transparent; color: var(--color-text-tertiary); touch-action: none; } .plan-comment-sheet-grabber span { width: 2.25rem; height: 0.3rem; border-radius: var(--radius-full); background: var(--color-text-tertiary); } .plan-comment-sheet-content { height: calc(100% - 2rem); overflow: auto; padding-right: env(safe-area-inset-right, 0px); padding-bottom: calc(env(safe-area-inset-bottom, 0px) + var(--drawer-keyboard-inset, 0px)); padding-left: env(safe-area-inset-left, 0px); overscroll-behavior: contain; } .plan-comment-sheet-content > [data-plan-comment-card] { border-radius: 0; box-shadow: none; } .plan-comment-sheet-popup[data-starting-style], .plan-comment-sheet-popup[data-ending-style] { transform: translateY(100%); } .plan-comment-sheet-backdrop[data-starting-style], .plan-comment-sheet-backdrop[data-ending-style] { opacity: 0; } @media (prefers-reduced-motion: reduce) { .plan-comment-card { animation: none; } .plan-comment-sheet-backdrop, .plan-comment-sheet-popup { transition-duration: 0ms; } } @media (prefers-reduced-motion: reduce) { .motion-comment-preview, .motion-comment-surface { transition: none; } } .plan-decisions { display: flex; height: 100%; min-width: 0; min-height: 0; flex-direction: column; overflow: hidden; container-type: inline-size; /* The workspace ground shows through the rail. */ background: transparent; } .plan-decisions-content { padding: 1.5rem var(--plan-gutter) 60vh; margin-inline: auto; max-width: calc(var(--plan-measure) + var(--plan-gutter) * 2); } .plan-decisions-content--empty { display: flex; width: 100%; max-width: none; align-items: safe center; justify-content: center; padding: 3rem var(--plan-gutter); } .plan-decisions-empty { display: flex; width: min(100%, 42rem); min-height: 19rem; flex-direction: column; align-items: center; justify-content: center; padding: 2.5rem 1.5rem; border: var(--edge-width) dashed var(--color-control-edge); border-radius: var(--radius-lg); text-align: center; } .plan-decisions-empty-icon { display: grid; width: 3.5rem; height: 3.5rem; place-items: center; margin-block-end: 1.5rem; border-radius: var(--radius-lg); background: var(--color-inset); color: var(--color-text-primary); } .plan-decisions-empty h3 { margin: 0; font-size: var(--text-xl); font-weight: 600; line-height: 1.25; color: var(--color-text-primary); } .plan-decisions-empty p { max-width: 27rem; margin: 0.5rem 0 0; font-size: var(--text-base); line-height: 1.5; color: var(--color-text-tertiary); } /* Decorator hosts are block-level plan content, not zero-size anchors. */ .plan .plan-content [data-plan-questionnaire], .plan .plan-content [data-plan-decision] { margin-block: 1rem; } /* Keep the one editable caret paragraph between consecutive cards compact. */ .plan .plan-content > :is([data-plan-questionnaire], [data-plan-decision]):has(+ p > br:only-child):has(+ p + :is([data-plan-questionnaire], [data-plan-decision])) { margin-block-end: 0; } .plan .plan-content > :is([data-plan-questionnaire], [data-plan-decision]) + p:has(> br:only-child):has(+ :is([data-plan-questionnaire], [data-plan-decision])) { margin-block: 0; } .plan .plan-content > :is([data-plan-questionnaire], [data-plan-decision]) + p:has(> br:only-child) + :is([data-plan-questionnaire], [data-plan-decision]) { margin-block-start: 0; } /* The inner card is hidden after resolution, but its Lexical decorator host also has document spacing. Remove that host from layout as well. */ .plan .plan-content [data-plan-questionnaire]:has([data-card-hidden]) { display: none; margin-block: 0; } .plan .plan-content :is([data-plan-questionnaire], [data-plan-decision]) article { width: 100%; } .decision-collapse { display: grid; grid-template-rows: 1fr; transition: grid-template-rows var(--duration-base) var(--ease-out), opacity var(--duration-base) var(--ease-out); } .decision-collapse.is-closing { grid-template-rows: 0fr; opacity: 0; } @media (prefers-reduced-motion: reduce) { .decision-collapse { transition: none; } } /* * The prose a sidecar card refers to, while somebody is pointing at that card. * * Registered in `CSS.highlights`, so it paints over the text without putting * anything in the document. One mark for both halves of the sidecar: questions * used to outline a block while comments washed a range, so the same fact — * this is the prose that card refers to — read as two different things * depending on which card it came from. `::highlight()` cannot draw an outline, * so the wash is what they can both be. * * Nothing is painted at rest. Standing marks for live comments and for decided * prose were tried and taken out: a plan accumulates decisions, so anything * permanent ends up covering most of the document, and a page that is mostly * highlighted has told you nothing. * * `background-color`, not `background`: only a handful of longhands are * permitted here, and the shorthand works today solely because browsers expand * it before filtering. Not worth depending on. * * The colour is recorded beside the declaration because it is calibrated as * a rendered wash, not as an abstract percentage of a darker accent. */ ::highlight(plan-related) { /* #d8eaee */ background-color: var(--color-brand-wash); } /* * The same pointer, for a browser with no Highlight API. * * A block outline rather than a wash, because without the API there is no way * to mark part of one — less precise, and honest about it. Standing marks are * dropped entirely on this path: outlining every decided passage would box up * half the plan. */ .plan .plan-content [data-plan-related] { border-radius: var(--radius-sm); outline: 1px solid color-mix(in oklch, var(--color-brand) 60%, transparent); outline-offset: 3px; background: var(--color-brand-wash); } /* * Retheme MDXEditor onto Ace's tokens. * * MDXEditor ships a light Radix palette on its own root and only switches off * it for a `.dark-theme` class it expects the host to add. Ace has its own * theme, so rather than opt into a second one we point its variables at ours — * the editor then follows Ace into light or dark with nothing to keep in sync. * * These have to land on the same element MDXEditor sets them on; an ancestor * would lose to its own declaration. */ .plan .mdxeditor { background: transparent; font-family: var(--font-sans); --text-xs: var(--plan-text-xs); --text-sm: var(--plan-text-sm); --text-base: var(--plan-body); --basePageBg: transparent; --baseBase: var(--color-page); --baseBgSubtle: var(--color-inset); --baseBg: var(--color-hover); --baseBgHover: var(--color-hover); --baseBgActive: var(--color-selected); --baseLine: var(--color-edge); --baseBorder: var(--color-edge); --baseBorderHover: var(--color-brand); --baseSolid: var(--color-text-tertiary); --baseSolidHover: var(--color-text-primary); --baseText: var(--color-text-primary); --baseTextContrast: var(--color-text-primary); --accentBase: var(--color-page); --accentBgSubtle: var(--color-brand-wash); --accentBg: var(--color-brand-wash); --accentBgHover: var(--color-brand-wash); --accentBgActive: var(--color-brand-wash); --accentLine: var(--color-edge); --accentBorder: var(--color-edge); --accentBorderHover: var(--color-brand); --accentSolid: var(--color-brand); --accentSolidHover: var(--color-brand-hover); --accentText: var(--color-brand); --accentTextContrast: var(--color-text-primary); --admonitionTipBg: var(--color-success-wash); --admonitionTipBorder: var(--color-success); --admonitionInfoBg: var(--color-brand-wash); --admonitionInfoBorder: var(--color-brand); --admonitionCautionBg: var(--color-warning-wash); --admonitionCautionBorder: var(--color-warning); --admonitionDangerBg: var(--color-destructive-wash); --admonitionDangerBorder: var(--color-destructive); --admonitionNoteBg: var(--color-hover); --admonitionNoteBorder: var(--color-edge); --error-color: var(--color-destructive); --font-body: var(--font-sans); --font-mono: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, monospace; } /* * Scoped to `.plan` on purpose. MDXEditor sets padding and colour directly * on its own content class, which is exactly as specific as ours, so a bare * `.plan-content` wins only by landing later in the bundle. That is a * property of how the CSS happens to be chunked, not of anything we control. */ .plan .plan-content { --plan-leading-gutter: var(--plan-gutter); --plan-wide-inline-size: calc(100cqi - var(--plan-leading-gutter) - var(--plan-gutter)); --plan-wide-inline-offset: calc((100% - var(--plan-wide-inline-size)) / 2); /* * The trailing space is deliberate and large: the caret should never sit * against the bottom of the pane, and a plan is usually appended to. */ padding: 3rem var(--plan-gutter) 60vh; padding-inline-start: var(--plan-leading-gutter); margin-inline: auto; max-width: calc(var(--plan-measure) + var(--plan-gutter) * 2); font-size: var(--plan-body); /* Prose wants looser leading than the UI rhythm the token pairs with. */ line-height: 1.6; color: var(--color-text-primary); } /* * MDXEditor gives its placeholder the content class too. Pin its absolute box * across the wrapper so auto margins center it like the editable document; * without both insets it shrink-wraps left of the caret on a wide canvas. */ .plan .mdxeditor-root-contenteditable > .plan-content { inset-inline: 0; color: var(--color-text-quaternary); } :root[data-plan-coarse-pointer] .plan-menu-row { height: 44px; min-height: 44px; } :root[data-plan-coarse-pointer] .plan-menu-cell { width: 44px; height: 44px; min-width: 44px; } /* * Document typography. * * Tailwind's preflight resets headings to inherit their size and weight and * strips list markers, which is right for an app chrome and wrong for prose. * A plan is prose, so the document elements are given their meaning back here. */ .plan-content :is(h1, h2, h3, h4, h5, h6) { font-weight: 600; line-height: 1.3; margin-block: 1.5em 0.5em; color: var(--color-text-primary); /* Short enough to weigh every break at once, and an 8/1 split reads as a mistake. */ text-wrap: balance; } .plan-content :is(h1, h2, h3, h4, h5, h6):first-child { margin-block-start: 0; } .plan-content h1 { font-size: var(--text-2xl); line-height: var(--text-2xl--line-height); } .plan-content h2 { font-size: var(--text-xl); line-height: var(--text-xl--line-height); } .plan-content h3 { font-size: var(--text-lg); line-height: var(--text-lg--line-height); } .plan-content :is(h4, h5, h6) { font-size: var(--plan-body); } /* iOS Safari zooms the page when it focuses editable text under 16px. */ @media (pointer: coarse) { .plan .plan-content, .plan-content :is(h4, h5, h6) { font-size: max(1rem, var(--plan-body)); } } .plan-content p { margin-block: 0.75em; } .plan-content :is(ul, ol) { margin-block: 0.75em; padding-inline-start: 1.5em; } .plan-content ul { list-style: disc; } .plan-content ol { list-style: decimal; } .plan-content li { margin-block: 0.25em; } /* Nested lists sit tighter than the block they interrupt. */ .plan-content li > :is(ul, ol) { margin-block: 0.25em; } .plan-content blockquote { margin-block: 1em; padding-inline-start: 1em; border-inline-start: 3px solid var(--color-edge); color: var(--color-text-secondary); } .plan-content a { color: var(--color-brand); text-decoration: underline; text-underline-offset: 2px; overflow-wrap: anywhere; } /* Prose has no scrollbar of its own, so an authored token must be allowed to wrap. */ .plan-content :is(p, li, blockquote, h1, h2, h3, h4, h5, h6) { overflow-wrap: anywhere; } .plan-content hr { margin-block: 1.5em; border: 0; border-block-start: var(--edge-width) solid var(--color-edge); } .plan-content :is(strong, b) { font-weight: 600; color: var(--color-text-primary); } /* Structural components ------------------------------------------------- */ .plan-content pre { background: var(--color-inset); border-radius: var(--radius-md); padding: 0.75rem 1rem; overflow-x: auto; font-family: ui-monospace, SFMono-Regular, monospace; font-size: var(--text-sm); } /* Inline code follows its parent heading or prose; block code has its own role. */ .plan-content :not(pre) > code { background: var(--color-inset); color: var(--color-text-primary); border-radius: var(--radius-sm); padding: 0.125em 0.3em; font-family: ui-monospace, SFMono-Regular, monospace; font-size: inherit; } /* * Tables scroll independently so a wide one never widens the document. * * `display: block` turns the table itself into the scrollport. Its inline size * is the readable width, while the cells' minimum widths preserve usable * columns and create overflow inside that scrollport when necessary. */ .plan-content table { display: block; inline-size: 100%; max-inline-size: 100%; overflow-x: auto; border-collapse: collapse; margin-block: 1em; } .plan-content th, .plan-content td { border: var(--edge-width) solid var(--color-edge); padding: 0.375rem 0.625rem; text-align: start; /* Without this a cell collapses onto its neighbour when a column is narrow, and two values read as one word. */ vertical-align: top; min-inline-size: 7rem; /* A table is deliberately allowed to scroll as a whole; breaking each unbroken identifier into syllables makes its columns unreadable. */ word-break: normal; overflow-wrap: normal; } .plan-content th { font-weight: 600; background: var(--color-hover); color: var(--color-text-primary); } /* A cell's paragraph is the cell; it should not add document spacing. */ .plan-content :is(th, td) > p { margin-block: 0; word-break: normal; overflow-wrap: normal; } /* Images and rendered diagrams are readings of the document, never its width. */ .plan-content :is(img, svg) { max-inline-size: 100%; block-size: auto; } /* Visual top-level blocks can use the document beyond the prose measure. */ .plan-content > :is(p:has(> [data-plan-src]:first-child + br[data-lexical-managed-linebreak]:last-child), [data-plan-language="mermaid"]) { inline-size: var(--plan-wide-inline-size); max-inline-size: var(--plan-wide-inline-size); margin-inline: var(--plan-wide-inline-offset); } /* A compact table shrink-wraps around its columns and stays on the document's centre line. */ .plan-content > table { inline-size: max-content; max-inline-size: var(--plan-wide-inline-size); margin-inline: 0; position: relative; inset-inline-start: 50%; translate: -50% 0; } /* The paragraph owns the wide row; the image keeps its intrinsic size inside it. */ .plan-content > p:has(> [data-plan-src]:first-child + br[data-lexical-managed-linebreak]:last-child) { display: grid; place-items: center; } .plan-content :is([data-plan-type], [data-plan-panels], .planMath) { min-inline-size: 0; max-inline-size: 100%; } .plan-content .planMath [data-plan-preview] { max-inline-size: 100%; overflow-x: auto; } /* * A selected range of cells. * * `@lexical/table` marks one by adding the class the theme names under * `tableCellSelected` and does nothing else at all — no inline style, no * fallback. MDXEditor's theme names no table class of any kind, so before * `plan-editor.tsx` added these the selection was live and completely * invisible: dragging across cells appeared to do nothing while a * `TableSelection` was very much in effect, and the next keystroke replaced * everything it covered. */ .plan .plan-content .plan-cell-selected { background: var(--color-selected); } /* Set on the table while a cell range is being dragged out, so the browser's own text selection does not paint underneath ours. */ .plan .plan-content .plan-table-selecting, .plan .plan-content .disable-selection { user-select: none; } /* Table rails ------------------------------------------------------------ */ /* * The grips a table is reshaped from. * * Positioned by script from measured cell rectangles, because a `` * cannot contain a `
` and there is nowhere inside the node to put them. * So: `position: fixed`, and no Tailwind translate utility anywhere near them * — v4 emits the `translate` property, which composes with a transform rather * than replacing it. */ /* * The rail takes pointer events across the whole of itself, holes included. * * It is tempting to pass the gaps through so the prose behind stays clickable, * and that is what this did first. But `pointerleave` fires the instant the * pointer crosses anything untargetable, so every hole was somewhere the rails * blinked out from under a hand on its way across them. A rail exists only * while its table is being pointed at, and it is 28 pixels of a 32-pixel * gutter; the prose it covers for that moment is not worth the flicker. */ .plan-rail { position: fixed; z-index: 40; overflow: hidden; } .plan-rail > * { position: absolute; padding: 0; border: 0; background: transparent; } .plan-grip { border-radius: var(--radius-sm); background: var(--color-selected); transition: background-color var(--duration-fast) var(--ease-out); } .plan-grip:hover, .plan-grip:focus-visible { background: color-mix(in oklch, var(--color-brand) 45%, transparent); } [data-plan-rail="column"] .plan-grip { cursor: grab; } [data-plan-rail="row"] .plan-grip { cursor: grab; } .plan-grip.is-held { cursor: grabbing; background: var(--color-brand); } /* The header row's bar. Drawn so the rail has no gap where the row plainly is, but not a control: the header cannot move and cannot be removed. */ .plan-grip.is-fixed { background: color-mix(in oklch, var(--color-edge) 60%, transparent); cursor: default; } /* * Remove and insert, in the lane beyond the grips. * * Nothing here overlaps a grip, so a drag can begin anywhere along one. Hiding * is `opacity` rather than `display`, so a button keeps its size and its place * in the tab order while it is out of sight — and `pointer-events` with it, so * an invisible cross cannot be clicked by accident. */ .plan-grip-remove, .plan-insert, .plan-align { opacity: 0; pointer-events: none; transition: opacity var(--duration-fast) var(--ease-out); border-radius: var(--radius-sm); background: var(--color-page); outline: var(--edge-width) solid var(--color-control-edge); outline-offset: calc(-1 * var(--edge-width)); color: var(--color-text-quaternary); } .plan-grip-remove[data-plan-shown], .plan-align[data-plan-shown], .plan-grip-remove:focus-visible, .plan-align:focus-visible, .plan-rail:hover .plan-insert, .plan-insert:focus-visible { opacity: 1; pointer-events: auto; } .plan-grip-remove:hover, .plan-insert:hover, .plan-align:hover { color: var(--color-text-primary); outline-color: var(--color-brand); } /* Drawn rather than lettered: at fifteen pixels a glyph is a smudge, and these have to read at a glance in the margin of a paragraph. */ .plan-grip-remove::before, .plan-grip-remove::after, .plan-insert::before, .plan-insert::after { content: ""; position: absolute; inset: 50% 25%; border-top: 1.5px solid currentcolor; translate: 0 -50%; } .plan-grip-remove::before { rotate: 45deg; } .plan-grip-remove::after { rotate: -45deg; } .plan-insert::after { rotate: 90deg; } /* * The alignment button reads as what it does: two lines, the lower one short * and sitting where the text will. A column with no alignment of its own shows * the same figure at half strength rather than a fourth icon, because "no * alignment" and "left" render identically in the table and a control claiming * otherwise would be lying about the source. */ .plan-align::before, .plan-align::after { content: ""; position: absolute; left: 3px; right: 3px; border-top: 1.5px solid currentcolor; } .plan-align::before { top: 5px; } .plan-align::after { top: 9px; } .plan-align[data-plan-align="default"] { opacity: 0; } .plan-align[data-plan-align="default"][data-plan-shown], .plan-align[data-plan-align="default"]:focus-visible { opacity: 0.55; } .plan-align[data-plan-align="default"]::after, .plan-align[data-plan-align="left"]::after { right: 7px; } .plan-align[data-plan-align="center"]::after { left: 5px; right: 5px; } .plan-align[data-plan-align="right"]::after { left: 7px; } /* Where the drag will land. A hairline, because it is a position rather than a region: anything wider reads as the width of what is being moved. */ .plan-drop { background: var(--color-brand); border-radius: 1px; pointer-events: none; } .plan-table-action-toolbar { position: fixed; z-index: 50; display: block; max-width: calc(100vw - 1rem); padding: 0.25rem; border-radius: var(--radius-lg); background: var(--color-page); box-shadow: var(--shadow-overlay); } .plan-table-action-groups, .plan-table-action-panel { display: grid; grid-template-columns: repeat(4, minmax(0, 1fr)); gap: 0.25rem; } .plan-table-action-panel { grid-template-columns: repeat(2, minmax(0, 1fr)); padding-block-start: 0.25rem; border-block-start: var(--edge-width) solid var(--color-edge); } .plan-table-action { width: 100%; min-width: 0; min-height: 44px; padding: 0.375rem 0.5rem; border-radius: var(--radius-md); color: var(--color-text-secondary); font-size: var(--text-sm); line-height: 1.15; } .plan-table-action[aria-expanded="true"] { background: var(--color-selected); color: var(--color-text-primary); } .plan-table-action:hover, .plan-table-action:focus-visible { background: var(--color-hover); color: var(--color-text-primary); } .plan-table-action:disabled { opacity: 0.45; } @media (prefers-reduced-motion: reduce) { .plan-grip, .plan-grip-remove, .plan-insert { transition: none; } } /* Remote cursors --------------------------------------------------------- */ /* * The frame remote cursors are measured against. It wraps the prose and grows * with it, so carets scroll with the text they belong to. */ .plan .plan-frame { position: relative; } /* * Lexical owns the caret's geometry — position, height and offsets are written * inline on every paint — and hands us the peer's colour as a custom property. * Only appearance belongs here. The shadow widens a 1px caret that would * otherwise be easy to miss, without fighting the inline width. */ .plan .plan-cursor { background-color: var(--lexical-cursor-color, currentColor); box-shadow: 0 0 0 0.5px var(--lexical-cursor-color, currentColor); border-radius: 1px; } .plan .plan-cursor-name { position: absolute; inset-block-end: 100%; inset-inline-start: -1px; margin-block-end: 2px; padding: 0 0.25rem; border-radius: var(--radius-sm); background: var(--lexical-cursor-color, currentColor); color: var(--color-page); font-size: var(--text-sm); line-height: 1rem; font-weight: 500; white-space: nowrap; max-inline-size: min(20rem, calc(100cqi - 2rem)); overflow: hidden; text-overflow: ellipsis; pointer-events: none; opacity: 0; transition: opacity var(--duration-base) var(--ease-out); } /* Raised while its peer is moving, then left to fade. */ .plan .plan-cursor[data-plan-active] .plan-cursor-name { opacity: 1; } /* * Where selections fall back to rect overlays, because the browser has no * Highlight API. The supported path is styled by Lexical from the same colour. */ .plan .plan-cursor-selection-bg { background-color: color-mix(in srgb, var(--lexical-cursor-color, currentColor) 30%, transparent); } @media (prefers-reduced-motion: reduce) { .plan * { transition: none !important; animation: none !important; } /* * Without the fade, a label would blink in and out on every keystroke a * peer makes. Leaving it up is the quieter of the two. */ .plan .plan-cursor-name { opacity: 1; } } /* Rendered previews ------------------------------------------------------ */ .plan-content [data-plan-preview]:empty { display: none; } /* * On the message rather than on the slot: everything in the slot is React's * now, including this, so the attribute goes on the element that says so * rather than on the element that holds it. */ .plan-content [data-plan-preview] [data-plan-error] { color: var(--color-destructive); font-size: var(--text-sm); padding: 0.5rem 0; } /* * Coloured code, and diffs. * * The renderer paints inside a shadow root, so none of the rules above reach * it and none of its reach out. What crosses is inherited: the custom * properties it reads for type, which are set here so a fence and its source * are the same size in the same face, and nothing else. */ .plan-content .plan-code-view { --diffs-font-family: ui-monospace, SFMono-Regular, monospace; --diffs-font-size: var(--text-sm); --diffs-line-height: 1.5; --diffs-tab-size: 2; --diffs-header-font-family: var(--font-sans); max-inline-size: 100%; overflow-x: auto; } /* The renderer's shadow root owns syntax painting; this host supplies the * readable width that the surrounding widget scrolls when a line exceeds it. */ .plan-content .plan-code-view > diffs-container { min-inline-size: max-content; } /* Each file in a patch is its own box, and they stack. */ .plan-content .plan-code-view diffs-container + diffs-container { display: block; margin-block-start: 0.5rem; } /* Source stays visible and editable beneath its preview. */ .plan-content [data-plan-source] { white-space: pre-wrap; overflow-wrap: normal; font-family: ui-monospace, SFMono-Regular, monospace; font-size: var(--text-sm); } /* * A general sibling, not an adjacent one: the block's chrome sits between the * preview and the source, and a block has exactly one source anyway. */ .plan-content [data-plan-preview]:not(:empty) ~ [data-plan-source] { opacity: 0.6; font-size: var(--text-sm); } /* * A block formula is an editable region exactly like a code block, and has to * read as one: the rendered result on the page, its source in a box below. * * A code block gets that box from the generic `pre` rule, and a formula renders * the same structure into plain `div`s — so without this it is a bare line of * monospace with no background, no padding and nothing in it, and inserting one * looks like the command did nothing at all. The box belongs to the source * alone; wrapping the preview too would put the rendered formula in a grey * well, which is not what a diagram does. */ .plan-content .planMath[data-plan-inline="false"] [data-plan-source] { --ace-formula-inset-block: 0.75rem; --ace-formula-inset-inline: 1rem; /* * No `display` here. A `div` is already block, so declaring it again buys * nothing — and this selector outranks the rule that hides the source when * the block is collapsed, which would keep a formula on screen forever. */ position: relative; background: var(--color-inset); border-radius: var(--radius-md); padding: var(--ace-formula-inset-block) var(--ace-formula-inset-inline); overflow-x: auto; } /* * Say what belongs in an empty one. Lexical fills an empty block with a managed * line break, so `:empty` never matches and the placeholder keys off that * instead. It sits out of flow so it cannot displace the caret in front of it, * which means it has to repeat the padding the caret is already inside. */ .plan-content .planMath[data-plan-inline="false"] [data-plan-source]:has(> br[data-lexical-managed-linebreak]:only-child)::before { content: "LaTeX, e.g. E = mc^2"; position: absolute; inset-block-start: var(--ace-formula-inset-block); inset-inline-start: var(--ace-formula-inset-inline); color: var(--color-text-quaternary); pointer-events: none; } /* * Showing a result without its source. * * Not the `hidden` attribute: a formula's source sets `display: block`, which * outranks the user agent's rule for `hidden` and would leave it on screen. */ .plan-content [data-plan-collapsed] [data-plan-source] { display: none; } .plan-content [data-plan-preview] svg { max-width: 100%; height: auto; } .plan-content .plan-diagram { max-inline-size: 100%; overflow-x: auto; overflow-y: hidden; overscroll-behavior-inline: contain; } .plan-content .plan-diagram svg { display: block; max-width: none; margin-inline: auto; } /* * A code block is one box: header, rendered code, source. A border rather than * an outline, because the renderer paints over an inset outline. */ .plan-content .planCode { display: flex; flex-direction: column; margin-block: 1em; overflow: hidden; border: var(--edge-width) solid var(--color-edge); border-radius: var(--radius-lg); background: var(--color-page); } .plan-content .planCode [data-plan-chrome="block"] { order: -1; padding: 0.25rem; border-block-end: var(--edge-width) solid var(--color-edge); background: var(--color-inset); } .plan-content .plan-code-toggle[aria-expanded="true"] { background: var(--color-brand-wash); color: var(--color-brand); } /* A global icon colour rule would otherwise keep the icon grey. */ .plan-content .plan-code-toggle[aria-expanded="true"] [data-nucleo-icon] { color: inherit; } .plan-content .plan-code-language-label { padding-inline: 0.5rem; font-size: var(--text-sm); color: var(--color-text-tertiary); } .plan-content .planCode .plan-code-view { border-radius: 0; } /* * One left edge for every line of text in the block: the language label, * rendered code, and source all start 0.75rem in. The renderer pads each line * by 1ch inside its shadow root, so its wrapper supplies the remainder in the * same monospace face. Diffs keep their gutter, which is its own column. */ .plan-content .planCode .plan-code-view[data-view="file"] { /* The renderer adds 8px above its code and 8px below; make both 0.75rem. */ padding-block: calc(0.75rem - 8px); padding-inline-start: calc(0.75rem - 1ch); font-family: ui-monospace, SFMono-Regular, monospace; font-size: var(--text-sm); } /* * Editable code and formula source also stays at 16px on touch, so iOS does * not zoom. The rendered code follows so a fence and its source still match. */ @media (pointer: coarse) { .plan-content :is(pre, [data-plan-source]), .plan-content [data-plan-preview]:not(:empty) ~ [data-plan-source], .plan-content .planCode .plan-code-view[data-view="file"] { font-size: max(1rem, var(--text-sm)); } .plan-content .plan-code-view { --diffs-font-size: max(1rem, var(--text-sm)); } } .plan-content .planCode [data-plan-source] { margin: 0; padding-inline: 0.75rem; border-radius: 0; background: var(--color-page); } .plan-content .planCode [data-plan-preview]:not(:empty) ~ [data-plan-source] { border-block-start: var(--edge-width) solid var(--color-edge); background: var(--color-inset); } .plan-content .planCode [data-plan-preview] :is(.plan-diagram, [data-plan-error]) { padding-inline: 0.75rem; } /* Diagram error callout. */ .plan-content .planCode [data-plan-preview] [data-plan-error]:has(.plan-error-badge) { display: flex; align-items: flex-start; gap: 0.625rem; margin: 0.75rem; padding: 0.625rem 0.75rem; border-radius: var(--radius-md); background: var(--color-ruby-2); } .plan-content .plan-error-badge { display: grid; flex-shrink: 0; place-items: center; width: 1.5rem; height: 1.5rem; border-radius: 50%; background: var(--color-danger-surface); color: var(--color-destructive); } .plan-content .plan-error-badge [data-nucleo-icon] { color: inherit; } .plan-content .plan-error-text { min-width: 0; } .plan-content .plan-error-title { display: block; font-size: var(--text-sm); font-weight: 600; color: var(--color-destructive-ink); } .plan-content .plan-error-message { margin: 0.125rem 0 0; font-size: var(--text-sm); color: var(--color-destructive-ink); overflow-wrap: anywhere; line-height: var(--text-sm--line-height); } .plan-content .plan-error-detail { margin: 0.5rem 0 0; padding: 0; overflow-x: auto; background: transparent; font-size: var(--text-xs); line-height: var(--text-xs--line-height); color: var(--color-destructive-ink); } .plan-content .plan-error-expected { margin: 0.375rem 0 0; font-size: var(--text-sm); line-height: var(--text-sm--line-height); color: var(--color-destructive-ink); overflow-wrap: anywhere; } .plan-content .plan-code-title { min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; font-size: var(--text-sm); color: var(--color-text-secondary); } .plan-content .plan-code-title::before { content: "·"; margin-inline: 0.125rem 0.375rem; color: var(--color-text-quaternary); } /* Tabs ------------------------------------------------------------------- */ .plan-content [data-plan-chrome="tabs"] { margin-block-end: 0.5rem; } .plan-content [data-plan-panels] > [hidden] { display: none; } /* Status ----------------------------------------------------------------- */ /* * Floats over the end of the prose rather than taking a row from it. Never * interactive, so it stays out of the way of selecting the text underneath. */ .plan-status { position: absolute; z-index: 2; inset-block-end: 0.75rem; inset-inline-end: 0.75rem; display: flex; align-items: center; gap: 0.375rem; max-width: min(22rem, calc(100% - 1.5rem)); pointer-events: none; font-size: var(--text-sm); line-height: var(--text-sm--line-height); } /* * A hidden state is not `display: none`: the label stays in the accessibility * tree so the live region keeps reporting durability. With every child * screen-reader-only the box collapses to nothing on screen. */ .plan-status[data-level="notice"] { padding: 0.25rem 0.625rem; border-radius: var(--radius-full); background: var(--color-page); outline: var(--edge-width) solid var(--color-edge); outline-offset: calc(-1 * var(--edge-width)); box-shadow: var(--shadow-resting); } .plan-status-dot { flex: none; width: 0.375rem; height: 0.375rem; border-radius: var(--radius-full); background: currentColor; } /* What the agent changed ------------------------------------------------- */ /* * Marks that take no space and add no element. * * An agent editing below the fold must not shift the sentence somebody is in * the middle of typing, and a mark that expires a few seconds later must not * shift it back. So a block is washed, and a hole is drawn in `box-shadow` on * the edge of the block still beside it. A `::before` rule was the obvious way * to draw a hole and is the wrong one: it needs the block to become a * containing block, which re-anchors any absolutely positioned widget chrome * inside it for as long as the mark is up. * * Two colours, and they mean different things. `ring` is prose that was * written; `muted-foreground` is structure that moved or went, where no words * are new. Where the agent is working *now* is not said here at all — that is * its cursor, which comes and goes with the turn rather than waiting to be * read. */ .plan .plan-content [data-plan-change] { transition: background var(--duration-linger) var(--ease-out); } /* Written just now. */ .plan .plan-content [data-plan-change="added"] { background: var(--color-brand-wash); } /* * Moved, not written. In the structural colour rather than a fainter version * of the one above: the words are ones the reader may already have read, and * saying "new, but less so" about them would not be true. */ .plan .plan-content [data-plan-change="moved"] { background: color-mix(in oklch, var(--color-text-quaternary) 9%, transparent); } /* * A hole where something was. * * One block can hold a hole above it and another below — delete the second and * fourth of four blocks and the third carries both — so the two sides cannot * each set `box-shadow`, or the cascade would keep only whichever rule came * last and one hairline would silently never draw. Custom properties do * cascade independently, so each side sets one and they compose here. */ .plan .plan-content [data-plan-gap-before], .plan .plan-content [data-plan-gap-after] { box-shadow: var(--plan-gap-above, 0 0 transparent), var(--plan-gap-below, 0 0 transparent); transition: box-shadow var(--duration-linger) var(--ease-out); } .plan .plan-content [data-plan-gap-before="removed"] { --plan-gap-above: 0 -0.375rem 0 -0.3125rem var(--color-text-quaternary); } .plan .plan-content [data-plan-gap-after="removed"] { --plan-gap-below: 0 0.375rem 0 -0.3125rem var(--color-text-quaternary); } /* * Where a block left from, as opposed to where one was deleted. Fainter, since * nothing was lost — the content is still in the plan, further up or down. */ .plan .plan-content [data-plan-gap-before="vacated"] { --plan-gap-above: 0 -0.375rem 0 -0.34375rem color-mix( in oklch, var(--color-text-quaternary) 55%, transparent ); } .plan .plan-content [data-plan-gap-after="vacated"] { --plan-gap-below: 0 0.375rem 0 -0.34375rem color-mix( in oklch, var(--color-text-quaternary) 55%, transparent ); } /* * The agent wrote this block and the reader is pointing at a card that refers * to it. * * Only reachable where there is no Highlight API, since that is the only path * on which a sidecar mark touches the element's own background. The two rules * have equal specificity, so without saying so the later one — this file's — * would simply replace the earlier and the pointer would lose its wash. * Pointing is something the reader just did and the agent's mark is ambient, * so the pointer wins; the gutter bar is in `box-shadow` and survives either * way. */ .plan .plan-content [data-plan-related][data-plan-change] { background: var(--color-brand-wash); } /* Reaching the ones off screen ------------------------------------------- */ /* * One chip per edge, over the prose rather than in it, and only while * something in that direction is still unread. Opposite the status chip at the * bottom, which is why the lower one sits inline-start of it. */ .plan-changes { position: absolute; z-index: 3; inset-inline-start: 50%; translate: -50% 0; display: flex; flex-direction: column; gap: 0.375rem; max-width: min(24rem, calc(100% - 1.5rem)); } .plan-changes[data-side="above"] { inset-block-start: 0.75rem; } .plan-changes[data-side="below"] { inset-block-end: 0.75rem; flex-direction: column-reverse; } .plan-changes-bar { display: flex; align-self: center; align-items: stretch; border-radius: var(--radius-full); background: var(--color-page); outline: var(--edge-width) solid var(--color-edge); outline-offset: calc(-1 * var(--edge-width)); box-shadow: var(--shadow-resting); font-size: var(--text-sm); line-height: var(--text-sm--line-height); max-inline-size: 100%; } .plan-changes-go, .plan-changes-more { min-inline-size: 0; display: flex; align-items: center; gap: 0.375rem; padding: 0.25rem 0.625rem; color: var(--color-text-primary); cursor: pointer; } .plan-changes-more { border-start-end-radius: var(--radius-full); border-end-end-radius: var(--radius-full); padding-inline: 0.375rem; border-inline-start: var(--edge-width) solid var(--color-edge); } .plan-changes-go { border-start-start-radius: var(--radius-full); border-end-start-radius: var(--radius-full); } .plan-changes-go:hover, .plan-changes-more:hover { background: var(--color-brand-wash); } .plan-changes-list, .plan-changes-empty { margin: 0; padding: 0.375rem; max-height: 12rem; overflow-y: auto; list-style: none; border-radius: var(--radius-md); background: var(--color-page); outline: var(--edge-width) solid var(--color-edge); outline-offset: calc(-1 * var(--edge-width)); box-shadow: var(--shadow-overlay); font-size: var(--text-sm); line-height: var(--text-sm--line-height); } .plan-changes-empty { padding: 0.5rem 0.625rem; color: var(--color-text-quaternary); } .plan-changes-item { display: flex; gap: 0.5rem; padding: 0.25rem 0.5rem; border-radius: var(--radius-sm); } .plan-changes-kind { flex: none; font-weight: 600; color: var(--color-text-tertiary); } /* Still waiting to be reached, as opposed to already shown and counted down. */ .plan-changes-item[data-unread] { background: var(--color-brand-wash); } .plan-changes-item[data-unread]::before { content: ""; flex: none; width: 0.375rem; height: 0.375rem; margin-block-start: 0.4375rem; border-radius: var(--radius-full); background: var(--color-brand); } .plan-changes-item[data-unread] .plan-changes-kind { color: var(--color-text-primary); } .plan-changes-item[data-kind="removed"] .plan-changes-text { color: var(--color-text-secondary); text-decoration: line-through; } /* One line each: this is an index, not a diff. */ .plan-changes-text { overflow: hidden; white-space: nowrap; text-overflow: ellipsis; } ::highlight(plan-decided) { background-color: var(--color-success-wash); } /* Presence on a decision card: aligned to the 1.5rem mark beside the title. */ .presence-faces { display: flex; flex: none; align-items: center; block-size: 1.5rem; } .presence-face + .presence-face { margin-inline-start: -0.375rem; } .presence-more { margin-inline-start: 0.25rem; font-size: var(--text-xs); font-weight: 600; color: var(--color-text-tertiary); font-variant-numeric: tabular-nums; } /* * A resolved decision: a margin marker beside the prose it produced. * * Reader-local chrome drawn over the document like the comment layer, and * for the same reason: nothing here may move a collaborative line box. */ ::highlight(plan-decision) { background-color: var(--color-success-wash); } /* A card carried by markers takes no room in the plan. */ .plan .plan-content [data-plan-questionnaire]:has([data-plan-collapsed]) { margin-block: 0; } .plan-decision-layer { pointer-events: none; position: absolute; inset: 0; z-index: 2; } .plan-decision-marker { --plan-decision-hit: 1.25rem; pointer-events: auto; position: absolute; display: grid; place-items: center; inline-size: var(--plan-decision-hit); block-size: var(--plan-decision-hit); padding: 0; border-radius: var(--radius-full); } /* A touch screen gets the comment button's 44px target around the same disc. */ :root[data-plan-coarse-pointer] .plan-decision-marker { --plan-decision-hit: 2.75rem; } .plan-decision-disc { display: grid; place-items: center; inline-size: 1.25rem; block-size: 1.25rem; border-radius: var(--radius-full); background: var(--color-success-wash); color: var(--color-success); } /* Too narrow a gutter for a disc: a slim bar the height of the first line. */ .plan-decision-marker[data-compact] { place-items: center start; border-radius: 0; } .plan-decision-marker[data-compact] .plan-decision-disc { inline-size: 0.25rem; block-size: var(--plan-decision-line, 1lh); margin-inline-start: var(--plan-decision-bar, 0px); background: var(--color-success); } .plan-decision-marker[data-compact] svg { display: none; } .plan-decision-marker:hover .plan-decision-disc, .plan-decision-marker[aria-expanded="true"] .plan-decision-disc { outline: var(--edge-width) solid var(--color-success); } .plan-decision-pop { max-width: none; padding: 0.75rem; border-radius: var(--radius-lg); font-size: var(--text-sm); line-height: var(--text-sm--line-height); } .plan-decision-pop[data-pinned] { pointer-events: auto; } .plan-decision-pop p { margin: 0; } .plan-decision-head { display: flex; align-items: flex-start; gap: 0.5rem; } .plan-decision-question { flex: 1; line-height: var(--text-xs--line-height); font-weight: 500; color: var(--color-text-secondary); } .plan-decision-pop .plan-decision-tools { display: flex; margin: -0.125rem -0.25rem 0 0; opacity: 0; pointer-events: none; transition: opacity var(--duration-fast) var(--ease-out); } /* The close button is a compact 24px, like the comment card's. */ .plan-decision-tools .btn-icon { inline-size: 1.5rem; block-size: 1.5rem; min-inline-size: 1.5rem; min-block-size: 1.5rem; padding: 0; } .plan-decision-pop[data-pinned] .plan-decision-tools { opacity: 1; pointer-events: auto; } .plan-decision-pop .plan-decision-answer { display: flex; align-items: center; gap: 0.375rem; margin-block-start: 0.375rem; font-size: var(--text-sm); font-weight: 500; color: var(--color-success); } .plan-decision-pop .plan-decision-answer > span { display: inline; margin: 0; color: var(--color-text-primary); } /* What was not chosen: same size, lighter weight, a grey cross in the check's place. */ .plan-decision-pop .plan-decision-answer[data-rejected] { margin-block-start: 0.25rem; font-weight: 400; color: var(--color-text-quaternary); } .plan-decision-pop .plan-decision-answer[data-rejected] > span { color: var(--color-text-secondary); } .plan-decision-meta { display: flex; flex-wrap: wrap; align-items: center; column-gap: 1rem; row-gap: 0.5rem; /* A clear space separates what was decided from who and when. */ margin-block-start: 1rem; font-size: var(--text-xs); line-height: var(--text-xs--line-height); color: var(--color-text-tertiary); } .plan-decision-pop .plan-decision-meta > p { display: flex; align-items: flex-start; gap: 0.375rem; min-width: 0; overflow-wrap: anywhere; } .plan-decision-meta > p > :first-child { flex: none; } .plan-decision-meta > p > svg { margin-block: calc((1lh - 0.875rem) / 2); } .plan-decision-meta > p > :is(img, [role="img"]) { margin-block: calc((1lh - 1.125rem) / 2); } .plan-decision-pop .plan-decision-meta span { display: inline; margin: 0; color: inherit; } .plan-decision-meta strong { font-weight: 500; color: var(--color-text-secondary); } .plan-decision-fold { display: grid; grid-template-rows: 0fr; transition: grid-template-rows var(--duration-base) var(--ease-out); } .plan-decision-fold > div { overflow: hidden; } .plan-decision-pop[data-pinned] .plan-decision-fold { grid-template-rows: 1fr; } @media (prefers-reduced-motion: reduce) { .plan-decision-fold, .plan-decision-tools, .plan-decision-disc { transition: none; } }