/**
* 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";
.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;
}
/* 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: 3rem var(--plan-gutter) 60vh;
margin-inline: auto;
max-width: calc(var(--plan-measure) + var(--plan-gutter) * 2);
}
/* 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;
}
.plan .plan-content :is([data-plan-questionnaire], [data-plan-decision]) article {
width: 100%;
}
/*
* 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-wide-inline-size: calc(100cqi - 2 * 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;
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);
}
.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);
}
.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;
}
/* 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: grid;
gap: 0.75rem;
/* 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;
}
.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;
}
}