/* 05-documents - part of the former static/style.css.
*
* The stylesheet is split by area, but the split is mechanical: rules were
* moved verbatim and NOT regrouped. The numeric prefix is the load order, and
* that order is what preserves the cascade. A rule sits in the file its
* original position fell into, so an area's rules are not all in its file and
* a file is not only that area's rules.
*
* Do not reorder the tags in static/index.html, and do not move a rule
* between files: with 1,151 redeclarations and 1,886 !important in this
* cascade, either can change which declaration wins.
*
* static/index.html and the PRECACHE list in static/sw.js must agree on this
* order. tests/test_static_stylesheet_manifest.py and
* tests/test_css_computed_style_snapshot.py fail if that drifts.
*/
.memory-suggestions-actions {
position: relative;
left: -4px;
}
.memory-suggestions-actions .memory-item-btn,
.memory-suggestion-actions .memory-item-btn {
background: color-mix(in srgb, var(--fg) 6%, transparent);
border-color: color-mix(in srgb, var(--border) 85%, transparent);
}
.memory-suggestions-actions .memory-item-btn.save,
.memory-suggestion-actions .memory-item-btn.save {
background: color-mix(in srgb, var(--red) 9%, transparent);
border-color: color-mix(in srgb, var(--red) 28%, transparent);
}
.memory-suggestion-item {
display: flex;
align-items: flex-start;
justify-content: space-between;
gap: 6px;
padding: 5px 8px;
border: 1px solid var(--border);
border-radius: 6px;
background: color-mix(in srgb, var(--red) 3%, transparent);
}
/* Tidy animations */
.memory-tidy-editing {
border-color: color-mix(in srgb, var(--hl-function) 40%, transparent);
background: color-mix(in srgb, var(--hl-function) 6%, transparent);
transition: all 0.3s;
}
.memory-tidy-text-old {
opacity: 0.4;
text-decoration: line-through;
transition: opacity 0.25s;
}
.memory-tidy-text-new {
color: var(--hl-function);
transition: color 0.4s;
}
.memory-tidy-removing {
text-decoration: line-through;
opacity: 0;
max-height: 0;
padding-top: 0;
padding-bottom: 0;
margin-top: 0;
margin-bottom: 0;
border-color: transparent;
overflow: hidden;
transition: opacity 0.3s, max-height 0.4s 0.1s, padding 0.4s 0.1s, border-color 0.3s;
}
/* ===== DOCUMENT EDITOR (ARTIFACTS) PANEL ===== */
/* Doc editor is a body-level sibling of chat-container */
/* Divider between chat and doc editor */
.doc-divider {
width: 1px;
flex-shrink: 0;
background: color-mix(in srgb, var(--fg) 11%, transparent);
cursor: col-resize;
transition: background 0.15s;
position: relative;
z-index: 10;
}
.doc-divider::before {
content: '';
position: absolute;
top: 0; bottom: 0;
left: -10px;
width: 21px;
cursor: col-resize;
}
.doc-divider:hover {
background: color-mix(in srgb, var(--fg) 30%, transparent);
}
/* Always-visible "›" handle on the drag divider — clickable to collapse the
panel, and signals the divider is interactive. */
.doc-divider-collapse {
position: absolute;
top: 50%;
left: 2px;
transform: translateY(-50%);
width: 20px;
height: 34px;
display: flex;
align-items: center;
justify-content: center;
border: 1px solid var(--border);
border-radius: 7px;
background: var(--panel);
color: var(--fg);
font-size: 16px;
font-weight: 700;
line-height: 1;
opacity: 0.6;
cursor: pointer;
transition: opacity 0.1s, border-color 0.1s, left 0.08s ease;
z-index: 12;
}
.doc-divider.doc-divider-handle-idle:hover .doc-divider-collapse,
.doc-divider.doc-divider-handle-idle:focus-within .doc-divider-collapse {
opacity: 0.92;
pointer-events: auto;
}
/* When in fullscreen mode (cursor outside the doc), the flap itself slides
to the OUTSIDE edge of the divider — visually belonging to the chat side
the cursor is on, not the doc. The left/right shift pairs with the glyph
rotation for a single coordinated transition. */
.doc-divider-collapse[data-mode="fullscreen"] {
left: -22px;
}
.doc-divider-collapse:hover { opacity: 1 !important; border-color: var(--accent, var(--red)); }
/* The same `›` glyph is in the markup; CSS rotates 180° for the left-pointing
(fullscreen) state. Smooth transition pairs with the chevron's slide. */
.doc-divider-collapse > span {
display: inline-block;
transition: transform 0.08s ease, opacity 0.08s ease;
}
.doc-divider-collapse[data-mode="fullscreen"] > span {
transform: rotate(180deg);
}
/* Secondary "hide panel" X button in the divider — invisible until the pane
is fullscreen, then floats just below the unfullscreen chevron so the user
has a one-tap escape that minimises the pane instead of just exiting
fullscreen. */
.doc-divider-hide {
position: absolute;
top: 50%;
left: 2px;
width: 20px;
height: 20px;
margin-top: 22px;
display: none;
align-items: center;
justify-content: center;
border: 1px solid var(--border);
border-radius: 50%;
background: var(--panel);
color: var(--accent, var(--red));
cursor: pointer;
padding: 0;
z-index: 12;
opacity: 0.85;
transition: opacity 0.15s, border-color 0.15s;
}
.doc-divider-hide:hover {
opacity: 1;
border-color: var(--accent, var(--red));
}
/* Copy / Export split button — main click copies, the caret opens the export menu. */
.doc-split-btn {
display: inline-flex;
align-items: stretch;
border: 1px solid var(--border);
border-radius: 7px;
overflow: hidden;
height: 22px;
flex-shrink: 0;
}
.doc-split-btn .doc-split-main,
.doc-split-btn .doc-split-caret {
border: none !important;
border-radius: 0 !important;
height: 100% !important;
min-height: 0 !important;
/* Keep the element fully opaque so the divider line stays crisp; dim the
glyph via colour instead (the base .doc-action-icon-btn fades the whole
element to 0.3, which also hides the divider). */
opacity: 1 !important;
color: color-mix(in srgb, var(--fg) 55%, transparent) !important;
background: none !important;
transition: color 0.12s, background 0.12s;
}
.doc-split-btn:hover .doc-split-main,
.doc-split-btn:hover .doc-split-caret { color: var(--fg) !important; }
.doc-split-btn .doc-split-caret {
border-left: 1px solid var(--border) !important;
padding: 0 5px 0 7px !important;
}
.doc-split-btn .doc-split-main:hover,
.doc-split-btn .doc-split-caret:hover {
background: color-mix(in srgb, var(--fg) 8%, transparent) !important;
color: var(--fg) !important;
}
/* Editor pane — body-level flex sibling */
.doc-editor-pane {
flex: 1;
min-width: 0;
max-width: 70vw;
container-type: inline-size;
container-name: docpane;
display: flex;
flex-direction: column;
background: var(--bg);
border-left: 1px solid var(--border);
box-shadow: -10px 0 22px rgba(0, 0, 0, 0.16);
overflow: hidden;
height: 100%;
position: relative;
z-index: 1;
color-scheme: dark;
/* Desktop entrance is driven by openPanel() so the pane has one transform
owner. The mobile sheet has its own animation below. */
animation: none;
transform-origin: right center;
will-change: clip-path, opacity;
}
@media (prefers-reduced-motion: reduce) {
.doc-editor-pane,
.doc-editor-pane.doc-pane-leaving { animation: none; }
}
.doc-loading-overlay {
position: absolute;
inset: 0;
z-index: 10;
display: flex;
align-items: center;
justify-content: center;
background: var(--bg);
}
/* ---- Tab bar ---- */
.doc-tab-bar {
display: flex;
align-items: stretch;
background: var(--bg);
border-bottom: 1px solid var(--border);
flex-shrink: 0;
height: 36px;
}
.doc-tab-scroll {
display: flex;
align-items: stretch;
flex: 1;
min-width: 0;
overflow-x: auto;
overflow-y: hidden;
scrollbar-width: none;
justify-content: flex-start;
/* Fade tabs into the bar's background at the edges (next to the scroll
arrows) so an overflowing tab dissolves instead of being hard-cut. The
fade is conditional — when we're at an edge there's nothing to fade to,
so the mask gradient becomes flat on that side (no shadow). */
-webkit-mask-image: linear-gradient(to right, transparent 0, #000 18px, #000 calc(100% - 18px), transparent 100%);
mask-image: linear-gradient(to right, transparent 0, #000 18px, #000 calc(100% - 18px), transparent 100%);
}
.doc-tab-arrow {
background: none;
border: none;
color: var(--fg);
opacity: 0.55;
cursor: pointer;
font-size: 18px;
padding: 0 6px;
flex-shrink: 0;
transition: opacity 0.15s;
line-height: 1;
display: flex;
align-items: center;
position: relative;
top: -2px;
}
.doc-tab-arrow:hover {
opacity: 1;
}
/* Mobile swipe-down grab handle at the top of the doc sheet. */
.doc-mobile-grabber { display: none; }
@media (max-width: 768px) {
body.doc-view .doc-mobile-grabber {
display: block;
flex-shrink: 0;
height: 18px;
position: relative;
background: transparent;
background-color: transparent;
background-image: none;
touch-action: none;
cursor: grab;
}
body.doc-view .doc-mobile-grabber::before {
content: '';
position: absolute;
top: 7px;
left: 50%;
transform: translateX(-50%);
width: 36px;
height: 4px;
background: var(--fg);
opacity: 0.25;
border-radius: 2px;
}
}
.doc-tab {
display: flex;
align-items: center;
gap: 6px;
padding: 0 10px;
font-family: inherit;
font-size: 11px;
color: var(--fg);
opacity: 0.4;
cursor: pointer;
white-space: nowrap;
transition: opacity 0.1s, background 0.1s;
flex-shrink: 0;
border-right: 1px solid color-mix(in srgb, var(--border) 50%, transparent);
position: relative;
}
.doc-tab:hover {
opacity: 0.7;
background: color-mix(in srgb, var(--fg) 3%, transparent);
}
.doc-tab.active {
opacity: 1;
background: color-mix(in srgb, var(--fg) 5%, transparent);
border-radius: 7px 7px 0 0;
}
.doc-tab-title {
max-width: 140px;
overflow: hidden;
text-overflow: ellipsis;
}
.doc-tab-title-input {
background: transparent;
border: 1px solid var(--fg);
border-radius: 2px;
color: var(--fg);
font-family: inherit;
font-size: 11px;
padding: 0 4px;
height: 18px;
width: 120px;
max-width: 180px;
outline: none;
}
.doc-tab-lang {
font-size: 9px;
opacity: 0.5;
}
.doc-tab-close {
display: inline-flex;
align-items: center;
justify-content: center;
background: none;
border: 1px solid transparent;
border-radius: 999px;
color: var(--fg);
opacity: 0.4;
cursor: pointer;
font-size: 18px;
line-height: 1;
width: 24px;
height: 24px;
padding: 0;
box-sizing: border-box;
margin-left: 3px;
flex-shrink: 0;
align-self: center;
transition: opacity 0.12s, color 0.12s, background 0.12s, border-color 0.12s;
}
.doc-tab-close:hover {
opacity: 1;
color: var(--fg);
background: color-mix(in srgb, var(--fg) 10%, transparent);
border-color: color-mix(in srgb, var(--fg) 12%, transparent);
}
/* Mobile-only footer (Close + Copy); hidden on desktop and in email mode. */
.doc-mobile-footer { display: none; }
.doc-tab-new {
background: none;
border: none;
color: var(--fg);
opacity: 0.25;
cursor: pointer;
font-size: 11px;
font-weight: 600;
padding: 0 10px;
transition: opacity 0.1s;
flex-shrink: 0;
display: flex;
align-items: center;
height: 100%;
}
.doc-tab-new:hover {
opacity: 0.7;
}
.doc-tab-play {
background: none;
border: none;
color: var(--fg);
opacity: 0.55;
cursor: pointer;
padding: 0 2px;
font-size: 10px;
line-height: 1;
transition: opacity 0.1s, color 0.1s;
flex-shrink: 0;
}
.doc-tab-play:hover {
opacity: 1;
color: var(--green, #4ec970);
}
.doc-tab-play.active {
opacity: 1;
color: var(--green, #4ec970);
}
.doc-tab.dragging {
opacity: 0.3;
}
.doc-tab.drag-over {
border-left: 2px solid var(--fg);
}
/* ---- HTML preview iframe ---- */
.doc-html-preview {
flex: 1;
width: 100%;
border: none;
background: #fff;
}
/* ---- Editor header ---- */
.doc-editor-header {
display: flex;
align-items: center;
gap: 6px;
padding: 6px 10px;
/* Moved to the bottom as a footer: flex order pushes it last in the pane
column, and the divider flips to the top edge. */
order: 99;
border-top: 1px solid var(--border);
flex-shrink: 0;
flex-wrap: nowrap;
min-height: 36px;
background: var(--bg);
}
/* Version now lives in the document tab, not the footer. */
#doc-version-badge { display: none !important; }
/* Language icon chip on doc tabs — sits between the version pill and title.
Hidden when empty so docs without a language don't get awkward spacing. */
.doc-tab-lang {
display: inline-flex; align-items: center;
flex-shrink: 0;
align-self: center;
}
.doc-tab-lang:empty { display: none; }
.doc-tab-lang svg { display: block; }
.doc-tab-version {
font-size: 9px;
font-weight: 600;
padding: 1px 6px;
cursor: pointer;
flex-shrink: 0;
align-self: center;
line-height: 1.4;
/* Sits to the LEFT of the title now — space it off the title text. */
margin-right: 6px;
/* Accent pill so it's obvious the version is a clickable control. */
color: var(--accent-primary, var(--red));
border: 1px solid color-mix(in srgb, var(--accent-primary, var(--red)) 45%, transparent);
background: color-mix(in srgb, var(--accent-primary, var(--red)) 12%, transparent);
border-radius: 9px;
}
.doc-tab-version:hover {
border-color: var(--accent-primary, var(--red));
background: color-mix(in srgb, var(--accent-primary, var(--red)) 22%, transparent);
}
.doc-close-btn {
order: -1;
opacity: 0.5;
flex-shrink: 0;
}
.doc-close-btn:hover {
opacity: 1;
}
.doc-editor-actions {
display: flex;
align-items: center;
gap: 6px;
flex: 1;
justify-content: flex-end;
}
.doc-left .doc-editor-actions {
margin-left: 0;
}
.doc-version-badge {
background: color-mix(in srgb, var(--red) 12%, transparent);
border: 1px solid color-mix(in srgb, var(--red) 55%, transparent);
color: var(--red);
padding: 1px 8px;
border-radius: 999px;
font-size: 10px;
font-weight: 600;
line-height: 1.4;
cursor: pointer;
user-select: none;
transition: background 0.1s, border-color 0.1s;
opacity: 0.9;
}
.doc-version-badge:hover {
opacity: 1;
background: color-mix(in srgb, var(--red) 20%, transparent);
border-color: var(--red);
}
.doc-stream-indicator {
display: flex;
align-items: center;
gap: 5px;
font-size: 11px;
color: var(--accent);
opacity: 0.9;
white-space: nowrap;
animation: doc-stream-pulse 1.5s ease-in-out infinite;
}
.doc-stream-dot {
width: 6px;
height: 6px;
border-radius: 50%;
background: var(--accent);
}
/* In the doc footer the type picker sits next to the accent Copy/Export split —
match its 28px height and 6px radius so the right-hand controls line up. */
.doc-actions-footer #doc-language-select {
height: 28px;
border-radius: 6px;
font-size: 11px;
top: 0;
}
/* Lang-type icon shown to the LEFT of the language select. Browsers won't
render SVG inside