/* 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