@import "tailwindcss"; @import "@chopin/visuals/theme.css"; @import "./conversation-plan/source.css"; /* * Sibling packages that build on these utilities. * * They are reached through workspace symlinks, which Tailwind's automatic * content detection skips — without these the classes in the editor and the * questionnaire views resolve to nothing at all, silently. * `.ts` is included because shared class lists can live outside components. */ @source "../../../packages/editor/src/**/*.{ts,tsx}"; @source "../../../packages/question/src/**/*.{ts,tsx}"; @utility ring-hairline { outline: var(--edge-width) solid var(--color-edge); outline-offset: calc(-1 * var(--edge-width)); } :where([data-nucleo-icon]) { color: var(--color-icon); } :where(.btn-primary, .btn-destructive) [data-nucleo-icon] { color: inherit; } @utility control-edge { border: var(--edge-width) solid var(--color-control-edge); &:disabled { border-color: var(--color-edge); } } @utility btn { display: inline-flex; align-items: center; justify-content: center; gap: calc(var(--spacing) * 2); border: 0; border-radius: var(--radius-md); font-size: var(--text-sm); font-weight: 500; white-space: nowrap; &:disabled { cursor: default; opacity: 0.4; } &[aria-busy="true"]:disabled { opacity: 0.65; } &[aria-busy="true"] [data-button-loader] { animation: button-loader-spin 800ms linear infinite; } } @keyframes button-loader-spin { to { transform: rotate(360deg); } } @utility btn-md { height: 2rem; padding-inline: calc(var(--spacing) * 3); } @utility btn-sm { height: 1.5rem; padding-inline: calc(var(--spacing) * 2); } @utility btn-icon { width: 1.75rem; height: 1.75rem; padding: 0.4375rem; } @utility btn-primary { background-color: var(--color-brand); box-shadow: var(--shadow-resting); color: var(--color-page); outline: var(--button-edge-width) solid var(--color-control-edge); outline-offset: calc(-1 * var(--button-edge-width)); &:hover:not(:disabled) { background-color: var(--color-brand-hover); } &:active:not(:disabled) { background-color: var(--color-brand-active); } } @utility btn-secondary { background-color: var(--color-gray-200); color: var(--color-text-primary); outline: var(--button-edge-width) solid var(--color-edge); outline-offset: calc(-1 * var(--button-edge-width)); &:hover:not(:disabled) { background-color: var(--color-gray-300); } &:active:not(:disabled) { background-color: var(--color-gray-400); } } @utility btn-outline { background-color: var(--color-page); color: var(--color-text-primary); outline: var(--button-edge-width) solid var(--color-control-edge); outline-offset: calc(-1 * var(--button-edge-width)); box-shadow: var(--shadow-resting); &:hover:not(:disabled) { background-color: var(--color-inset); } &:active:not(:disabled) { background-color: var(--color-gray-200); box-shadow: none; } } @utility btn-ghost { background-color: transparent; color: var(--color-text-secondary); &:hover:not(:disabled) { background-color: var(--color-gray-200); } &:active:not(:disabled) { background-color: var(--color-gray-300); } } @utility btn-destructive { background-color: var(--color-destructive); color: var(--color-page); outline: var(--button-edge-width) solid var(--color-control-edge); outline-offset: calc(-1 * var(--button-edge-width)); &:hover:not(:disabled) { background-color: var(--color-destructive-hover); } &:active:not(:disabled) { background-color: var(--color-destructive-active); } } @utility field { border: var(--edge-width) solid var(--color-control-edge); border-radius: var(--radius-md); background-color: var(--color-page); box-shadow: var(--shadow-resting); color: var(--color-text-primary); &:disabled { border-color: var(--color-edge); background-color: var(--color-gray-200); color: var(--color-gray-600); } &[aria-invalid="true"] { border-color: var(--color-destructive); outline: 2px solid var(--color-destructive); outline-offset: 2px; } } @utility field-ghost { border: var(--edge-width) solid transparent; border-radius: var(--radius-md); background-color: transparent; color: var(--color-text-primary); &:hover:not(:disabled) { border-color: var(--color-control-edge); } &:disabled { border-color: var(--color-edge); background-color: var(--color-gray-200); color: var(--color-gray-600); } } @utility choice-control { appearance: none; border: var(--edge-width) solid var(--color-control-edge); background-color: var(--color-page); color: var(--color-text-primary); &[type="radio"] { border-radius: var(--radius-full); } &[type="checkbox"] { border-radius: var(--radius-sm); } &:checked { border-color: var(--color-brand); background-color: var(--color-brand); } &[type="radio"]:checked { border-width: calc(var(--edge-width) * 2); box-shadow: inset 0 0 0 3px var(--color-page); } &[type="checkbox"]:checked { background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 14 14'%3E%3Cpath fill='none' stroke='white' stroke-linecap='round' stroke-linejoin='round' stroke-width='2' d='m3 7 2.5 2.5L11 4'/%3E%3C/svg%3E"); } &:disabled { border-color: var(--color-edge); background-color: var(--color-gray-200); color: var(--color-gray-600); } &:disabled:checked { background-color: var(--color-gray-600); } &[type="radio"]:disabled:checked { box-shadow: inset 0 0 0 3px var(--color-gray-200); } &[type="checkbox"]:disabled:checked { background-color: var(--color-gray-200); background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 14 14'%3E%3Cpath fill='none' stroke='%23605e56' stroke-linecap='round' stroke-linejoin='round' stroke-width='2' d='m3 7 2.5 2.5L11 4'/%3E%3C/svg%3E"); } } /* Extra contrast is an opt-in accommodation, not the normal control edge. */ @media (prefers-contrast: more) { :where(.control-edge, .field, .choice-control):not([aria-invalid="true"]) { outline: var(--edge-width) solid var(--color-control-boundary); outline-offset: calc(-1 * var(--edge-width)); } :where(.control-edge, .field, .choice-control):disabled { outline-color: var(--color-edge); } } @utility hairline-t { border-top: var(--edge-width) solid var(--color-edge); } @utility hairline-r { border-right: var(--edge-width) solid var(--color-edge); } @utility hairline-b { border-bottom: var(--edge-width) solid var(--color-edge); } @utility hairline-l { border-left: var(--edge-width) solid var(--color-edge); } @media (min-resolution: 2dppx) { :root { --edge-width: 0.5px; } } html, body { height: 100%; overflow-x: hidden; } #root { position: fixed; left: var(--app-left, 0px); top: var(--app-top, 0px); width: var(--app-width, 100%); height: 100%; height: var(--app-height, 100dvh); } .motion-dropdown { 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-dropdown.is-open { opacity: 1; transform: scale(1); } .motion-dropdown.is-closing { pointer-events: none; opacity: 0; transform: scale(var(--dropdown-closing-scale)); transition-duration: var(--dropdown-close-dur); } .motion-popover { opacity: 0; transform: scale(var(--dropdown-pre-scale)); transform-origin: var(--motion-origin-x, 50%) var(--motion-origin-y, 50%); transition: opacity var(--dropdown-open-dur) var(--dropdown-ease), transform var(--dropdown-open-dur) var(--dropdown-ease); will-change: opacity, transform; } .motion-popover.is-open { opacity: 1; transform: scale(1); } .motion-popover.is-closing { pointer-events: none; opacity: 0; transform: scale(var(--dropdown-closing-scale)); transition-duration: var(--dropdown-close-dur); } .motion-popover[data-motion-immediate] { transition: none; } .motion-collapse { display: grid; grid-template-rows: 0fr; transition: grid-template-rows var(--acc-expand) var(--motion-move); } .motion-collapse-content { min-height: 0; overflow: hidden; opacity: 0; transform: translateY(-4px); transition: opacity var(--acc-expand) var(--motion-move), transform var(--acc-expand) var(--motion-move); } .motion-collapse.is-open { grid-template-rows: 1fr; } .motion-collapse.is-open > .motion-collapse-content { opacity: 1; transform: translateY(0); } .motion-collapse.is-closing { grid-template-rows: 0fr; transition-duration: var(--acc-collapse); } .motion-collapse.is-closing > .motion-collapse-content { opacity: 0; transform: translateY(-4px); transition-duration: var(--acc-collapse); } .motion-feedback[data-motion-feedback="icon"] { transform: none; } .motion-feedback[data-motion-feedback="count"] { transform: none; } .motion-feedback[data-motion-feedback="alert"] { transform: none; } :root[data-motion-input="pointer"] .motion-feedback[data-motion-feedback="icon"] { transition-property: opacity, transform; transition-duration: var(--icon-swap-dur); transition-timing-function: var(--motion-smooth-out); } :root[data-motion-input="pointer"] .motion-feedback[data-motion-feedback="count"] { transition-property: opacity, transform; transition-duration: var(--badge-pop-dur); transition-timing-function: var(--motion-smooth-out); } :root[data-motion-input="pointer"] .motion-feedback[data-motion-feedback="alert"] { transition-property: opacity, transform; transition-duration: var(--toast-open); transition-timing-function: var(--motion-smooth-out); } @starting-style { .motion-feedback[data-motion-feedback="icon"] { opacity: 0; transform: scale(0.96); } .motion-feedback[data-motion-feedback="count"] { opacity: 0; transform: translateY(2px) scale(0.96); } .motion-feedback[data-motion-feedback="alert"] { opacity: 0; transform: translateY(4px); } } .chat-toggle-icon-close { opacity: 0; } .chat-toggle:is(:hover, :focus-visible) .chat-toggle-icon-default { opacity: 0; } .chat-toggle:is(:hover, :focus-visible) .chat-toggle-icon-close { opacity: 0.5; } :root[data-motion-input="pointer"] .chat-toggle-icon { transition: opacity var(--duration-fast) var(--ease-out); } :root[data-motion-input="pointer"] .motion-picker-option { transition: background-color var(--duration-fast) var(--ease-out), scale var(--duration-fast) var(--ease-out); } body { margin: 0; background: var(--color-ground); color: var(--color-text-primary); font-family: var(--font-sans); /* macOS subpixel smoothing thickens Inter past the weight asked for. */ -webkit-font-smoothing: antialiased; -moz-osx-font-smoothing: grayscale; } /* One owner per edge: top chrome owns the top inset, compact navigation the bottom one. */ .room-header { padding-right: calc(0.5rem + env(safe-area-inset-right)); padding-left: calc(0.5rem + env(safe-area-inset-left)); padding-top: calc(0.5rem + env(safe-area-inset-top)); } .hosted-header { height: calc(3.5rem + env(safe-area-inset-top)); padding-right: calc(0.75rem + env(safe-area-inset-right)); padding-left: calc(0.75rem + env(safe-area-inset-left)); padding-top: env(safe-area-inset-top); } .workspace-navigation { padding-right: calc(0.25rem + env(safe-area-inset-right)); padding-bottom: calc(0.25rem + env(safe-area-inset-bottom)); padding-left: calc(0.25rem + env(safe-area-inset-left)); } .workspace-navigation [aria-current="page"] { background-color: var(--color-page); color: var(--color-text-primary); box-shadow: var(--shadow-resting); } /* The split workspace is one elevated surface; its panes only own the divider. */ .workspace-root[data-workspace-mode="split"] .workspace-frame { margin-left: calc(var(--spacing) * 3 + env(safe-area-inset-left)); margin-right: calc(var(--spacing) * 3 + env(safe-area-inset-right)); } .workspace-frame .plan-document { box-shadow: none; } .workspace-frame .workspace-chat-panel { border-color: rgb(0 0 0 / 9%); } .workspace-root[data-workspace-mode="split"] .workspace-chat-panel { --panel-direction: -1; } .workspace-root[data-workspace-mode="split"] .workspace-frame > main { min-width: 9rem; } .motion-panel { opacity: 0; transform: translateX(calc(var(--panel-distance) * var(--panel-direction, 1))); transition: opacity var(--panel-open-dur) var(--motion-smooth-out), transform var(--panel-open-dur) var(--motion-smooth-out); will-change: opacity, transform; } .motion-panel.is-open { opacity: 1; transform: translateX(0); } .motion-panel.is-closing { pointer-events: none; opacity: 0; transform: translateX(calc(var(--panel-distance) * var(--panel-direction, 1))); transition-duration: var(--panel-close-dur); } .content-swap-stack { display: grid; } .content-swap-stack > .motion-content-swap { grid-area: 1 / 1; min-width: 0; } .motion-content-swap { opacity: 0; transform: translateX(var(--page-slide-distance)); transition: opacity var(--page-slide-dur) var(--motion-smooth-out), transform var(--page-slide-dur) var(--motion-smooth-out); will-change: opacity, transform; } .motion-content-swap.is-open { opacity: 1; transform: none; will-change: auto; } .motion-content-swap.is-closing { pointer-events: none; opacity: 0; transform: translateX(calc(var(--page-slide-distance) * -1)); } .workspace-root:not([data-workspace-mode="split"]) .workspace-chat-panel { position: absolute; inset: 0; z-index: 10; } .chat-message-body:has([data-analysis-trigger]) { min-height: calc(var(--spacing) * 7); } .chat-composer .field { min-height: 98px; border-color: var(--color-edge); border-radius: var(--radius-lg); box-shadow: var(--shadow-resting); } .chat-composer .field:focus-within { outline: 2px solid var(--color-brand); outline-offset: 2px; } .chat-composer textarea:focus { outline: none; } .chat-header-control:hover:not(:disabled), .chat-header-control:active:not(:disabled) { background-color: transparent; } .chat-composer .send-action:disabled { background-color: var(--color-brand); color: var(--color-page); } @media (min-width: 640px) { .room-header { padding-right: calc(1.25rem + env(safe-area-inset-right)); padding-left: calc(1.25rem + env(safe-area-inset-left)); padding-top: env(safe-area-inset-top); } .hosted-header { padding-right: calc(1.5rem + env(safe-area-inset-right)); padding-left: calc(1.5rem + env(safe-area-inset-left)); } } @media (min-width: 1200px) { /* The desktop composer keeps the same clearance on each exposed edge. */ .chat-composer { padding-inline: 1rem; padding-bottom: calc(1rem + env(safe-area-inset-bottom)); } } @media (max-width: 639px) { /* Touch-sized header controls and their text labels share a vertical center. */ .room-header { align-items: center; } .room-member-face:nth-child(n+4) { display: none; } .room-member-overflow { display: inline; } .repository-picker-trigger { max-width: min(8rem, 36vw); } } :root[data-plan-coarse-pointer] .room-header :where(button, summary), :root[data-plan-coarse-pointer] .chat-composer button, :root[data-plan-coarse-pointer] .hosted-header button, :root[data-plan-coarse-pointer] .repository-picker-search, :root[data-plan-coarse-pointer] .channel-create-input, :root[data-plan-coarse-pointer] [data-hosted] .btn, :root[data-plan-coarse-pointer] .question-choice-row, :root[data-plan-coarse-pointer] .question-actions button, :root[data-plan-coarse-pointer] :where(button, [role="button"])[data-press="wide"] { min-width: 44px; min-height: 44px; } :root[data-plan-coarse-pointer] .question-choice-row { padding-block: 0.5rem; } .question-field { scroll-margin-block-end: calc(var(--keyboard-inset, 0px) + 1rem); } /* The decision card: a question, lettered options, and a quiet footer. */ .question-card { padding: 1rem 0.5rem 0.75rem; } /* Coloured icon holders: the global icon colour would otherwise win. */ :is(.question-mark, .question-check) [data-nucleo-icon], .question-callout .plan-research-badge [data-nucleo-icon] { color: inherit; } .question-head { display: flex; align-items: flex-start; gap: 0.625rem; padding-inline: 0.5rem; } .question-mark { display: grid; flex: none; place-items: center; inline-size: 1.5rem; block-size: 1.5rem; border-radius: var(--radius-full); background: var(--color-success-wash); color: var(--color-success); } .question-head-text { flex: 1; min-width: 0; } .question-title { /* Centre the first line on the 1.5rem mark, then lift 2px optically. */ margin: calc((1.5rem - 1lh) / 2 - 0.125rem) 0 0; font-size: var(--text-base); line-height: var(--text-lg--line-height); font-weight: 600; color: var(--color-text-primary); overflow-wrap: break-word; text-wrap: pretty; } .question-title-link { font: inherit; } .question-hint { margin: 0.125rem 0 0; font-size: var(--text-xs); line-height: var(--text-xs--line-height); color: var(--color-text-tertiary); } .question-options { display: grid; gap: 0.125rem; min-width: 0; margin: 0.75rem 0 0; padding: 0; border: 0; } .question-option { position: relative; display: flex; align-items: flex-start; gap: 0.625rem; padding: 0.375rem 0.75rem 0.375rem 0.5rem; border-radius: var(--radius-md); font-size: var(--text-sm); line-height: var(--text-sm--line-height); transition: background-color var(--duration-fast) var(--ease-out); } /* The native control stays focusable but unseen; its label row is the target. */ .question-input { appearance: none; position: absolute; inset-block-start: 0; inset-inline-start: 0; inline-size: 1px; block-size: 1px; margin: 0; opacity: 0; } label.question-option:has(.question-input:not(:disabled)) { cursor: pointer; } .question-option:has(.question-input:focus-visible) { outline: var(--focus-ring-width) solid var(--focus-ring-color); outline-offset: var(--focus-ring-offset); } .question-option:has(.question-input:not(:disabled)):hover { background: var(--color-hover); } .question-option:has(.question-input:checked), .question-option:has(.question-input:checked:not(:disabled)):hover { background: var(--color-brand-wash); } /* A one-line slot (inherits the label's line height) holding a small tile. */ .question-key { display: flex; flex: none; align-items: center; block-size: 1lh; } /* Same 1.5rem as the header mark, so option text shares the title's left edge. */ .question-key > span { display: grid; place-items: center; inline-size: 1.5rem; block-size: 1.5rem; border-radius: var(--radius-sm); background: var(--color-gray-150); font-size: var(--text-xs); line-height: 1; font-weight: 500; color: var(--color-text-secondary); transition: background-color var(--duration-fast) var(--ease-out), color var(--duration-fast) var(--ease-out); } .question-option:has(.question-input:checked) .question-key > span { background: var(--color-brand); color: var(--color-white); } /* With a description below, the tile sits 4px under the label's centre (optical). */ .question-option:has(.question-desc) .question-key > span { translate: 0 0.25rem; } .question-text { display: grid; flex: 1; min-width: 0; } .question-label { font-weight: 500; color: var(--color-text-primary); } .question-desc { color: var(--color-text-secondary); } .question-check { flex: none; display: grid; block-size: 1lh; place-items: center; color: var(--color-brand); opacity: 0; transition: opacity var(--duration-fast) var(--ease-out); } .question-option:has(.question-input:checked) .question-check { opacity: 1; } .question-add, .question-add .question-key { color: var(--color-text-tertiary); } .question-add .question-key > span { background: var(--color-inset); } :is(.question-add, .question-adding) { align-items: center; padding-block: 0.5rem; } .question-add:hover .question-text, .question-add:hover .question-key { color: var(--color-text-secondary); } /* The add row is a real button, not a label around a hidden input. */ button.question-add { inline-size: 100%; border: 0; background: transparent; text-align: start; cursor: pointer; } button.question-add:not(:disabled):hover { background: var(--color-hover); } button.question-add:focus-visible { outline: var(--focus-ring-width) solid var(--focus-ring-color); outline-offset: var(--focus-ring-offset); } button.question-add:disabled { cursor: default; } /* The new option, shown before the server confirms it. */ .question-pending { opacity: 0.6; } .question-adding { outline: var(--edge-width) solid var(--color-control-edge); outline-offset: calc(-1 * var(--edge-width)); } .question-adding:focus-within { outline-color: var(--color-brand); } .question-field { flex: 1; min-width: 0; padding: 0; border: 0; background: transparent; font: inherit; color: var(--color-text-primary); outline: none; resize: none; field-sizing: content; } .question-field:focus-visible { outline: none; } .question-field::placeholder { color: var(--color-text-tertiary); } .question-card[data-saving] .question-options { opacity: 0.6; } /* Matches the research card: danger badge beside pale-red callout text. */ .plan-research-callout.question-callout { display: flex; align-items: flex-start; gap: 0.5rem; margin: 0.5rem 0.5rem 0; padding: 0.5rem 0.625rem 0.5rem 0.5rem; } .question-callout p { display: grid; } .question-callout strong { font-weight: 500; } .question-actions { display: flex; align-items: center; justify-content: flex-end; gap: 0.375rem; margin-block-start: 0.75rem; padding-inline: 0.5rem; } @media (prefers-reduced-motion: no-preference) { .question-actions > * { animation: question-fade var(--duration-fast) var(--ease-out); } } @keyframes question-fade { from { opacity: 0; } } /* Discard confirm: the footer swaps in place and fades in. */ .question-confirm { margin-inline-end: auto; font-size: var(--text-sm); font-weight: 500; color: var(--color-text-primary); } /* Footer stepper, left: ‹ question name n/m ›, 28px carets and --text-sm. */ .question-stepper { display: inline-flex; min-width: 0; align-items: center; gap: 0.125rem; margin-inline: -0.375rem auto; } /* 28px button, 16px chevron: drop btn-icon's padding so the icon isn't squeezed. */ .question-caret { padding: 0; } .question-caret[data-flip] [data-nucleo-icon] { rotate: 180deg; } .question-count { display: inline-flex; min-width: 0; gap: 0.375rem; padding-inline: 0.125rem; font-size: var(--text-sm); color: var(--color-text-tertiary); font-variant-numeric: tabular-nums; } .question-count strong { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; font-weight: 500; color: var(--color-text-primary); } /* * What focus looks like, once, for everything that can take it. An outline * rather than a ring: it follows `border-radius` and takes no space in the * layout. Components may still own their focused fill or other state; the * indicator's colour, width, and offset stay here. */ [data-focus-boundary] { --focus-ring-offset: -2px; } :where(a, button, input, select, summary, textarea, [role="button"], [tabindex]):focus-visible { outline: var(--focus-ring-width) solid var(--focus-ring-color); outline-offset: var(--focus-ring-offset); } /* * The ring answers "where am I?" for keyboard users, so it hides after a pointer * press. Text fields keep theirs: a caret alone is easy to lose. */ :root[data-focus-input="pointer"] :where(button, select, summary, [role="button"], [role="listbox"], [role="option"], [tabindex]:not(input, textarea)):focus-visible { outline: none; } /* A listbox holds the keyboard's place on its highlighted option instead. */ :where(.plan-language-menu[role="listbox"]):focus-visible { outline: none; } /* The editable plan has a live caret, so a second focus marker is redundant. */ :where(.focus-caret):focus-visible { outline: none; } .workspace-working-indicator { width: 0.875rem; height: 0.875rem; border: 2px solid currentColor; border-right-color: transparent; border-radius: var(--radius-full); } @media (prefers-reduced-motion: reduce) { .btn[aria-busy="true"] [data-button-loader] { animation: none; } .motion-dropdown, .motion-popover, .motion-panel, .motion-content-swap, .motion-collapse, .motion-collapse-content { transition: none; } :root[data-motion-input="pointer"] .chat-toggle-icon { transition: none; } :root[data-motion-input="pointer"] .motion-picker-option { transition: none; } :root[data-motion-input="pointer"] .motion-feedback[data-motion-feedback] { transition-duration: 0s; } } /* * What a press feels like. Zero specificity so a `transition` utility on the * element wins instead — Tailwind's covers `scale` along with the colours. * Disabled controls are excluded: a press that animates and then does nothing * says the opposite of what disabled is trying to say. */ :where(button, [role="button"]) { transition: scale var(--duration-fast) var(--ease-out); } /* * The scale is a proportion, so the default is subtle: 4% of a full-width row * is several pixels at each edge and reads as jumping, where 4% of a 28px * toolbar cell is the one pixel that is wanted. Small controls opt in to the * stronger press with `.btn-icon`, `.btn-sm`, or `data-press="small"`; a * full-screen backdrop opts out with `data-press="none"`. */ @media (prefers-reduced-motion: no-preference) { :where(button, [role="button"]):not(:disabled):active { scale: 0.99; } :where(button, [role="button"]):where(.btn-icon, .btn-sm, [data-press="small"]):not(:disabled):active { scale: 0.96; } :where(button, [role="button"])[data-press="none"]:not(:disabled):active { scale: none; } }